Oracle Scratchpad

March 9, 2007

Push_Subq

Filed under: CBO,Execution plans,Hints,Troubleshooting,Tuning — Jonathan Lewis @ 2:08 pm GMT Mar 9,2007

Another little warning for those about to upgrade from 9i to 10g. Some hints will change their behaviour. Consider the following SQL.

 
rem
rem     Script:  push_subq.sql
rem     Author:  Jonathan Lewis
rem     Dated:   Sept 2003
rem

select  /*+ push_subq */ 
        par.small_vc1, 
        chi.small_vc1 
from 
        parent  par, 
        child   chi 
where 
        par.id1 between 1001 and 2200 
and     chi.id1 = par.id1 
and     exists ( 
                select 
                        /*+ no_unnest qb_name(subq1) */ 
                        null 
                from    subtest sub1 
                where 
                        sub1.id1       = par.id1 
                and     sub1.small_vc1 = par.small_vc1 
                and     sub1.small_vc2 >= '2' 
        ) 
; 

By default if Oracle cannot unnest a subquery it postpones execution of that subquery to the end of the execution plan. I’ve used the no_unnest hint in the subquery to stop Oracle from unnesting it.

In the absence of the push_subq hint Oracle would join parent to child then run the subquery. With the push_subq hint in place Oracle will acquire data from parent, run the subquery (which correlates to the parent), then join any parent row that survives the subquery to child.

At least, that’s how it works until I get to 10g.

Historically the push_subq hint told Oracle to run any outstanding subqueries at the earliest possible moment. But in 10g this changes (for the better). Imagine I have two subqueries in the query; it is quite possible that the optimum execution plan is to run one early and the other late, but the original implementation of push_subq is ‘all or nothing’. 10g allows you to be selective about which subqueries should be pushed.

There are two options. I can simply move the push_subq hint into each of the subqueries that I want pushed or I can use a ‘parameterized’ version of the push_subq hint in the main query block.

You will notice that I have used the qb_name() hint (a new 10g hint) in the subquery to give the subquery the name “subq1”. Because the query block is now named, I can reference it by name in other hints. So I can leave my existing push_subq hint where it is, but change it to read:

push_subq(@subq1)

(The “@” sign is necessary, not a typing error). If I have other subqueries that I want pushed I can create an individual hint for each of them, naming each in turn.

It doesn’t really matter which option you choose, I can think of good arguments for either, the important point is that if you are currently using the push_subq hint you will have to change your code when you upgrade to 10g.

Not surprisingly, the no_push_subq hint  (there are some subqueries that Oracle will automatically run early)  follows the same pattern. There may be other hints that have changed in similar ways, but I haven’t gone looking for them yet.

Footnote

Execution plan with “pushed” subquery. (See also my reply to comment 10 below for a detailed explanation of reading the plan – which includes the predicate, object/alias, and outline sections from 12cR2)

-------------------------------------------------------------------------
| Id  | Operation                     | Name    | Rows  | Bytes | Cost  |
-------------------------------------------------------------------------
|   0 | SELECT STATEMENT              |         |   139 |  2502 |   101 |
|*  1 |  HASH JOIN                    |         |   139 |  2502 |    99 |
|*  2 |   TABLE ACCESS FULL           | PARENT  |    50 |   450 |    20 |
|*  3 |    TABLE ACCESS BY INDEX ROWID| SUBTEST |     1 |    14 |     2 |
|*  4 |     INDEX UNIQUE SCAN         | SUB_PK  |     1 |       |     1 |
|*  5 |   TABLE ACCESS FULL           | CHILD   |  8011 | 72099 |    78 |
-------------------------------------------------------------------------

Execution plan with a no_push_subq hint in the subquery

------------------------------------------------------------------------
| Id  | Operation                    | Name    | Rows  | Bytes | Cost  |
------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |         |     1 |    18 |  2881 |
|*  1 |  FILTER                      |         |       |       |       |
|*  2 |   HASH JOIN                  |         |  2781 | 50058 |    99 |
|*  3 |    TABLE ACCESS FULL         | PARENT  |  1001 |  9009 |    20 |
|*  4 |    TABLE ACCESS FULL         | CHILD   |  8011 | 72099 |    78 |
|*  5 |   TABLE ACCESS BY INDEX ROWID| SUBTEST |     1 |    14 |     2 |
|*  6 |    INDEX UNIQUE SCAN         | SUB_PK  |     1 |       |     1 |
------------------------------------------------------------------------

13 Comments »

  1. Jonathan, is the old “/*+ push_subq */” (without any argument) ignored in 10g ? I haven’t understood whether the new syntax is mandatory or simply a fine-grained additional variation. Thanks!

    Comment by Alberto Dell'Era — March 10, 2007 @ 9:44 am GMT Mar 10,2007 | Reply

  2. I’ve noticed this change with push_subq some time ago and i was using the method with push_subq used in subquery. But method with naming subquery and referencing it name in other hint is nice solution.

    Comment by Paweł Barut — March 10, 2007 @ 4:53 pm GMT Mar 10,2007 | Reply

  3. Alberto, the old syntax no longer works.

    Comment by Jonathan Lewis — March 11, 2007 @ 7:09 am GMT Mar 11,2007 | Reply

  4. Hi

    I have a question about push_subq.

    I can see that it works as explained with exist.doest it work ,in case of NOT EXISTS as well.

    I have a query like this – returns 14 rows,takes almost 90 mins.

    SELECT /*+   ordered  use_hash(mtp) push_subq */ ooha.org_id "OPERATING UNIT ID"
    FROM        apps.oe_transaction_types_all ott,
                apps.oe_transaction_types_tl ottt_l,
                apps.oe_order_lines_all oola,
                apps.mtl_parameters mtp,
                apps.oe_order_headers_all ooha,
                apps.hz_cust_accounts hca
    WHERE  ooha.header_id = oola.header_id AND
           ooha.org_id = oola.org_id AND
           ooha.order_source_id = 1263 AND
           ott.transaction_type_id = ottt_l.transaction_type_id AND
           ott.org_id = ooha.org_id AND
           oola.line_type_id = ottt_l.transaction_type_id AND
           oola.line_type_id = ott.transaction_type_id AND
           mtp.organization_id = oola.ship_from_org_id AND
           hca.cust_account_id = ooha.sold_to_org_id AND
           (ottt_l.NAME LIKE 'GPO%FE_CONSIGNMENT_LINE' OR
           ottt_l.NAME LIKE 'GPO%FE_SHIPMENT_LINE') AND
           oola.flow_status_code IN ('SHIPPED', 'CLOSED') AND
           hca.attribute9 IS NOT NULL
           and NOT EXISTS
           ( SELECT /*+ NO_UNNEST */ 1
           FROM   apps.mtl_material_transactions mmt ,
                  apps.mtl_transaction_lot_numbers mtnl ,
                  apps.mtl_parameters mp ,
                  apps.mtl_item_locations mil
           WHERE  mmt.inventory_item_id = oola.inventory_item_id AND
                  mmt.transaction_type_id IN (420, 40, 41, 42) AND
                  mmt.transaction_quantity > 0 AND
                  mtnl.lot_number = ooha.order_number AND
                  mtnl.transaction_id = mmt.transaction_id AND
                  mmt.transaction_date > (TRUNC(SYSDATE) -90) AND
                  mmt.organization_id = mp.organization_id AND
                  mp.attribute7 = 'PARTS' AND
                  mp.attribute11 ='SERVICE' AND
                  mil.inventory_location_id = hca.attribute9 AND
                  mil.organization_id = mp.organization_id
           ) AND
           NVL(oola.actual_shipment_date, oola.schedule_ship_date) > SYSDATE - 90
    /
    
    its tkprof - 
    
    call     count       cpu    elapsed       disk      query    current        rows
    ------- ------  -------- ---------- ---------- ---------- ----------  ----------
    Parse        1      0.00       0.00          0          0          0           0
    Execute      1      0.00       0.00          0          0          0           0
    Fetch        2    690.28    4899.16    1524771   23494672          0          12
    ------- ------  -------- ---------- ---------- ---------- ----------  ----------
    total        4    690.28    4899.16    1524771   23494672          0          12
    
    Misses in library cache during parse: 0
    Optimizer goal: CHOOSE
    Parsing user id: 65  (APPS)
    
    Rows     Row Source Operation
    -------  ---------------------------------------------------
         12  NESTED LOOPS  (cr=23494672 r=1524771 w=0 time=4640213970 us)
     157696   NESTED LOOPS  (cr=5730245 r=1402270 w=0 time=3699650845 us)
     157697    HASH JOIN  (cr=5257151 r=1379549 w=0 time=3576187058 us)
     157697     TABLE ACCESS BY INDEX ROWID OE_ORDER_LINES_ALL (cr=5257149 r=1379549 w=0 time=3575115599 us)
    2789657      NESTED LOOPS  (cr=13232 r=12982 w=0 time=90323177 us)
         76       HASH JOIN  (cr=277 r=82 w=0 time=175347 us)
       4597        TABLE ACCESS FULL OE_TRANSACTION_TYPES_ALL (cr=157 r=0 w=0 time=6683 us)
         76        TABLE ACCESS FULL OE_TRANSACTION_TYPES_TL (cr=120 r=82 w=0 time=157808 us)
    2789580       INDEX RANGE SCAN GEMS_ONT_ORDER_LINES_N99 (cr=12955 r=12900 w=0 time=86301122 us)(object id 1863725)
        377     INDEX FULL SCAN MTL_PARAMETERS_U1 (cr=2 r=0 w=0 time=367 us)(object id 9847)
     157696    TABLE ACCESS BY INDEX ROWID OE_ORDER_HEADERS_ALL (cr=473094 r=22721 w=0 time=123022055 us)
     157697     INDEX UNIQUE SCAN OE_ORDER_HEADERS_U1 (cr=315397 r=1363 w=0 time=11565697 us)(object id 688729)
         12   TABLE ACCESS BY INDEX ROWID HZ_CUST_ACCOUNTS (cr=17764427 r=122501 w=0 time=1114177945 us)
     157696    INDEX UNIQUE SCAN HZ_CUST_ACCOUNTS_U1 (cr=315395 r=274 w=0 time=3770326 us)(object id 715003)
     157621    TABLE ACCESS BY INDEX ROWID MTL_TRANSACTION_LOT_NUMBERS (cr=17291336 r=121205 w=0 time=1096619120 us)
    6897874     NESTED LOOPS  (cr=14562731 r=114663 w=0 time=1018837983 us)
    3448931      NESTED LOOPS  (cr=7488435 r=112057 w=0 time=936487677 us)
     157633       NESTED LOOPS  (cr=788165 r=581 w=0 time=10022817 us)
     157633        INDEX RANGE SCAN MTL_ITEM_LOCATIONS_U1 (cr=472899 r=581 w=0 time=7984520 us)(object id 9761)
     157633        TABLE ACCESS BY INDEX ROWID MTL_PARAMETERS (cr=315266 r=0 w=0 time=1667395 us)
     157633         INDEX UNIQUE SCAN MTL_PARAMETERS_U1 (cr=157633 r=0 w=0 time=770942 us)(object id 9847)
    3448931       TABLE ACCESS BY INDEX ROWID MTL_MATERIAL_TRANSACTIONS (cr=6700270 r=111476 w=0 time=924106849 us)
    8273225        INDEX RANGE SCAN MTL_MATERIAL_TRANSACTIONS_N1 (cr=695175 r=32781 w=0 time=303092226 us)(object id 6205511)
    3448931      INDEX RANGE SCAN MTL_TRANSACTION_LOT_NUMBERS_N1 (cr=7074296 r=2606 w=0 time=74416944 us)(object id 9976)
    

    – my questiones are:

    how does push_subq will help or rathe is it helping or is it a valid choice here.

    the tables in NOT EXISTS have lots of data.

    is there a way I can improve the performance of this query.

    Thanks
    Ajeet

    Comment by Ajeet — April 4, 2008 @ 2:24 pm BST Apr 4,2008 | Reply

  5. Ajeet,

    Sorry, the supplied data is not formatted for easy reading. See the link to “how to comment” at top-right if you want to try again. [Now fixed – jl]

    The only thing I will say is that push_subq applies to subqueries that are not ‘unnested’ – which means that appear in the execution plans as filter operations. I didn’t see an explicit filter in your execution plan – but sometimes the filter operation is not displayed.

    Comment by Jonathan Lewis — April 16, 2008 @ 7:11 pm BST Apr 16,2008 | Reply

  6. Hi Jonathan

    I read about query transformation in your book.
    I could not uderstand why the query below is not using HASH_AJ for the not exists subquery .

    the query below runs for 1 hr to return 50K rows.

    could you please suggest what is wrong here –

    SELECT POH.SEGMENT1 ||'|' ||POH.LAST_UPDATE_DATE ||'|' ||POH.CURRENCY_CODE ||
      '|' ||POH.REVISION_NUM ||'|' ||POH.CLOSED_CODE || '|' ||POL.LINE_NUM ||'|'
      ||POL.LAST_UPDATE_DATE ||'|' ||POL.QUANTITY ||'|' ||POL.UNIT_PRICE ||'|'
      ||POL.CLOSED_CODE ||'|' ||POLL.SHIPMENT_NUM ||'|' ||POLL.LAST_UPDATE_DATE
      ||'|' ||POLL.QUANTITY ||'|' ||(SELECT COUNT(PDA2.PO_DISTRIBUTION_ID)
    FROM
     APPS.PO_DISTRIBUTIONS_ALL PDA2 WHERE PDA2.PO_HEADER_ID = POH.PO_HEADER_ID)
      ||'|' ||POLL.QUANTITY_RECEIVED ||'|' ||POLL.QUANTITY_CANCELLED ||'|'
      ||POLL.QUANTITY_BILLED ||'|' ||POLL.CLOSED_CODE ||'|' ||POH.ORG_ID ||'|'
      ||GCC.SEGMENT1||'|' ||GCC.SEGMENT2||'|' ||GCC.SEGMENT3||'|'
      ||GCC.SEGMENT4||'|' ||GCC.SEGMENT5||'|' ||GCC.SEGMENT6||'|'
      ||GCC.SEGMENT7||'|' ||GCC.SEGMENT8||'|' ||GCC.SEGMENT9||'|'
      ||PV.SEGMENT1||'|' ||PV.VENDOR_NAME||'|' ||POVS.VENDOR_SITE_CODE||'|'
      ||POH.FULL_NAME || '|' ||MC.SEGMENT1|| MC.SEGMENT2|| MC.SEGMENT3||
      MC.SEGMENT4|| MC.SEGMENT5 || '|' ||PDA.QUANTITY_ORDERED || '|'
      ||PDA.QUANTITY_DELIVERED || '|' ||POLL.NEED_BY_DATE || '|' ||NULL || '|'
      ||NULL || '|' ||NULL || '|' ||PDA.DISTRIBUTION_NUM || '|'
      ||POLL.QUANTITY_REJECTED || '|' ||POLL.DAYS_EARLY_RECEIPT_ALLOWED || '|'
      ||POLL.DAYS_LATE_RECEIPT_ALLOWED || '|' ||POLL.RECEIPT_DAYS_EXCEPTION_CODE
      || '|' ||POLL.QTY_RCV_TOLERANCE || '|' ||'|' ||'|' ||'|' ||'|' ||'|' ||'|'
      ||'|' ||'|' ||'|' ||'|' ||'|' PO_RECON_DATA_CUR FROM T3 POH ,
      APPS.PO_DISTRIBUTIONS_ALL PDA ,APPS.MTL_CATEGORIES MC ,
      APPS.GL_CODE_COMBINATIONS GCC ,APPS.PO_LINE_LOCATIONS_ALL POLL ,
      APPS.PO_LINES_ALL POL ,APPS.PO_VENDOR_SITES_ALL POVS ,APPS.PO_VENDORS PV
      WHERE PDA.PO_HEADER_ID=POH.PO_HEADER_ID AND POH.VENDOR_ID = PV.VENDOR_ID
      AND POVS.VENDOR_ID = PV.VENDOR_ID AND POVS.VENDOR_SITE_ID =
      POH.VENDOR_SITE_ID AND POVS.ORG_ID = POH.ORG_ID AND PDA.CODE_COMBINATION_ID
      = GCC.CODE_COMBINATION_ID AND POL.PO_HEADER_ID = POH.PO_HEADER_ID AND
      POLL.PO_HEADER_ID = POH.PO_HEADER_ID AND POLL.PO_LINE_ID = POL.PO_LINE_ID
      AND PDA.PO_LINE_ID = POLL.PO_LINE_ID AND PDA.LINE_LOCATION_ID =
      POLL.LINE_LOCATION_ID AND POL.CATEGORY_ID = MC.CATEGORY_ID AND NOT EXISTS
      (SELECT /*+ HASH_AJ(apps.ap_invoice_distributions_all)*/ 1 FROM
      APPS.AP_INVOICE_DISTRIBUTIONS_ALL WHERE PO_DISTRIBUTION_ID=
      PDA.PO_DISTRIBUTION_ID)
    
    
    call     count       cpu    elapsed       disk      query    current        rows
    ------- ------  -------- ---------- ---------- ---------- ----------  ----------
    Parse        1      0.14       0.12          0          0          0           0
    Execute      1      0.00       0.00          0          0          0           0
    Fetch    53777    431.65    4146.97     849561   15077239          0       53776
    ------- ------  -------- ---------- ---------- ---------- ----------  ----------
    total    53779    431.79    4147.10     849561   15077239          0       53776
    
    Misses in library cache during parse: 1
    Optimizer goal: CHOOSE
    Parsing user id: 65  (APPS)   (recursive depth: 1)
    
    Rows     Row Source Operation
    -------  ---------------------------------------------------
      53776  FILTER  (cr=14968161 r=830308 w=0 time=3992044684 us)
     857292   NESTED LOOPS  (cr=12395792 r=765116 w=0 time=3469343008 us)
     857292    NESTED LOOPS  (cr=9770138 r=746321 w=0 time=3257402729 us)
     817106     NESTED LOOPS  (cr=7122874 r=527908 w=0 time=2163647611 us)
     815224      NESTED LOOPS  (cr=4467909 r=254484 w=0 time=873769330 us)
     815224       NESTED LOOPS  (cr=3600016 r=254484 w=0 time=867378738 us)
     815224        NESTED LOOPS  (cr=1916899 r=254484 w=0 time=854167408 us)
     536644         HASH JOIN  (cr=67037 r=20961 w=0 time=23132035 us)
     265974          TABLE ACCESS FULL PO_VENDOR_SITES_ALL (cr=16203 r=13803 w=0 time=8342317 us)
     536644          HASH JOIN  (cr=50834 r=7158 w=0 time=10949413 us)
     146208           TABLE ACCESS FULL PO_VENDORS (cr=7332 r=0 w=0 time=156064 us)
     536644           TABLE ACCESS FULL T3 (cr=43502 r=7158 w=0 time=7288830 us)
     815224         TABLE ACCESS BY INDEX ROWID PO_LINES_ALL (cr=1849862 r=233523 w=0 time=828571445 us)
     815224          INDEX RANGE SCAN PO_LINES_U2 (cr=1169044 r=61552 w=0 time=254156446 us)(object id 10182)
     815224        TABLE ACCESS BY INDEX ROWID MTL_CATEGORIES_B (cr=1683117 r=0 w=0 time=10259002 us)
     815224         INDEX UNIQUE SCAN MTL_CATEGORIES_B_U1 (cr=867893 r=0 w=0 time=4879455 us)(object id 9632)
     815224       INDEX UNIQUE SCAN MTL_CATEGORIES_TL_U1 (cr=867893 r=0 w=0 time=4024518 us)(object id 684692)
     817106      TABLE ACCESS BY INDEX ROWID PO_LINE_LOCATIONS_ALL (cr=2654965 r=273424 w=0 time=1285786829 us)
     817106       INDEX RANGE SCAN PO_LINE_LOCATIONS_N2 (cr=1744948 r=71462 w=0 time=361371836 us)(object id 10194)
     857292     TABLE ACCESS BY INDEX ROWID PO_DISTRIBUTIONS_ALL (cr=2647264 r=218413 w=0 time=1089673131 us)
     857292      INDEX RANGE SCAN PO_DISTRIBUTIONS_N1 (cr=1748737 r=75456 w=0 time=367235456 us)(object id 10127)
     857292    TABLE ACCESS BY INDEX ROWID GL_CODE_COMBINATIONS (cr=2625654 r=18795 w=0 time=208731765 us)
     857292     INDEX UNIQUE SCAN GL_CODE_COMBINATIONS_U1 (cr=1768362 r=3248 w=0 time=41687174 us)(object id 51426)
     803516   INDEX RANGE SCAN AP_INVOICE_DISTRIBUTIONS_N7 (cr=2572369 r=65192 w=0 time=516473241 us)(object id 7199)
    

    database version is oracle 9iR2.

    Comment by Ajeet — May 7, 2008 @ 2:42 pm BST May 7,2008 | Reply

  7. Ajeet,

    I pointed out that your last commented hadn’t been formatted for readability – and the same applies to this one. [Now fixed – jl]

    Ignoring the huge mess of the query and tkprof output, though, your question is “why hasn’t Oracle changed a “not exists” subquery to an anti-join.

    You don’t need a big example to investigate this. A small example with just two tables will let you investigate that one detail and then allow you to apply the concept to more complex cases.

    Off the top of my head, there are probably only two things to investigate. (a) maybe it’s the arithmetic, (b) maybe it relates to the need for “not null” constraints on the correlating columns. (And (a2) if you want to hint it, then checking that you’ve definitely used the hints correctly).

    Comment by Jonathan Lewis — May 8, 2008 @ 7:41 am BST May 8,2008 | Reply

  8. Jonathan

    adding a not null constraint in the subquery has indeed converted that into hash anti join,
    infact in the “Cost based fundamentals” also you have written – but i was confused as it was discussed for NOT IN.
    sorry about poor formating.
    I will pay attention on this in future postings.

    Thanks

    Comment by Ajeet — May 13, 2008 @ 7:15 am BST May 13,2008 | Reply

  9. […] push_subq: how upgrades can stop hints working. […]

    Pingback by Rules for Hinting « Oracle Scratchpad — December 2, 2010 @ 8:57 am GMT Dec 2,2010 | Reply

  10. Hi Jonathan,

    Thanks for the post.Looks like interpreting plans with pushed subqueries is tricky.
    How can i interpret this plan and the order of execution.

    Execution plan with “pushed” subquery

    -------------------------------------------------------------------------
    | Id  | Operation                     | Name    | Rows  | Bytes | Cost  |
    -------------------------------------------------------------------------
    |   0 | SELECT STATEMENT              |         |   139 |  2502 |   101 |
    |*  1 |  HASH JOIN                    |         |   139 |  2502 |    99 |
    |*  2 |   TABLE ACCESS FULL           | PARENT  |    50 |   450 |    20 |
    |*  3 |    TABLE ACCESS BY INDEX ROWID| SUBTEST |     1 |    14 |     2 |
    |*  4 |     INDEX UNIQUE SCAN         | SUB_PK  |     1 |       |     1 |
    |*  5 |   TABLE ACCESS FULL           | CHILD   |  8011 | 72099 |    78 |
    -------------------------------------------------------------------------
    

    Thank you,
    sri

    Comment by sri — May 20, 2020 @ 3:50 pm BST May 20,2020 | Reply

    • Sri,

      Here’s the plan (from 12.2.0.1, so some differences in numbers) with the predciate, outline and alias information added.

      -------------------------------------------------------------------------
      | Id  | Operation                     | Name    | Rows  | Bytes | Cost  |
      -------------------------------------------------------------------------
      |   0 | SELECT STATEMENT              |         |   196 |  3528 |    89 |
      |*  1 |  HASH JOIN                    |         |   196 |  3528 |    87 |
      |*  2 |   TABLE ACCESS FULL           | PARENT  |    60 |   540 |     9 |
      |*  3 |    TABLE ACCESS BY INDEX ROWID| SUBTEST |     1 |    14 |     2 |
      |*  4 |     INDEX UNIQUE SCAN         | SUB_PK  |     1 |       |     1 |
      |*  5 |   TABLE ACCESS FULL           | CHILD   |  9611 | 86499 |    77 |
      -------------------------------------------------------------------------
      
      Query Block Name / Object Alias (identified by operation id):
      -------------------------------------------------------------
         1 - SEL$1
         2 - SEL$1 / PAR@SEL$1
         3 - SEL$2 / SUB@SEL$2
         4 - SEL$2 / SUB@SEL$2
         5 - SEL$1 / CHI@SEL$1
      
      Outline Data
      -------------
        /*+
            BEGIN_OUTLINE_DATA
            INDEX_RS_ASC(@"SEL$2" "SUB"@"SEL$2" ("SUBTEST"."ID1"))
            PUSH_SUBQ(@"SEL$2")
            USE_HASH(@"SEL$1" "CHI"@"SEL$1")
            LEADING(@"SEL$1" "PAR"@"SEL$1" "CHI"@"SEL$1")
            FULL(@"SEL$1" "CHI"@"SEL$1")
            FULL(@"SEL$1" "PAR"@"SEL$1")
            OUTLINE_LEAF(@"SEL$1")
            OUTLINE_LEAF(@"SEL$2")
            ALL_ROWS
            OPT_PARAM('_optimizer_cost_model' 'io')
            DB_VERSION('12.2.0.1')
            OPTIMIZER_FEATURES_ENABLE('12.2.0.1')
            IGNORE_OPTIM_EMBEDDED_HINTS
            END_OUTLINE_DATA
        */
      
      Predicate Information (identified by operation id):
      ---------------------------------------------------
         1 - access("CHI"."ID1"="PAR"."ID1")
         2 - filter("PAR"."ID1">=1001 AND "PAR"."ID1"<=2200 AND  EXISTS
                    (SELECT /*+ PUSH_SUBQ NO_UNNEST */ 0 FROM "SUBTEST" "SUB" WHERE
                    "SUB"."ID1"=:B1 AND "SUB"."SMALL_VC1"=:B2 AND "SUB"."SMALL_VC2">='2'))
         3 - filter("SUB"."SMALL_VC1"=:B1 AND "SUB"."SMALL_VC2">='2')
         4 - access("SUB"."ID1"=:B1)
         5 - filter("CHI"."ID1">=1001 AND "CHI"."ID1"<=2200)
      
      
      

      The query block/ object alias information shows you that operations 3 and 4 make up a separate query block (sel$2) in the final query, and the predicate information shows you that operation 2 includes a FILTER() subquery which includes the hints /*+ no_unnest push_subq */.

      From this you should see that order of activity is;

      operation 1:
      Call first child (operation 2) to supply data for the build table

      operation 2:
      Table scan of table parent, for each row execute the existence subquery (operations 3 and 4); for each row that survives the existence test pass the row up to operation 1.

      operation 1
      Call second child (operation 5) to supply probe table

      operation 5
      Table scan of child selecting relevant rows, pass resulting rows up to operation 1

      operation 1
      For each row from operation 5 probe the in-memory build table; for each row that survives pass up to operaiton 0 / client.

      Order of rowsource generation: 2, 4, 3, 5, 1

      Comment by Jonathan Lewis — May 20, 2020 @ 4:46 pm BST May 20,2020 | Reply

  11. Hello Jonathan,

    Thank you for the post. We have very limited information about the subquery plan interpretation.

    From the outline section,we have

       OUTLINE_LEAF(@"SEL$1")
       OUTLINE_LEAF(@"SEL$2")----&gt;It is related to subquery
    

    What about the first one OUTLINE_LEAF(@”SEL$1″)–>

    From query block section,it shows both

    2 - SEL$1 / PAR@SEL$1
    

    and

    5 - SEL$1 / CHI@SEL$1
    

    Please clarify.
    Regards,
    Sandeep.

    Comment by Sandeep — August 16, 2023 @ 12:26 pm BST Aug 16,2023 | Reply

    • The outline_leaf() items are the final set of query blocks that the optimizes has produced “sub-plans” for before stitching the sub-plans together to produce the final full execution plan. (The outline() items are the original query blocks that the optimizer sees in the SQL as you’ve written it – sometimes, as in this case, the two lists are identical.)

      It’s not always straightforward to unstitch the query blocks in the plan, and the plan doesn’t always report a query block name for every operation, but in this case it’s quite simple.

      If you look at just the sel$1 operations you get the following plan – which is telling you that the main body of the plan is a hash join with tablescans.

      -------------------------------------------------------------------------
      | Id  | Operation                     | Name    | Rows  | Bytes | Cost  |
      -------------------------------------------------------------------------
      |*  1 |  HASH JOIN                    |         |   196 |  3528 |    87 |
      |*  2 |   TABLE ACCESS FULL           | PARENT  |    60 |   540 |     9 |
      |*  5 |   TABLE ACCESS FULL           | CHILD   |  9611 | 86499 |    77 |
      -------------------------------------------------------------------------
      

      If you look at the just the sel$2 operations you get the following:

      -------------------------------------------------------------------------
      | Id  | Operation                     | Name    | Rows  | Bytes | Cost  |
      -------------------------------------------------------------------------
      |*  3 |    TABLE ACCESS BY INDEX ROWID| SUBTEST |     1 |    14 |     2 |
      |*  4 |     INDEX UNIQUE SCAN         | SUB_PK  |     1 |       |     1 |
      -------------------------------------------------------------------------
      

      This tells you how the subquery was handled.

      We should know that a filter subquery (nominally) executes once for each from “somwhere”, so when we look at the two sub-plans together we can infer that that subquery executes once for each row returned from the tablescan in operation 2, before it is passed up (assuming it passes the subquery test) to operation 1.

      Regards
      Jonathan Lewis

      Comment by Jonathan Lewis — August 16, 2023 @ 1:44 pm BST Aug 16,2023 | Reply


RSS feed for comments on this post. TrackBack URI

Leave a reply to Rules for Hinting « Oracle Scratchpad Cancel reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Website Powered by WordPress.com.