Friday, May 25, 2012

Subquery Factoring

Subquery Factoring:
I have a small collection of postings where I’ve described anomalies or limitations in subquery factoring (the “with subquery”, or Common Table Expression (CTE) to give it the official ANSI name). Here’s another example of Oracle’s code not behaving consistently. You may recognise the basic query from yesterday’s example of logical tuning – so I won’t reprint the code to generate the data sets. This examples in this note were created on 11.2.0.2 – we start with a simple query and its execution plan:
select
*
from
t2
where
not exists (
select null
from t3
where n3a = n2a
and n3b = n2b
and id3 = id2
)
and not exists (
select null
from t3
where n3a = n2a
and n3b = n2b
and id3 = id2 + 1000
)
;

-------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost |
-------------------------------------------------------------
| 0 | SELECT STATEMENT | | 50 | 7200 | 16 |
| 1 | NESTED LOOPS ANTI | | 50 | 7200 | 16 |
| 2 | NESTED LOOPS ANTI | | 4999 | 644K| 16 |
| 3 | TABLE ACCESS FULL| T2 | 5000 | 585K| 16 |
|* 4 | INDEX UNIQUE SCAN| T3_PK | 1 | 12 | |
|* 5 | INDEX UNIQUE SCAN | T3_PK | 5000 | 60000 | |
-------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
4 - access("N3A"="N2A" AND "N3B"="N2B" AND "ID3"="ID2"+1000)
5 - access("N3A"="N2A" AND "N3B"="N2B" AND "ID3"="ID2")


Now what happens if I take this piece of SQL (which is really the core of a much more complex query), put it into a CTE, and reference it later ? In one of the queries in the client’s application I wanted to materialize a piece of code like this because the result set was used multiple times in the body of the query. You’ll notice that I got two anti-joins when my test case ran as a “naked” SQL statement, but look what happens when I try to use the CTE mechanism in the simplest possible way:
with cte as (
select
/*+ materialize */
*
from
t2
where
not exists (
select
null
from t3
where n3a = n2a
and n3b = n2b
and id3 = id2
)
and not exists (
select
null
from t3
where n3a = n2a
and n3b = n2b
and id3 = id2 + 1000
)
)
select
*
from
cte
;

------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost |
------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 5000 | 468K| 5029 |
| 1 | TEMP TABLE TRANSFORMATION | | | | |
| 2 | LOAD AS SELECT | SYS_TEMP_0FD9D6641_40101879 | | | |
|* 3 | FILTER | | | | |
| 4 | TABLE ACCESS FULL | T2 | 5000 | 585K| 15 |
|* 5 | INDEX UNIQUE SCAN | T3_PK | 1 | 12 | 1 |
|* 6 | INDEX UNIQUE SCAN | T3_PK | 1 | 12 | 1 |
| 7 | VIEW | | 5000 | 468K| 14 |
| 8 | TABLE ACCESS FULL | SYS_TEMP_0FD9D6641_40101879 | 5000 | 585K| 14 |
------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
3 - filter( NOT EXISTS (SELECT 0 FROM "T3" "T3" WHERE "ID3"=:B1 AND "N3B"=:B2
AND "N3A"=:B3) AND NOT EXISTS (SELECT 0 FROM "T3" "T3" WHERE "ID3"=:B4+1000 AND
"N3B"=:B5 AND "N3A"=:B6))
5 - access("N3A"=:B1 AND "N3B"=:B2 AND "ID3"=:B3)
6 - access("N3A"=:B1 AND "N3B"=:B2 AND "ID3"=:B3+1000)


When I try to materialize the view Oracle reverts to a pair of filter subqueries. If I take away the /*+ materialize */ hint the CTE goes inline and the query optimizes with anti-joins – just as it did in the original form – so the behaviour isn’t as inconsistent as some of the earlier cases I’ve documented. I can get the behaviour I want by adding /*+ unnest */ hints to the two subqueries so the problem isn’t a show-stopper, but it’s just a little irritating to have to do this.
Here’s what I found in the 10053 trace file with the attempt to materialize:
SU:   Checking validity of unnesting subquery SEL$2 (#0)
SU: SU bypassed: invalidated.
SU: Validity checks failed.
SU: Checking validity of unnesting subquery SEL$3 (#0)
SU: SU bypassed: invalidated.
SU: Validity checks failed.

That’s just a little strange, considering that the equivalent section of the trace for the version where the CTE goes inline reads as folllows:
SU:   Checking validity of unnesting subquery SEL$2 (#3)
SU: Passed validity checks.
SU: Unnesting subquery query block SEL$2 (#3)SU: Transform ALL/NOTEXISTS subquery into a regular antijoin.
SU: Checking validity of unnesting subquery SEL$3 (#2)
SU: Passed validity checks.
SU: Unnesting subquery query block SEL$3 (#2)SU: Transform ALL/NOTEXISTS subquery into a regular antijoin.

Thank goodness we can still hint – and then generate SQL Baselines.




DIGITAL JUICE

No comments:

Post a Comment

Thank's!