{"id":73199,"date":"2014-05-28T15:26:33","date_gmt":"2014-05-28T15:26:33","guid":{"rendered":"https:\/\/www.red-gate.com\/simple-talk\/uncategorized\/execution-plans-part-5-first-child-variations\/"},"modified":"2021-07-14T13:07:32","modified_gmt":"2021-07-14T13:07:32","slug":"execution-plans-part-5-first-child-variations","status":"publish","type":"post","link":"https:\/\/www.red-gate.com\/simple-talk\/databases\/oracle-databases\/execution-plans-part-5-first-child-variations\/","title":{"rendered":"Execution Plans part 5: First Child Variations"},"content":{"rendered":"<p>I&#8217;ve offered up <em>&#8220;first child first, recursive descent&#8221;<\/em> as a basic strategy for interpreting execution plans &#8211; but it&#8217;s not a perfect rule, and it can be easy to lose track of what\u2019s going on even when the basic \u201cfirst child first\u201d is being obeyed. In this article we\u2019ll be looking at a couple of examples where we will still be using \u201cfirst child first\u201d some of the time (though slightly camouflaged), an example of a bug which makes the rule look wrong, and one example where \u201cfirst child first\u201d doesn\u2019t apply. There are several different cases, in fact, where the rule doesn&#8217;t apply, but we\u2019ll have to wait for part 6 to see more of them.<\/p>\n<h2>Subquery Update<\/h2>\n<p>The examples I&#8217;ll cover in this article are: updates, scalar subqueries in the select list, and subquery factoring. To cover as many examples as possible I&#8217;ll supply just a sample statement with plan and make a few comments; I won&#8217;t be supplying the full code to reproduce the tables and data.<\/p>\n<p>The first example is an update with subquery; partly because DML plans rarely appear in the literature of execution plans and partly because it will be useful to contrast it with my second example. Here\u2019s my sample statement:<\/p>\n<pre>update t1 set \r\n\tn1 = (\r\n\t\tselect\tmax(mod100)\r\n\t\tfrom\t\tt2\r\n\t\twhere\t\tt2.id = t1.id\r\n\t),\r\n\tn2 = (\r\n\t\tselect\tmax(trunc100)\r\n\t\tfrom\t\tt3\r\n\t\twhere\t\tt3.id = t1.id\r\n\t)\r\nwhere\r\n\tid between 101 and 200\r\n;<\/pre>\n<p>Looking at the query there are three \u201cintuitively obvious\u201d steps. First we find the rows to update then, for each row, we run the t2 subquery followed by the t3 subquery: and that\u2019s exactly what we see in the plan.<\/p>\n<pre>---------------------------------------------------------------------------------------\r\n| Id  | Operation                     | Name  | Rows  | Bytes | Cost (%CPU)| Time     |\r\n---------------------------------------------------------------------------------------\r\n|   0 | UPDATE STATEMENT              |       |   101 |  1212 |   610  (34)| 00:00:04 |\r\n|   1 |  UPDATE                       | T1    |       |       |            |          |\r\n|*  2 |   INDEX RANGE SCAN            | T1_I1 |   101 |  1212 |     2   (0)| 00:00:01 |\r\n|   3 |   SORT AGGREGATE              |       |     1 |     7 |            |          |\r\n|   4 |    FIRST ROW                  |       |     1 |     7 |     2   (0)| 00:00:01 |\r\n|*  5 |     INDEX RANGE SCAN (MIN\/MAX)| T2_I1 |     1 |     7 |     2   (0)| 00:00:01 |\r\n|   6 |   SORT AGGREGATE              |       |     1 |     7 |            |          |\r\n|   7 |    FIRST ROW                  |       |     1 |     7 |     2   (0)| 00:00:01 |\r\n|*  8 |     INDEX RANGE SCAN (MIN\/MAX)| T3_I1 |     1 |     7 |     2   (0)| 00:00:01 |\r\n---------------------------------------------------------------------------------------\r\n\r\nPredicate Information (identified by operation id):\r\n---------------------------------------------------\r\n   2 - access(\"ID\"&gt;=101 AND \"ID\"&lt;=200)\r\n   5 - access(\"T2\".\"ID\"=:B1)\r\n   8 - access(\"T3\".\"ID\"=:B1)<\/pre>\n<p>As you can see, this plan follows \u201cfirst child first\u201d exactly. The update operation at line 1 has three children: lines 2, 3 and 6. The first child is the index range scan that lets us find the rowids of the rows to be updated \u2013 the second child produces the subplan (lines 3 \u2013 5) that represents the subquery updating column <em><strong>n1<\/strong><\/em>, the third child produces the subplan (lines 6 \u2013 8) that represents the subquery updating column <em><strong>n2<\/strong><\/em>.<\/p>\n<p>There is a little oddity with the cost of this plan (see also: <a href=\"http:\/\/jonathanlewis.wordpress.com\/2014\/05\/02\/costing-bug\/\" target=\"_blank\">http:\/\/jonathanlewis.wordpress.com\/2014\/05\/02\/costing-bug\/<\/a>) \u2013 the total cost of 610 seems to come from summing the cost of executing the two subqueries 101 times (which is reasonable) then adding the cost of visiting the table 101 times <strong>for each subquery<\/strong> (which is not reasonable).<\/p>\n<h2>Scalar Subqueries<\/h2>\n<p>For my second example I\u2019m going to take this statement and turn it into a query that shows how the data would be changed by the update. All I have to do is take each of the subqueries from the update and write it as an inline scalar subquery in the select list. In this example we see the \u201cfirst child first\u201d rule coming close to being turned upside down:<\/p>\n<pre>select\r\n\tn1, n2,\r\n\t(\r\n\t\tselect\tmax(mod100)\r\n\t\tfrom\tt2\r\n\t\twhere\tt2.id = t1.id\r\n\t) new_n1,\r\n\t(\r\n\t\tselect\tmax(trunc100)\r\n\t\tfrom\tt3\r\n\t\twhere\tt3.id = t1.id\r\n\t) new_n2\r\nfrom\r\n\tt1\r\nwhere\r\n\tt1.id between 101 and 200\r\n;\r\n\r\n--------------------------------------------------------------------------------------\r\n| Id  | Operation                    | Name  | Rows  | Bytes | Cost (%CPU)| Time     |\r\n--------------------------------------------------------------------------------------\r\n|   0 | SELECT STATEMENT             |       |   101 |  1212 |     4   (0)| 00:00:01 |\r\n|   1 |  SORT AGGREGATE              |       |     1 |     7 |            |          |\r\n|   2 |   FIRST ROW                  |       |     1 |     7 |     2   (0)| 00:00:01 |\r\n|*  3 |    INDEX RANGE SCAN (MIN\/MAX)| T2_I1 |     1 |     7 |     2   (0)| 00:00:01 |\r\n|   4 |  SORT AGGREGATE              |       |     1 |     7 |            |          |\r\n|   5 |   FIRST ROW                  |       |     1 |     7 |     2   (0)| 00:00:01 |\r\n|*  6 |    INDEX RANGE SCAN (MIN\/MAX)| T3_I1 |     1 |     7 |     2   (0)| 00:00:01 |\r\n|   7 |  TABLE ACCESS BY INDEX ROWID | T1    |   101 |  1212 |     4   (0)| 00:00:01 |\r\n|*  8 |   INDEX RANGE SCAN           | T1_I1 |   101 |       |     2   (0)| 00:00:01 |\r\n--------------------------------------------------------------------------------------\r\n\r\nPredicate Information (identified by operation id):\r\n---------------------------------------------------\r\n   3 - access(\"T2\".\"ID\"=:B1)\r\n   6 - access(\"T3\".\"ID\"=:B1)\r\n   8 - access(\"T1\".\"ID\"&gt;=101 AND \"T1\".\"ID\"&lt;=200)<\/pre>\n<p>The plan tells use that the select statement at line 0 has three child rows (lines 1, 4, and 7); but when we compare our intuitive understanding of what must happen with the order of the child operations we see that it\u2019s the <em><strong>last<\/strong><\/em> child that represents the starting point of the driving query.\u00a0When you have scalar subqueries in the select list then the last child in the plan is the first child to be called, and the other children, which represent the scalar subqueries, are called subsequently (in order from first to last but one).<\/p>\n<p>Again there\u2019s an odd detail in the costing of the query \u2013 at no point does the optimizer attempt to calculate a cost for the entire query; all it\u2019s given you is the cost of running the three main parts of the query once each even though, in principle, it has information that tells it that the two scalar subqueries might be run 101 times each.<\/p>\n<p>If you run this test on 12c you might see the optimizer using a new \u201cscalar subquery\u201d optimization that transforms the two scalar subqueries into outer joins \u2013 and if this happens the costing will be correct.<\/p>\n<h2>Presentation bug<\/h2>\n<p>Inevitably it is always possible to write increasingly complex SQL, and it takes just a tiny extra effort to hit a display error relating to scalar subqueries \u2013 what if we wanted to use data from table <em><strong>t2<\/strong><\/em> to update some rows in <em><strong>t1<\/strong><\/em>, and data from table <em><strong>t3<\/strong><\/em> to update others? We might use a <em><strong>decode()<\/strong><\/em> statement to switch between scalar subqueries. Here\u2019s a query (rather than an update) demonstrating the principle and the associated bug:<\/p>\n<pre>select\r\n\tn1,\r\n\tdecode(mod(n1,4),\r\n\t\t0,\t(\r\n\t\t\tselect\tmax(mod100)\r\n\t\t\tfrom\tt2\r\n\t\t\twhere\tt2.id = t1.id\r\n\t\t\t),\r\n\t\t\t(\r\n\t\t\tselect\tmax(trunc100)\r\n\t\t\tfrom\tt3\r\n\t\t\twhere\tt3.id = t1.id\r\n\t\t\t)\r\n\t)\r\nfrom\r\n\tt1\r\nwhere\r\n\tt1.id between 101 and 200\r\n;\r\n\r\n-----------------------------------------------------------------------------------------\r\n| Id  | Operation                       | Name  | Rows  | Bytes | Cost (%CPU)| Time     |\r\n-----------------------------------------------------------------------------------------\r\n|   0 | SELECT STATEMENT                |       |   101 |   808 |     4   (0)| 00:00:01 |\r\n|   1 |  SORT AGGREGATE                 |       |     1 |     7 |            |          |\r\n|   2 |   FIRST ROW                     |       |     1 |     7 |     2   (0)| 00:00:01 |\r\n|*  3 |    INDEX RANGE SCAN (MIN\/MAX)   | T2_I1 |     1 |     7 |     2   (0)| 00:00:01 |\r\n|   4 |     SORT AGGREGATE              |       |     1 |     7 |            |          |\r\n|   5 |      FIRST ROW                  |       |     1 |     7 |     2   (0)| 00:00:01 |\r\n|*  6 |       INDEX RANGE SCAN (MIN\/MAX)| T3_I1 |     1 |     7 |     2   (0)| 00:00:01 |\r\n|   7 |  TABLE ACCESS BY INDEX ROWID    | T1    |   101 |   808 |     4   (0)| 00:00:01 |\r\n|*  8 |   INDEX RANGE SCAN              | T1_I1 |   101 |       |     2   (0)| 00:00:01 |\r\n-----------------------------------------------------------------------------------------\r\n\r\nPredicate Information (identified by operation id):\r\n---------------------------------------------------\r\n   3 - access(\"T2\".\"ID\"=:B1)\r\n   6 - access(\"T3\".\"ID\"=:B1)\r\n   8 - access(\"T1\".\"ID\"&gt;=101 AND \"T1\".\"ID\"&lt;=200)<\/pre>\n<p>As you can see in the query, if <em><strong>n1<\/strong><\/em> is a multiple of 4 we return a value from <em><strong>t2<\/strong><\/em>, if not we return a result from <em><strong>t3<\/strong><\/em>. Looking at the query it seems quite reasonable to believe that the two subqueries are in some way &#8220;on a par&#8221; with each other but when we look at the execution plan this doesn&#8217;t seem to be the case.<\/p>\n<p>Of course, we start by noting that the driving query appears as the last child of the select statement, but then we notice that there is only one other child of the select statement and, if we apply \u201cfirst child first\u201d we find that, apparently, the subquery against <em><strong>t3<\/strong><\/em> seems to be a child of the subquery against <em><strong>t2<\/strong><\/em>. It seems as if \u201cfirst child first\u201d is telling use that we have to operate lines 4 \u2013 6 to produce a rowsource that we can then pass up through lines 3,2, and 1.<\/p>\n<p>It would actually be possible to write a query that produces a plan that looks just like the one above and requires exactly that order of work (the predicate section would look different, though) but what I&#8217;ve described is not what\u2019s going on here; the plan is wrong. (12c uses the same plan, and makes the same error.)<\/p>\n<p>I explained earlier on in this series that Oracle calculates a <em><strong>depth<\/strong><\/em> value for each line of a plan, and we can select that column from the plan table (or equivalent dynamic performance views) to produce the indentation for execution plans, and I pointed out that sometimes the optimizer calculates the wrong value for this depth. This example is one of those cases, and we might need to write our own code (using a <strong>connect by<\/strong> query on the <em><strong>parent_id<\/strong><\/em> and <em><strong>id<\/strong><\/em> columns of the plan table) to get the right shape to the plan.<\/p>\n<p>Rather than writing that query to show you what the plan should look like, though, I&#8217;ve taken an easier route. I&#8217;ve simply executed the query with <em><strong>sql_trace<\/strong><\/em> enabled and used <em><strong>tkprof<\/strong><\/em> on the resulting trace file. Here\u2019s the result (with a little cosmetic editing \u2013 I&#8217;ve only run the query once so I&#8217;ve cut out the <em><strong>Rows(avg)<\/strong><\/em> and <em><strong>Rows(max)<\/strong><\/em> columns that were added to the <em><strong>tkprof<\/strong><\/em> output in 11g, and I\u2019ve also removed references to the <em><strong>object_id<\/strong><\/em> from the ends of lines):<\/p>\n<pre>Rows (1st)  Row Source Operation\r\n----------  ---------------------------------------------------\r\n        25  SORT AGGREGATE (cr=11 pr=0 pw=0 time=126 us)\r\n        25   FIRST ROW  (cr=11 pr=0 pw=0 time=83 us cost=2 size=7 card=1)\r\n        25    INDEX RANGE SCAN (MIN\/MAX) T2_I1 (cr=11 pr=0 pw=0 time=74 us cost=2 size=7 card=1)\r\n        75  SORT AGGREGATE (cr=11 pr=0 pw=0 time=241 us)\r\n        75   FIRST ROW  (cr=11 pr=0 pw=0 time=166 us cost=2 size=7 card=1)\r\n        75    INDEX RANGE SCAN (MIN\/MAX) T3_I1 (cr=11 pr=0 pw=0 time=140 us cost=2 size=7 card=1)\r\n       100  TABLE ACCESS BY INDEX ROWID T1 (cr=13 pr=0 pw=0 time=82 us cost=4 size=808 card=101)\r\n       100   INDEX RANGE SCAN T1_I1 (cr=6 pr=0 pw=0 time=654 us cost=2 size=0 card=101)<\/pre>\n<p>As you can see from this output the two subqueries are equivalent children of the select statement, just as they were in the earlier select statement. The trace file doesn&#8217;t hold the depth information; its STAT lines only hold the <em><strong>id<\/strong><\/em> and <em><strong>parent_id<\/strong><\/em> (labeled <em><strong>id<\/strong><\/em> and <em><strong>pid<\/strong><\/em> respectively in the raw trace file), so <em><strong>tkprof<\/strong><\/em> has to derive the depth, which gives us the right shape for the plan.<\/p>\n<p>Another nice feature of the <em><strong>tkprof<\/strong><\/em> output, of course, is that we can look at the <em>\u201cRows (1st)\u201d<\/em> column and see that the subquery against <em><strong>t2<\/strong><\/em> returned a total of 25 rows while the subquery against <em><strong>t3<\/strong><\/em> returned a total of 75. Looking back at the original query we were expecting (or hoping) that one query would run 25 times and the other 75 times, so in this case we have some corroborating evidence.<\/p>\n<p>Note: we don\u2019t actually have enough information in the output to know that this really is the case \u2013 we are jumping to a conclusion based on our understanding of the data and the query: in principle both queries could have run 100 times each, returning data 25% and 75% of the time respectively \u2013 what we really need is the \u201cstarts\u201d statistics from the internal <em><strong>v$sql_plan_statistics<\/strong><\/em> view but that view is going to produce the wrong shape plan if we use <em><strong>dbms_xplan<\/strong><\/em> to query it \u2013 and we shouldn&#8217;t use a connect by query on the view for performance reasons \u2013 so we have to look in two places to get the right shape plan from one and the correct statistics from another.<\/p>\n<h2>Subquery Factoring<\/h2>\n<p>I pointed out that 12c was able to use a new transformation to turn scalar subqueries into joins. Let\u2019s go back to an earlier query \u2013 the one with the two simple inline scalar subqueries \u2013 and emulate that plan in 11g. Here\u2019s one way we might do it:<\/p>\n<pre>with sq2 as (\r\n\tselect\t\/*+ materialize *\/\r\n\t\tt2.id, max(t2.mod100)\tnew_n1\r\n\tfrom\tt2\r\n\twhere\tt2.id between 101 and 200\r\n\tgroup by t2.id\r\n),\r\nsq3 as (\r\n\tselect\t\/*+ materialize *\/\r\n\t\tt3.id, max(t3.trunc100) new_n2\r\n\tfrom\tt3\r\n\twhere\tt3.id between 101 and 200\r\n\tgroup by t3.id\r\n)\r\nselect\r\n\tt1.n1, t1.n2,\r\n\tsq2.new_n1,\r\n\tsq3.new_n2\r\nfrom\r\n\tt1, sq2, sq3\r\nwhere\r\n\tt1.id between 101 and 200\r\nand\tsq2.id(+) = t1.id\r\nand\tsq3.id(+) = t1.id\r\n;\r\n\r\n-----------------------------------------------------------------------------------------\r\n| Id | Operation                      | Name     | Rows | Bytes | Cost (%CPU)| Time     |\r\n-----------------------------------------------------------------------------------------\r\n|  0 | SELECT STATEMENT               |          |  101 |  6464 |    12   (0)| 00:00:01 |\r\n|  1 |  TEMP TABLE TRANSFORMATION     |          |      |       |            |          |\r\n|  2 |   LOAD AS SELECT               | SYS_TEMP |      |       |            |          |\r\n|  3 |    SORT GROUP BY NOSORT        |          |  101 |   707 |     2   (0)| 00:00:01 |\r\n|* 4 |     INDEX RANGE SCAN           | T2_I1    |  101 |   707 |     2   (0)| 00:00:01 |\r\n|  5 |   LOAD AS SELECT               | SYS_TEMP |      |       |            |          |\r\n|  6 |    SORT GROUP BY NOSORT        |          |  101 |   707 |     2   (0)| 00:00:01 |\r\n|* 7 |     INDEX RANGE SCAN           | T3_I1    |  101 |   707 |     2   (0)| 00:00:01 |\r\n|* 8 |   HASH JOIN OUTER              |          |  101 |  6464 |     8   (0)| 00:00:01 |\r\n|* 9 |    HASH JOIN OUTER             |          |  101 |  3838 |     6   (0)| 00:00:01 |\r\n| 10 |     TABLE ACCESS BY INDEX ROWID| T1       |  101 |  1212 |     4   (0)| 00:00:01 |\r\n|*11 |      INDEX RANGE SCAN          | T1_I1    |  101 |       |     2   (0)| 00:00:01 |\r\n|*12 |     VIEW                       |          |  101 |  2626 |     2   (0)| 00:00:01 |\r\n| 13 |      TABLE ACCESS FULL         | SYS_TEMP |  101 |   707 |     2   (0)| 00:00:01 |\r\n|*14 |    VIEW                        |          |  101 |  2626 |     2   (0)| 00:00:01 |\r\n| 15 |     TABLE ACCESS FULL          | SYS_TEMP |  101 |   707 |     2   (0)| 00:00:01 |\r\n-----------------------------------------------------------------------------------------\r\n\r\nPredicate Information (identified by operation id):\r\n---------------------------------------------------\r\n   4 - access(\"T2\".\"ID\"&gt;=101 AND \"T2\".\"ID\"&lt;=200)\r\n   7 - access(\"T3\".\"ID\"&gt;=101 AND \"T3\".\"ID\"&lt;=200) \r\n   8 - access(\"SQ3\".\"ID\"(+)=\"T1\".\"ID\") \r\n   9 - access(\"SQ2\".\"ID\"(+)=\"T1\".\"ID\") \r\n  11 - access(\"T1\".\"ID\"&gt;=101 AND \"T1\".\"ID\"&lt;=200) \r\n  12 - filter(\"SQ2\".\"ID\"(+)&gt;=101 AND \"SQ2\".\"ID\"(+)&lt;=200) \r\n  14 - filter(\"SQ3\".\"ID\"(+)&gt;=101 AND \"SQ3\".\"ID\"(+)&lt;=200)<\/pre>\n<p>(To reduce the width of the plan output I&#8217;ve trimmed the name of the temporary table references down to <em><strong>sys_temp<\/strong><\/em> from the original <em><strong>sys_temp_0fd9d6611_770b8e1<\/strong><\/em>.)<\/p>\n<p>I&#8217;ve used subquery factoring with a <em><strong>\/*+ materialize *\/<\/strong><\/em> hint to force Oracle into creating a pair of internal global temporary tables (GTTs) that hold the results that we will need from <em><strong>t2<\/strong><\/em> and <em><strong>t3<\/strong><\/em>, then I\u2019ve written the rest of the code to do an outer join from <em><strong>t1<\/strong><\/em> to these two result sets. In fact I could have left out the hints and Oracle would have copied the \u201cfactored subqueries\u201d inline which would have produced a similar set of outer hash joins with the two aggregate result sets being held in a workarea in the session memory. I chose the materialize option simply to show the appearance of a plan with materialized subqueries. If we cut it back to the bare minimum (remember how we can click on \u201cminus\u201d icons the OEM\/Grid\/Cloud control screens) we would see the following:<\/p>\n<pre>-----------------------------------------------------------------------------------------\r\n| Id | Operation                      | Name     | Rows | Bytes | Cost (%CPU)| Time     |\r\n-----------------------------------------------------------------------------------------\r\n|  0 | SELECT STATEMENT               |          |  101 |  6464 |    12   (0)| 00:00:01 |\r\n|  1 |  TEMP TABLE TRANSFORMATION     |          |      |       |            |          |\r\n|  2 |   LOAD AS SELECT               | SYS_TEMP |      |       |            |          |\r\n|  5 |   LOAD AS SELECT               | SYS_TEMP |      |       |            |          |\r\n|* 8 |   HASH JOIN OUTER              |          |  101 |  6464 |     8   (0)| 00:00:01 |\r\n-----------------------------------------------------------------------------------------<\/pre>\n<p>I have to say that it\u2019s not exactly what I would like to see \u2013 I\u2019d prefer to see line 8 (the hash join) as a child to line 0 (the select statement) \u2013 nevertheless we can see that there are three major stages the plan, all children of the temp table transformation: it\u2019s \u201cfirst child first\u201d again \u2013 we create two temporary tables and then do a hash join. If we expand line 2 we see we\u2019re aggregating table <em><strong>t2<\/strong><\/em>, if we expand line 5 we see we\u2019re aggregating table <em><strong>t3<\/strong><\/em>, and if we expand line 8 we see we\u2019re doing a pair of (outer) hash joins between <em><strong>t1<\/strong><\/em> and (views of) two temporary tables.<\/p>\n<pre>-----------------------------------------------------------------------------------------\r\n| Id | Operation                      | Name     | Rows | Bytes | Cost (%CPU)| Time     |\r\n-----------------------------------------------------------------------------------------\r\n|* 8 |   HASH JOIN OUTER              |          |  101 |  6464 |     8   (0)| 00:00:01 |\r\n|* 9 |    HASH JOIN OUTER             |          |  101 |  3838 |     6   (0)| 00:00:01 |\r\n| 10 |     TABLE ACCESS BY INDEX ROWID| T1       |  101 |  1212 |     4   (0)| 00:00:01 |\r\n|*12 |     VIEW                       |          |  101 |  2626 |     2   (0)| 00:00:01 |\r\n|*14 |    VIEW                        |          |  101 |  2626 |     2   (0)| 00:00:01 |\r\n-----------------------------------------------------------------------------------------<\/pre>\n<p>Apart from the fact that the plan for the main query is pushed slightly to the right, and that there\u2019s potentially a large number of lines in the plan before we get to the main query \u2013as we saw in the plan for the query with scalar subqueries in the select list \u2013 plans that use materialization in their subquery factoring still follow the basic rule, and can be dissected simply by collapsing down the excess volume until you can see the starting line of each child of the first (proper) operation in the plan.<\/p>\n<h2>Conclusion<\/h2>\n<p>In part 5 we&#8217;ve looked at a few examples \u2013 basically about subqueries lurking somewhere within a larger query \u2013 to see how \u201cfirst child first\u201d plays out in more complex queries. In the cases we&#8217;ve examined we&#8217;ve found one example of a bug where blindly following the rule without cross-reference to the query and to alternative ways of generating the plan could lead us to an incorrect interpretation. We&#8217;ve also seen the special case for scalar subqueries, where the driving activity of the whole plan is the last child of the select operation.<\/p>\n<p>In the next instalment we\u2019ll see cases of queries where the optimizer\u2019s placement of subqueries really does break \u201cfirst child first\u201d and can fool you into misinterpreting the plan completely.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>I&#8217;ve offered up &#8220;first child first, recursive descent&#8221; as a basic strategy for interpreting execution plans &#8211; but it&#8217;s not a perfect rule, and it can be easy to lose track of what\u2019s going on even when the basic \u201cfirst child first\u201d is being obeyed. In this article we\u2019ll be looking at a couple of examples where we will st&hellip;<\/p>\n","protected":false},"author":101205,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[143533],"tags":[4783,48494,5396],"coauthors":[],"class_list":["post-73199","post","type-post","status-publish","format-standard","hentry","category-oracle-databases","tag-execution-plans","tag-scalar-subqueries","tag-subqueries"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/73199","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/users\/101205"}],"replies":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/comments?post=73199"}],"version-history":[{"count":1,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/73199\/revisions"}],"predecessor-version":[{"id":91685,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/73199\/revisions\/91685"}],"wp:attachment":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media?parent=73199"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/categories?post=73199"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/tags?post=73199"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/coauthors?post=73199"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}