{"id":2847,"date":"2026-10-14T07:00:00","date_gmt":"2026-10-14T05:00:00","guid":{"rendered":"https:\/\/blog.sqlora.com\/en\/?p=2847"},"modified":"2026-10-12T17:13:25","modified_gmt":"2026-10-12T15:13:25","slug":"oracle-26ai-qualify-under-the-hood-joelkallmanday","status":"publish","type":"post","link":"https:\/\/blog.sqlora.com\/en\/oracle-26ai-qualify-under-the-hood-joelkallmanday\/","title":{"rendered":"Oracle 26ai: QUALIFY Under the Hood #JoelKallmanDay"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">Analytic functions have been among my favorite Oracle SQL features ever since their introduction in Oracle 8i. I&#8217;ve used them extensively over the years, and I still remember how much easier they made many SQL problems that previously required rather complicated solutions.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Tom Kyte once described analytic functions as <em>&#8220;the coolest thing to happen to the SQL language since the introduction of the keyword SELECT.&#8221;<\/em> I couldn&#8217;t agree more.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">But there was one thing I kept missing: the ability to filter directly on the results of analytic functions. With the new <code>QUALIFY<\/code> clause in Oracle 26ai, this is finally possible. It&#8217;s a relatively small addition to Oracle SQL, but a long-awaited and very welcome one.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Let&#8217;s see how it works \u2013 and then take a look at something rather surprising under the hood.<\/p>\n\n\n\n<!--more-->\n\n\n\n<h2 class=\"wp-block-heading\">The Problem: Filtering on Analytic Functions<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Suppose we want to find the five best-selling products by revenue for each calendar year, using Oracle&#8217;s SH sample schema.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">We can calculate the revenue per product and year and assign a row number within each year:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: sql; title: ; notranslate\" title=\"\">\nSELECT t.calendar_year,\n       p.prod_name,\n       SUM(s.amount_sold) AS revenue,\n       ROW_NUMBER() OVER (\n         PARTITION BY t.calendar_year\n         ORDER BY SUM(s.amount_sold) DESC\n       ) AS rn\nFROM sh.sales s\nJOIN sh.times t    ON t.time_id = s.time_id\nJOIN sh.products p ON p.prod_id = s.prod_id\nGROUP BY t.calendar_year, p.prod_name;\n<\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\"><strong><em>Listing 1: Calculating revenue and row numbers per year<\/em><\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">So far, so good. But how do we restrict the result to the top five products per year?<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">We cannot simply add the filter to the WHERE or HAVING clause:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: sql; title: ; notranslate\" title=\"\">\nWHERE   ROW_NUMBER() OVER (PARTITION BY t.calendar_year\n        *\nERROR at line 9:\nORA-30483: window  functions are not allowed here\n<\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\">The <code>WHERE<\/code> clause cannot reference the result of an analytic function. It is because analytic functions are evaluated after the <code>WHERE<\/code> clause. The same applies to <code>HAVING<\/code>, which is evaluated after grouping and aggregation, but before analytic functions. Unlike aggregate functions, analytic functions do not participate in grouping.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Traditionally, we have to wrap the query in an inline view or a common table expression:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: sql; title: ; notranslate\" title=\"\">\nWITH ranked_products AS (\n  SELECT t.calendar_year,\n         p.prod_name,\n         SUM(s.amount_sold) AS revenue,\n         ROW_NUMBER() OVER (\n           PARTITION BY t.calendar_year\n           ORDER BY SUM(s.amount_sold) DESC\n         ) AS rn\n  FROM sh.sales s\n  JOIN sh.times t    ON t.time_id = s.time_id\n  JOIN sh.products p ON p.prod_id = s.prod_id\n  GROUP BY t.calendar_year, p.prod_name\n)\nSELECT calendar_year, prod_name, revenue, rn\nFROM ranked_products\nWHERE rn &lt;= 5\nORDER BY calendar_year, rn;\n\n---------------------------------------------------------------------------\n| Id  | Operation                         | Name     | Rows  | Cost (%CPU)|\n---------------------------------------------------------------------------\n|   0 | SELECT STATEMENT                  |          |    25 |  3902   (2)|\n|   1 |  SORT ORDER BY                    |          |    25 |  3902   (2)|\n|*  2 |   VIEW                            |          |    25 |  3901   (2)|\n|*  3 |    WINDOW SORT PUSHED RANK        |          |   237 |  3901   (2)|\n|   4 |     HASH GROUP BY                 |          |   237 |  3901   (2)|\n|*  5 |      HASH JOIN                    |          |   918K|  3847   (1)|\n|   6 |       PART JOIN FILTER CREATE     | :BF0000  |  1826 |    17   (0)|\n|   7 |        TABLE ACCESS FULL          | TIMES    |  1826 |    17   (0)|\n|*  8 |       HASH JOIN                   |          |   918K|  3828   (1)|\n|   9 |        TABLE ACCESS FULL          | PRODUCTS |    72 |     3   (0)|\n|  10 |        PARTITION RANGE JOIN-FILTER|          |   918K|  3823   (1)|\n|  11 |         TABLE ACCESS FULL         | SALES    |   918K|  3823   (1)|\n---------------------------------------------------------------------------\n\nPredicate Information (identified by operation id):\n---------------------------------------------------\n \n   2 - filter(&quot;RN&quot;&lt;=5)\n   3 - filter(ROW_NUMBER() OVER ( PARTITION BY &quot;T&quot;.&quot;CALENDAR_YEAR&quot; ORDER BY SUM(&quot;S&quot;.&quot;AMOUNT_SOLD&quot;) \n              DESC )&lt;=5)\n   5 - access(&quot;T&quot;.&quot;TIME_ID&quot;=&quot;S&quot;.&quot;TIME_ID&quot;)\n   8 - access(&quot;P&quot;.&quot;PROD_ID&quot;=&quot;S&quot;.&quot;PROD_ID&quot;)\n<\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\"><strong><em>Listing 2:<\/em><\/strong> <strong><em>Top-5 query using a CTE and its execution plan<\/em><\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">There is nothing fundamentally wrong with this solution. It works, and Oracle has been able to optimize such queries efficiently for a long time. Looking at the execution plan in Listing 2 (slightly shortened for readability), the most interesting operation is <code>WINDOW SORT PUSHED RANK<\/code>. In simple terms, Oracle applies the Top-N filter while processing the analytic function, rather than calculating the row numbers for all rows first and filtering them afterwards.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">But the additional query block exists only because we need to filter on the result of an analytic function. It does not add anything to the business logic.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">The Solution: QUALIFY<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Oracle 26ai introduces the <code>QUALIFY<\/code> clause, which allows us to apply filtering conditions to the results of analytic functions directly within the same query block.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Our example becomes:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: sql; highlight: [12,34]; title: ; notranslate\" title=\"\">\nSELECT t.calendar_year,\n       p.prod_name,\n       SUM(s.amount_sold) AS revenue,\n       ROW_NUMBER() OVER (\n         PARTITION BY t.calendar_year\n         ORDER BY SUM(s.amount_sold) DESC\n       ) AS rn\nFROM sh.sales s\nJOIN sh.times t    ON t.time_id = s.time_id\nJOIN sh.products p ON p.prod_id = s.prod_id\nGROUP BY t.calendar_year, p.prod_name\nQUALIFY rn &lt;= 5\nORDER BY t.calendar_year, rn;\n---------------------------------------------------------------------------\n| Id  | Operation                         | Name     | Rows  | Cost (%CPU)|\n---------------------------------------------------------------------------\n|   0 | SELECT STATEMENT                  |          |    25 |  3884   (2)|\n|   1 |  SORT ORDER BY                    |          |    25 |  3884   (2)|\n|*  2 |   VIEW                            |          |    25 |  3883   (2)|\n|*  3 |    WINDOW SORT PUSHED RANK        |          |   237 |  3883   (2)|\n|   4 |     HASH GROUP BY                 |          |   237 |  3883   (2)|\n|*  5 |      HASH JOIN                    |          |   918K|  3847   (1)|\n|   6 |       PART JOIN FILTER CREATE     | :BF0000  |  1826 |    17   (0)|\n|   7 |        TABLE ACCESS FULL          | TIMES    |  1826 |    17   (0)|\n|*  8 |       HASH JOIN                   |          |   918K|  3828   (1)|\n|   9 |        TABLE ACCESS FULL          | PRODUCTS |    72 |     3   (0)|\n|  10 |        PARTITION RANGE JOIN-FILTER|          |   918K|  3823   (1)|\n|  11 |         TABLE ACCESS FULL         | SALES    |   918K|  3823   (1)|\n---------------------------------------------------------------------------\n\nPredicate Information (identified by operation id):\n---------------------------------------------------\n \n   2 - filter(&quot;from$_subquery$_006&quot;.&quot;sys_qualify$_h$_predex$_0&quot;=TRUE)\n   3 - filter(ROW_NUMBER() OVER ( PARTITION BY &quot;T&quot;.&quot;CALENDAR_YEAR&quot; ORDER BY SUM(&quot;S&quot;.&quot;AMOUNT_SOLD&quot;) \n              DESC )&lt;=5)\n   5 - access(&quot;T&quot;.&quot;TIME_ID&quot;=&quot;S&quot;.&quot;TIME_ID&quot;)\n   8 - access(&quot;P&quot;.&quot;PROD_ID&quot;=&quot;S&quot;.&quot;PROD_ID&quot;)\n\n<\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\"><strong><em>Listing 3: Top-5 query using QUALIFY and its execution plan<\/em><\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">No inline view or additional CTE needed in our SQL.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The <code>QUALIFY<\/code> clause is evaluated after the analytic functions, so we can reference their results directly. We can also use the alias <code>rn<\/code>, which makes the condition particularly readable.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This makes <code>QUALIFY<\/code> somewhat similar to <code>HAVING<\/code>: while <code>HAVING<\/code> filters grouped results, <code>QUALIFY<\/code> filters results based on analytic functions.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">And just as with <code>HAVING<\/code>, we do not necessarily have to expose the expression used for filtering in the select list.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For example, if we are not interested in displaying the row number:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: sql; title: ; notranslate\" title=\"\">\nSELECT t.calendar_year,\n       p.prod_name,\n       SUM(s.amount_sold) AS revenue\nFROM sh.sales s\nJOIN sh.times t    ON t.time_id = s.time_id\nJOIN sh.products p ON p.prod_id = s.prod_id\nGROUP BY t.calendar_year, p.prod_name\nQUALIFY ROW_NUMBER() OVER (\n          PARTITION BY t.calendar_year\n          ORDER BY SUM(s.amount_sold) DESC\n        ) &lt;= 5\nORDER BY t.calendar_year, revenue DESC;\n<\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\"><strong><em>Listing 4: QUALIFY without displaying the row number<\/em><\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This is particularly convenient for common analytical patterns such as Top-N per group, deduplication, or selecting the latest record per business key.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In my opinion, <code>QUALIFY<\/code> is one of those features that should have been available in Oracle SQL a long time ago. It does not introduce fundamentally new functionality, but it eliminates unnecessary query blocks and makes the intention much clearer.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">What About the Execution Plan?<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Comparing the execution plans in Listings 2 and 3, we can see that they are almost identical. There are some minor differences in the estimated costs, but nothing significant. Most importantly, both plans contain the <code>WINDOW SORT PUSHED RANK<\/code> operation, so the Top-N optimization is still there.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">However, one detail in the predicate information of Listing 3 (line 34) caught my attention:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: sql; title: ; notranslate\" title=\"\">\n2 - filter(\"from$_subquery$_006\".\"sys_qualify$_h$_predex$_0\"=TRUE)\n<\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\">What exactly is <code>sys_qualify$_h$_predex$_0<\/code>? Where does this Boolean expression come from? By the way, we can also see Oracle&#8217;s long-awaited SQL Boolean data type in action here.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Whenever I investigate a new SQL feature, I usually try to find out what Oracle does with it internally. I look at query transformations, use <code>DBMS_UTILITY.EXPAND_SQL_TEXT<\/code>, and sometimes dig into the CBO trace.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Earlier this year, while preparing a presentation about different techniques for Top-N queries, I came across this very detail. And, as I already discussed in my earlier <a href=\"https:\/\/blog.sqlora.com\/en\/oracle-26ai-an-even-closer-look-at-join-to-one\/\" target=\"_blank\" rel=\"noreferrer noopener nofollow\">blog post about <code>JOIN TO ONE<\/code><\/a>, looking at Oracle&#8217;s transformed SQL can sometimes reveal interesting things \u2013 although we have to be careful when interpreting the results.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Let&#8217;s see what Oracle makes of our <code>QUALIFY<\/code> query and first pass it to <code>DBMS_UTILITY.EXPAND_SQL_TEXT<\/code>:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: sql; title: ; notranslate\" title=\"\">\nWITH FUNCTION get_sql(p_sql IN CLOB)\n  RETURN CLOB\nIS\n  p_raw CLOB;\nBEGIN\n  DBMS_UTILITY.EXPAND_SQL_TEXT(p_sql, p_raw);\n  RETURN p_raw;\nEND;\nSELECT get_sql(q&#039;&#x5B;\n  SELECT t.calendar_year,\n         p.prod_name,\n         SUM(s.amount_sold) AS revenue,\n         ROW_NUMBER() OVER (\n           PARTITION BY t.calendar_year\n           ORDER BY SUM(s.amount_sold) DESC\n         ) AS rn\n  FROM sh.sales s\n  JOIN sh.times t    ON t.time_id = s.time_id\n  JOIN sh.products p ON p.prod_id = s.prod_id\n  GROUP BY t.calendar_year, p.prod_name\n  QUALIFY rn &lt;= 5\n  ORDER BY t.calendar_year, rn\n]&#039;) AS expanded_sql;\n<\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\"><strong><em>Listing 5: Expanding the QUALIFY query with DBMS_UTILITY.EXPAND_SQL_TEXT<\/em><\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The result is surprisingly verbose. Click to see the full SQL text:<\/p>\n\n\n<pre class=\"brush: sql; collapse: true; light: false; title: Show full SQL; toolbar: true; notranslate\" title=\"Show full SQL\">\n\nSELECT &amp;amp;quot;A1&amp;amp;quot;.&amp;amp;quot;CALENDAR_YEAR&amp;amp;quot;     &amp;amp;quot;CALENDAR_YEAR&amp;amp;quot;,\n         &amp;amp;quot;A1&amp;amp;quot;.&amp;amp;quot;PROD_NAME&amp;amp;quot;         &amp;amp;quot;PROD_NAME&amp;amp;quot;,\n         &amp;amp;quot;A1&amp;amp;quot;.&amp;amp;quot;REVENUE&amp;amp;quot;           &amp;amp;quot;REVENUE&amp;amp;quot;,\n         &amp;amp;quot;A1&amp;amp;quot;.&amp;amp;quot;RN&amp;amp;quot;                &amp;amp;quot;RN&amp;amp;quot;\n    FROM (  SELECT &amp;amp;quot;A2&amp;amp;quot;.&amp;amp;quot;CALENDAR_YEAR_37&amp;amp;quot; &amp;amp;quot;CALENDAR_YEAR&amp;amp;quot;,\n                   &amp;amp;quot;A2&amp;amp;quot;.&amp;amp;quot;PROD_NAME_46&amp;amp;quot; &amp;amp;quot;PROD_NAME&amp;amp;quot;,\n                   SUM (&amp;amp;quot;A2&amp;amp;quot;.&amp;amp;quot;AMOUNT_SOLD_6&amp;amp;quot;) &amp;amp;quot;REVENUE&amp;amp;quot;,\n                   ROW_NUMBER ()\n                       OVER (PARTITION BY &amp;amp;quot;A2&amp;amp;quot;.&amp;amp;quot;CALENDAR_YEAR_37&amp;amp;quot;\n                             ORDER BY SUM (&amp;amp;quot;A2&amp;amp;quot;.&amp;amp;quot;AMOUNT_SOLD_6&amp;amp;quot;) DESC) &amp;amp;quot;RN&amp;amp;quot;,\n                   ROW_NUMBER ()\n                       OVER (PARTITION BY &amp;amp;quot;A2&amp;amp;quot;.&amp;amp;quot;CALENDAR_YEAR_37&amp;amp;quot;\n                             ORDER BY SUM (&amp;amp;quot;A2&amp;amp;quot;.&amp;amp;quot;AMOUNT_SOLD_6&amp;amp;quot;) DESC) &amp;amp;lt;=  5  &amp;amp;quot;sys_qualify$_h$_predex$_0&amp;amp;quot;,\n                   &amp;amp;quot;A2&amp;amp;quot;.&amp;amp;quot;CALENDAR_YEAR_37&amp;amp;quot; &amp;amp;quot;sys_qualify$_h$_obyex$_00000&amp;amp;quot;,\n                   ROW_NUMBER ()\n                       OVER (PARTITION BY &amp;amp;quot;A2&amp;amp;quot;.&amp;amp;quot;CALENDAR_YEAR_37&amp;amp;quot;\n                             ORDER BY SUM (&amp;amp;quot;A2&amp;amp;quot;.&amp;amp;quot;AMOUNT_SOLD_6&amp;amp;quot;) DESC) &amp;amp;quot;sys_qualify$_h$_obyex$_00001&amp;amp;quot;\n              FROM (SELECT &amp;amp;quot;A4&amp;amp;quot;.&amp;amp;quot;PROD_ID_0&amp;amp;quot; &amp;amp;quot;QCSJ_C000000000500000&amp;amp;quot;,\n                           &amp;amp;quot;A4&amp;amp;quot;.&amp;amp;quot;CUST_ID_1&amp;amp;quot; &amp;amp;quot;CUST_ID&amp;amp;quot;,\n                           &amp;amp;quot;A4&amp;amp;quot;.&amp;amp;quot;QCSJ_C000000000300000_2&amp;amp;quot; &amp;amp;quot;QCSJ_C000000000300000&amp;amp;quot;,\n                           &amp;amp;quot;A4&amp;amp;quot;.&amp;amp;quot;CHANNEL_ID_3&amp;amp;quot; &amp;amp;quot;CHANNEL_ID&amp;amp;quot;,\n                           &amp;amp;quot;A4&amp;amp;quot;.&amp;amp;quot;PROMO_ID_4&amp;amp;quot; &amp;amp;quot;PROMO_ID&amp;amp;quot;,\n                           &amp;amp;quot;A4&amp;amp;quot;.&amp;amp;quot;QUANTITY_SOLD_5&amp;amp;quot; &amp;amp;quot;QUANTITY_SOLD&amp;amp;quot;,\n                           &amp;amp;quot;A4&amp;amp;quot;.&amp;amp;quot;AMOUNT_SOLD_6&amp;amp;quot; &amp;amp;quot;AMOUNT_SOLD_6&amp;amp;quot;,\n                           &amp;amp;quot;A4&amp;amp;quot;.&amp;amp;quot;QCSJ_C000000000300001_7&amp;amp;quot; &amp;amp;quot;QCSJ_C000000000300001&amp;amp;quot;,\n                           &amp;amp;quot;A4&amp;amp;quot;.&amp;amp;quot;DAY_NAME_8&amp;amp;quot; &amp;amp;quot;DAY_NAME&amp;amp;quot;,\n                           &amp;amp;quot;A4&amp;amp;quot;.&amp;amp;quot;DAY_NUMBER_IN_WEEK_9&amp;amp;quot; &amp;amp;quot;DAY_NUMBER_IN_WEEK&amp;amp;quot;,\n                           &amp;amp;quot;A4&amp;amp;quot;.&amp;amp;quot;DAY_NUMBER_IN_MONTH_10&amp;amp;quot; &amp;amp;quot;DAY_NUMBER_IN_MONTH&amp;amp;quot;,\n                           &amp;amp;quot;A4&amp;amp;quot;.&amp;amp;quot;CALENDAR_WEEK_NUMBER_11&amp;amp;quot; &amp;amp;quot;CALENDAR_WEEK_NUMBER&amp;amp;quot;,\n                           &amp;amp;quot;A4&amp;amp;quot;.&amp;amp;quot;FISCAL_WEEK_NUMBER_12&amp;amp;quot; &amp;amp;quot;FISCAL_WEEK_NUMBER&amp;amp;quot;,\n                           &amp;amp;quot;A4&amp;amp;quot;.&amp;amp;quot;WEEK_ENDING_DAY_13&amp;amp;quot; &amp;amp;quot;WEEK_ENDING_DAY&amp;amp;quot;,\n                           &amp;amp;quot;A4&amp;amp;quot;.&amp;amp;quot;WEEK_ENDING_DAY_ID_14&amp;amp;quot; &amp;amp;quot;WEEK_ENDING_DAY_ID&amp;amp;quot;,\n                           &amp;amp;quot;A4&amp;amp;quot;.&amp;amp;quot;CALENDAR_MONTH_NUMBER_15&amp;amp;quot; &amp;amp;quot;CALENDAR_MONTH_NUMBER&amp;amp;quot;,\n                           &amp;amp;quot;A4&amp;amp;quot;.&amp;amp;quot;FISCAL_MONTH_NUMBER_16&amp;amp;quot; &amp;amp;quot;FISCAL_MONTH_NUMBER&amp;amp;quot;,\n                           &amp;amp;quot;A4&amp;amp;quot;.&amp;amp;quot;CALENDAR_MONTH_DESC_17&amp;amp;quot; &amp;amp;quot;CALENDAR_MONTH_DESC&amp;amp;quot;,\n                           &amp;amp;quot;A4&amp;amp;quot;.&amp;amp;quot;CALENDAR_MONTH_ID_18&amp;amp;quot; &amp;amp;quot;CALENDAR_MONTH_ID&amp;amp;quot;,\n                           &amp;amp;quot;A4&amp;amp;quot;.&amp;amp;quot;FISCAL_MONTH_DESC_19&amp;amp;quot; &amp;amp;quot;FISCAL_MONTH_DESC&amp;amp;quot;,\n                           &amp;amp;quot;A4&amp;amp;quot;.&amp;amp;quot;FISCAL_MONTH_ID_20&amp;amp;quot; &amp;amp;quot;FISCAL_MONTH_ID&amp;amp;quot;,\n                           &amp;amp;quot;A4&amp;amp;quot;.&amp;amp;quot;DAYS_IN_CAL_MONTH_21&amp;amp;quot; &amp;amp;quot;DAYS_IN_CAL_MONTH&amp;amp;quot;,\n                           &amp;amp;quot;A4&amp;amp;quot;.&amp;amp;quot;DAYS_IN_FIS_MONTH_22&amp;amp;quot; &amp;amp;quot;DAYS_IN_FIS_MONTH&amp;amp;quot;,\n                           &amp;amp;quot;A4&amp;amp;quot;.&amp;amp;quot;END_OF_CAL_MONTH_23&amp;amp;quot; &amp;amp;quot;END_OF_CAL_MONTH&amp;amp;quot;,\n                           &amp;amp;quot;A4&amp;amp;quot;.&amp;amp;quot;END_OF_FIS_MONTH_24&amp;amp;quot; &amp;amp;quot;END_OF_FIS_MONTH&amp;amp;quot;,\n                           &amp;amp;quot;A4&amp;amp;quot;.&amp;amp;quot;CALENDAR_MONTH_NAME_25&amp;amp;quot; &amp;amp;quot;CALENDAR_MONTH_NAME&amp;amp;quot;,\n                           &amp;amp;quot;A4&amp;amp;quot;.&amp;amp;quot;FISCAL_MONTH_NAME_26&amp;amp;quot; &amp;amp;quot;FISCAL_MONTH_NAME&amp;amp;quot;,\n                           &amp;amp;quot;A4&amp;amp;quot;.&amp;amp;quot;CALENDAR_QUARTER_DESC_27&amp;amp;quot; &amp;amp;quot;CALENDAR_QUARTER_DESC&amp;amp;quot;,\n                           &amp;amp;quot;A4&amp;amp;quot;.&amp;amp;quot;CALENDAR_QUARTER_ID_28&amp;amp;quot; &amp;amp;quot;CALENDAR_QUARTER_ID&amp;amp;quot;,\n                           &amp;amp;quot;A4&amp;amp;quot;.&amp;amp;quot;FISCAL_QUARTER_DESC_29&amp;amp;quot; &amp;amp;quot;FISCAL_QUARTER_DESC&amp;amp;quot;,\n                           &amp;amp;quot;A4&amp;amp;quot;.&amp;amp;quot;FISCAL_QUARTER_ID_30&amp;amp;quot; &amp;amp;quot;FISCAL_QUARTER_ID&amp;amp;quot;,\n                           &amp;amp;quot;A4&amp;amp;quot;.&amp;amp;quot;DAYS_IN_CAL_QUARTER_31&amp;amp;quot; &amp;amp;quot;DAYS_IN_CAL_QUARTER&amp;amp;quot;,\n                           &amp;amp;quot;A4&amp;amp;quot;.&amp;amp;quot;DAYS_IN_FIS_QUARTER_32&amp;amp;quot; &amp;amp;quot;DAYS_IN_FIS_QUARTER&amp;amp;quot;,\n                           &amp;amp;quot;A4&amp;amp;quot;.&amp;amp;quot;END_OF_CAL_QUARTER_33&amp;amp;quot; &amp;amp;quot;END_OF_CAL_QUARTER&amp;amp;quot;,\n                           &amp;amp;quot;A4&amp;amp;quot;.&amp;amp;quot;END_OF_FIS_QUARTER_34&amp;amp;quot; &amp;amp;quot;END_OF_FIS_QUARTER&amp;amp;quot;,\n                           &amp;amp;quot;A4&amp;amp;quot;.&amp;amp;quot;CALENDAR_QUARTER_NUMBER_35&amp;amp;quot; &amp;amp;quot;CALENDAR_QUARTER_NUMBER&amp;amp;quot;,\n                           &amp;amp;quot;A4&amp;amp;quot;.&amp;amp;quot;FISCAL_QUARTER_NUMBER_36&amp;amp;quot; &amp;amp;quot;FISCAL_QUARTER_NUMBER&amp;amp;quot;,\n                           &amp;amp;quot;A4&amp;amp;quot;.&amp;amp;quot;CALENDAR_YEAR_37&amp;amp;quot; &amp;amp;quot;CALENDAR_YEAR_37&amp;amp;quot;,\n                           &amp;amp;quot;A4&amp;amp;quot;.&amp;amp;quot;CALENDAR_YEAR_ID_38&amp;amp;quot; &amp;amp;quot;CALENDAR_YEAR_ID&amp;amp;quot;,\n                           &amp;amp;quot;A4&amp;amp;quot;.&amp;amp;quot;FISCAL_YEAR_39&amp;amp;quot; &amp;amp;quot;FISCAL_YEAR&amp;amp;quot;,\n                           &amp;amp;quot;A4&amp;amp;quot;.&amp;amp;quot;FISCAL_YEAR_ID_40&amp;amp;quot; &amp;amp;quot;FISCAL_YEAR_ID&amp;amp;quot;,\n                           &amp;amp;quot;A4&amp;amp;quot;.&amp;amp;quot;DAYS_IN_CAL_YEAR_41&amp;amp;quot; &amp;amp;quot;DAYS_IN_CAL_YEAR&amp;amp;quot;,\n                           &amp;amp;quot;A4&amp;amp;quot;.&amp;amp;quot;DAYS_IN_FIS_YEAR_42&amp;amp;quot; &amp;amp;quot;DAYS_IN_FIS_YEAR&amp;amp;quot;,\n                           &amp;amp;quot;A4&amp;amp;quot;.&amp;amp;quot;END_OF_CAL_YEAR_43&amp;amp;quot; &amp;amp;quot;END_OF_CAL_YEAR&amp;amp;quot;,\n                           &amp;amp;quot;A4&amp;amp;quot;.&amp;amp;quot;END_OF_FIS_YEAR_44&amp;amp;quot; &amp;amp;quot;END_OF_FIS_YEAR&amp;amp;quot;,\n                           &amp;amp;quot;A3&amp;amp;quot;.&amp;amp;quot;PROD_ID&amp;amp;quot; &amp;amp;quot;QCSJ_C000000000500001&amp;amp;quot;,\n                           &amp;amp;quot;A3&amp;amp;quot;.&amp;amp;quot;PROD_NAME&amp;amp;quot; &amp;amp;quot;PROD_NAME_46&amp;amp;quot;,\n                           &amp;amp;quot;A3&amp;amp;quot;.&amp;amp;quot;PROD_DESC&amp;amp;quot; &amp;amp;quot;PROD_DESC&amp;amp;quot;,\n                           &amp;amp;quot;A3&amp;amp;quot;.&amp;amp;quot;PROD_SUBCATEGORY&amp;amp;quot; &amp;amp;quot;PROD_SUBCATEGORY&amp;amp;quot;,\n                           &amp;amp;quot;A3&amp;amp;quot;.&amp;amp;quot;PROD_SUBCATEGORY_ID&amp;amp;quot; &amp;amp;quot;PROD_SUBCATEGORY_ID&amp;amp;quot;,\n                           &amp;amp;quot;A3&amp;amp;quot;.&amp;amp;quot;PROD_SUBCATEGORY_DESC&amp;amp;quot; &amp;amp;quot;PROD_SUBCATEGORY_DESC&amp;amp;quot;,\n                           &amp;amp;quot;A3&amp;amp;quot;.&amp;amp;quot;PROD_CATEGORY&amp;amp;quot; &amp;amp;quot;PROD_CATEGORY&amp;amp;quot;,\n                           &amp;amp;quot;A3&amp;amp;quot;.&amp;amp;quot;PROD_CATEGORY_ID&amp;amp;quot; &amp;amp;quot;PROD_CATEGORY_ID&amp;amp;quot;,\n                           &amp;amp;quot;A3&amp;amp;quot;.&amp;amp;quot;PROD_CATEGORY_DESC&amp;amp;quot; &amp;amp;quot;PROD_CATEGORY_DESC&amp;amp;quot;,\n                           &amp;amp;quot;A3&amp;amp;quot;.&amp;amp;quot;PROD_WEIGHT_CLASS&amp;amp;quot; &amp;amp;quot;PROD_WEIGHT_CLASS&amp;amp;quot;,\n                           &amp;amp;quot;A3&amp;amp;quot;.&amp;amp;quot;PROD_UNIT_OF_MEASURE&amp;amp;quot; &amp;amp;quot;PROD_UNIT_OF_MEASURE&amp;amp;quot;,\n                           &amp;amp;quot;A3&amp;amp;quot;.&amp;amp;quot;PROD_PACK_SIZE&amp;amp;quot; &amp;amp;quot;PROD_PACK_SIZE&amp;amp;quot;,\n                           &amp;amp;quot;A3&amp;amp;quot;.&amp;amp;quot;SUPPLIER_ID&amp;amp;quot; &amp;amp;quot;SUPPLIER_ID&amp;amp;quot;,\n                           &amp;amp;quot;A3&amp;amp;quot;.&amp;amp;quot;PROD_STATUS&amp;amp;quot; &amp;amp;quot;PROD_STATUS&amp;amp;quot;,\n                           &amp;amp;quot;A3&amp;amp;quot;.&amp;amp;quot;PROD_LIST_PRICE&amp;amp;quot; &amp;amp;quot;PROD_LIST_PRICE&amp;amp;quot;,\n                           &amp;amp;quot;A3&amp;amp;quot;.&amp;amp;quot;PROD_MIN_PRICE&amp;amp;quot; &amp;amp;quot;PROD_MIN_PRICE&amp;amp;quot;,\n                           &amp;amp;quot;A3&amp;amp;quot;.&amp;amp;quot;PROD_TOTAL&amp;amp;quot; &amp;amp;quot;PROD_TOTAL&amp;amp;quot;,\n                           &amp;amp;quot;A3&amp;amp;quot;.&amp;amp;quot;PROD_TOTAL_ID&amp;amp;quot; &amp;amp;quot;PROD_TOTAL_ID&amp;amp;quot;,\n                           &amp;amp;quot;A3&amp;amp;quot;.&amp;amp;quot;PROD_SRC_ID&amp;amp;quot; &amp;amp;quot;PROD_SRC_ID&amp;amp;quot;,\n                           &amp;amp;quot;A3&amp;amp;quot;.&amp;amp;quot;PROD_EFF_FROM&amp;amp;quot; &amp;amp;quot;PROD_EFF_FROM&amp;amp;quot;,\n                           &amp;amp;quot;A3&amp;amp;quot;.&amp;amp;quot;PROD_EFF_TO&amp;amp;quot; &amp;amp;quot;PROD_EFF_TO&amp;amp;quot;,\n                           &amp;amp;quot;A3&amp;amp;quot;.&amp;amp;quot;PROD_VALID&amp;amp;quot; &amp;amp;quot;PROD_VALID&amp;amp;quot;\n                      FROM (SELECT &amp;amp;quot;A6&amp;amp;quot;.&amp;amp;quot;PROD_ID&amp;amp;quot; &amp;amp;quot;PROD_ID_0&amp;amp;quot;,\n                                   &amp;amp;quot;A6&amp;amp;quot;.&amp;amp;quot;CUST_ID&amp;amp;quot; &amp;amp;quot;CUST_ID_1&amp;amp;quot;,\n                                   &amp;amp;quot;A6&amp;amp;quot;.&amp;amp;quot;TIME_ID&amp;amp;quot; &amp;amp;quot;QCSJ_C000000000300000_2&amp;amp;quot;,\n                                   &amp;amp;quot;A6&amp;amp;quot;.&amp;amp;quot;CHANNEL_ID&amp;amp;quot; &amp;amp;quot;CHANNEL_ID_3&amp;amp;quot;,\n                                   &amp;amp;quot;A6&amp;amp;quot;.&amp;amp;quot;PROMO_ID&amp;amp;quot; &amp;amp;quot;PROMO_ID_4&amp;amp;quot;,\n                                   &amp;amp;quot;A6&amp;amp;quot;.&amp;amp;quot;QUANTITY_SOLD&amp;amp;quot; &amp;amp;quot;QUANTITY_SOLD_5&amp;amp;quot;,\n                                   &amp;amp;quot;A6&amp;amp;quot;.&amp;amp;quot;AMOUNT_SOLD&amp;amp;quot; &amp;amp;quot;AMOUNT_SOLD_6&amp;amp;quot;,\n                                   &amp;amp;quot;A5&amp;amp;quot;.&amp;amp;quot;TIME_ID&amp;amp;quot; &amp;amp;quot;QCSJ_C000000000300001_7&amp;amp;quot;,\n                                   &amp;amp;quot;A5&amp;amp;quot;.&amp;amp;quot;DAY_NAME&amp;amp;quot; &amp;amp;quot;DAY_NAME_8&amp;amp;quot;,\n                                   &amp;amp;quot;A5&amp;amp;quot;.&amp;amp;quot;DAY_NUMBER_IN_WEEK&amp;amp;quot; &amp;amp;quot;DAY_NUMBER_IN_WEEK_9&amp;amp;quot;,\n                                   &amp;amp;quot;A5&amp;amp;quot;.&amp;amp;quot;DAY_NUMBER_IN_MONTH&amp;amp;quot; &amp;amp;quot;DAY_NUMBER_IN_MONTH_10&amp;amp;quot;,\n                                   &amp;amp;quot;A5&amp;amp;quot;.&amp;amp;quot;CALENDAR_WEEK_NUMBER&amp;amp;quot; &amp;amp;quot;CALENDAR_WEEK_NUMBER_11&amp;amp;quot;,\n                                   &amp;amp;quot;A5&amp;amp;quot;.&amp;amp;quot;FISCAL_WEEK_NUMBER&amp;amp;quot; &amp;amp;quot;FISCAL_WEEK_NUMBER_12&amp;amp;quot;,\n                                   &amp;amp;quot;A5&amp;amp;quot;.&amp;amp;quot;WEEK_ENDING_DAY&amp;amp;quot; &amp;amp;quot;WEEK_ENDING_DAY_13&amp;amp;quot;,\n                                   &amp;amp;quot;A5&amp;amp;quot;.&amp;amp;quot;WEEK_ENDING_DAY_ID&amp;amp;quot; &amp;amp;quot;WEEK_ENDING_DAY_ID_14&amp;amp;quot;,\n                                   &amp;amp;quot;A5&amp;amp;quot;.&amp;amp;quot;CALENDAR_MONTH_NUMBER&amp;amp;quot; &amp;amp;quot;CALENDAR_MONTH_NUMBER_15&amp;amp;quot;,\n                                   &amp;amp;quot;A5&amp;amp;quot;.&amp;amp;quot;FISCAL_MONTH_NUMBER&amp;amp;quot; &amp;amp;quot;FISCAL_MONTH_NUMBER_16&amp;amp;quot;,\n                                   &amp;amp;quot;A5&amp;amp;quot;.&amp;amp;quot;CALENDAR_MONTH_DESC&amp;amp;quot; &amp;amp;quot;CALENDAR_MONTH_DESC_17&amp;amp;quot;,\n                                   &amp;amp;quot;A5&amp;amp;quot;.&amp;amp;quot;CALENDAR_MONTH_ID&amp;amp;quot; &amp;amp;quot;CALENDAR_MONTH_ID_18&amp;amp;quot;,\n                                   &amp;amp;quot;A5&amp;amp;quot;.&amp;amp;quot;FISCAL_MONTH_DESC&amp;amp;quot; &amp;amp;quot;FISCAL_MONTH_DESC_19&amp;amp;quot;,\n                                   &amp;amp;quot;A5&amp;amp;quot;.&amp;amp;quot;FISCAL_MONTH_ID&amp;amp;quot; &amp;amp;quot;FISCAL_MONTH_ID_20&amp;amp;quot;,\n                                   &amp;amp;quot;A5&amp;amp;quot;.&amp;amp;quot;DAYS_IN_CAL_MONTH&amp;amp;quot; &amp;amp;quot;DAYS_IN_CAL_MONTH_21&amp;amp;quot;,\n                                   &amp;amp;quot;A5&amp;amp;quot;.&amp;amp;quot;DAYS_IN_FIS_MONTH&amp;amp;quot; &amp;amp;quot;DAYS_IN_FIS_MONTH_22&amp;amp;quot;,\n                                   &amp;amp;quot;A5&amp;amp;quot;.&amp;amp;quot;END_OF_CAL_MONTH&amp;amp;quot; &amp;amp;quot;END_OF_CAL_MONTH_23&amp;amp;quot;,\n                                   &amp;amp;quot;A5&amp;amp;quot;.&amp;amp;quot;END_OF_FIS_MONTH&amp;amp;quot; &amp;amp;quot;END_OF_FIS_MONTH_24&amp;amp;quot;,\n                                   &amp;amp;quot;A5&amp;amp;quot;.&amp;amp;quot;CALENDAR_MONTH_NAME&amp;amp;quot; &amp;amp;quot;CALENDAR_MONTH_NAME_25&amp;amp;quot;,\n                                   &amp;amp;quot;A5&amp;amp;quot;.&amp;amp;quot;FISCAL_MONTH_NAME&amp;amp;quot; &amp;amp;quot;FISCAL_MONTH_NAME_26&amp;amp;quot;,\n                                   &amp;amp;quot;A5&amp;amp;quot;.&amp;amp;quot;CALENDAR_QUARTER_DESC&amp;amp;quot; &amp;amp;quot;CALENDAR_QUARTER_DESC_27&amp;amp;quot;,\n                                   &amp;amp;quot;A5&amp;amp;quot;.&amp;amp;quot;CALENDAR_QUARTER_ID&amp;amp;quot; &amp;amp;quot;CALENDAR_QUARTER_ID_28&amp;amp;quot;,\n                                   &amp;amp;quot;A5&amp;amp;quot;.&amp;amp;quot;FISCAL_QUARTER_DESC&amp;amp;quot; &amp;amp;quot;FISCAL_QUARTER_DESC_29&amp;amp;quot;,\n                                   &amp;amp;quot;A5&amp;amp;quot;.&amp;amp;quot;FISCAL_QUARTER_ID&amp;amp;quot; &amp;amp;quot;FISCAL_QUARTER_ID_30&amp;amp;quot;,\n                                   &amp;amp;quot;A5&amp;amp;quot;.&amp;amp;quot;DAYS_IN_CAL_QUARTER&amp;amp;quot; &amp;amp;quot;DAYS_IN_CAL_QUARTER_31&amp;amp;quot;,\n                                   &amp;amp;quot;A5&amp;amp;quot;.&amp;amp;quot;DAYS_IN_FIS_QUARTER&amp;amp;quot; &amp;amp;quot;DAYS_IN_FIS_QUARTER_32&amp;amp;quot;,\n                                   &amp;amp;quot;A5&amp;amp;quot;.&amp;amp;quot;END_OF_CAL_QUARTER&amp;amp;quot; &amp;amp;quot;END_OF_CAL_QUARTER_33&amp;amp;quot;,\n                                   &amp;amp;quot;A5&amp;amp;quot;.&amp;amp;quot;END_OF_FIS_QUARTER&amp;amp;quot; &amp;amp;quot;END_OF_FIS_QUARTER_34&amp;amp;quot;,\n                                   &amp;amp;quot;A5&amp;amp;quot;.&amp;amp;quot;CALENDAR_QUARTER_NUMBER&amp;amp;quot; &amp;amp;quot;CALENDAR_QUARTER_NUMBER_35&amp;amp;quot;,\n                                   &amp;amp;quot;A5&amp;amp;quot;.&amp;amp;quot;FISCAL_QUARTER_NUMBER&amp;amp;quot; &amp;amp;quot;FISCAL_QUARTER_NUMBER_36&amp;amp;quot;,\n                                   &amp;amp;quot;A5&amp;amp;quot;.&amp;amp;quot;CALENDAR_YEAR&amp;amp;quot; &amp;amp;quot;CALENDAR_YEAR_37&amp;amp;quot;,\n                                   &amp;amp;quot;A5&amp;amp;quot;.&amp;amp;quot;CALENDAR_YEAR_ID&amp;amp;quot; &amp;amp;quot;CALENDAR_YEAR_ID_38&amp;amp;quot;,\n                                   &amp;amp;quot;A5&amp;amp;quot;.&amp;amp;quot;FISCAL_YEAR&amp;amp;quot; &amp;amp;quot;FISCAL_YEAR_39&amp;amp;quot;,\n                                   &amp;amp;quot;A5&amp;amp;quot;.&amp;amp;quot;FISCAL_YEAR_ID&amp;amp;quot; &amp;amp;quot;FISCAL_YEAR_ID_40&amp;amp;quot;,\n                                   &amp;amp;quot;A5&amp;amp;quot;.&amp;amp;quot;DAYS_IN_CAL_YEAR&amp;amp;quot; &amp;amp;quot;DAYS_IN_CAL_YEAR_41&amp;amp;quot;,\n                                   &amp;amp;quot;A5&amp;amp;quot;.&amp;amp;quot;DAYS_IN_FIS_YEAR&amp;amp;quot; &amp;amp;quot;DAYS_IN_FIS_YEAR_42&amp;amp;quot;,\n                                   &amp;amp;quot;A5&amp;amp;quot;.&amp;amp;quot;END_OF_CAL_YEAR&amp;amp;quot; &amp;amp;quot;END_OF_CAL_YEAR_43&amp;amp;quot;,\n                                   &amp;amp;quot;A5&amp;amp;quot;.&amp;amp;quot;END_OF_FIS_YEAR&amp;amp;quot; &amp;amp;quot;END_OF_FIS_YEAR_44&amp;amp;quot;\n                              FROM &amp;amp;quot;SH&amp;amp;quot;.&amp;amp;quot;SALES&amp;amp;quot; &amp;amp;quot;A6&amp;amp;quot;, &amp;amp;quot;SH&amp;amp;quot;.&amp;amp;quot;TIMES&amp;amp;quot; &amp;amp;quot;A5&amp;amp;quot;\n                             WHERE &amp;amp;quot;A5&amp;amp;quot;.&amp;amp;quot;TIME_ID&amp;amp;quot; = &amp;amp;quot;A6&amp;amp;quot;.&amp;amp;quot;TIME_ID&amp;amp;quot;) &amp;amp;quot;A4&amp;amp;quot;,\n                           &amp;amp;quot;SH&amp;amp;quot;.&amp;amp;quot;PRODUCTS&amp;amp;quot; &amp;amp;quot;A3&amp;amp;quot;\n                     WHERE &amp;amp;quot;A3&amp;amp;quot;.&amp;amp;quot;PROD_ID&amp;amp;quot; = &amp;amp;quot;A4&amp;amp;quot;.&amp;amp;quot;PROD_ID_0&amp;amp;quot;) &amp;amp;quot;A2&amp;amp;quot;\n          GROUP BY &amp;amp;quot;A2&amp;amp;quot;.&amp;amp;quot;CALENDAR_YEAR_37&amp;amp;quot;, &amp;amp;quot;A2&amp;amp;quot;.&amp;amp;quot;PROD_NAME_46&amp;amp;quot;) &amp;amp;quot;A1&amp;amp;quot;\n   WHERE &amp;amp;quot;A1&amp;amp;quot;.&amp;amp;quot;sys_qualify$_h$_predex$_0&amp;amp;quot; = TRUE\nORDER BY &amp;amp;quot;A1&amp;amp;quot;.&amp;amp;quot;sys_qualify$_h$_obyex$_00000&amp;amp;quot;,\n         &amp;amp;quot;A1&amp;amp;quot;.&amp;amp;quot;sys_qualify$_h$_obyex$_00001&amp;amp;quot;\n<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong><em>Listing 5a: Expanded SQL text (full output)<\/em><\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Oracle generates multiple nested inline views, additional column aliases, and a number of internal expressions.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Among them are:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: sql; title: ; notranslate\" title=\"\">\nROW_NUMBER() OVER (\n  PARTITION BY ...\n  ORDER BY ...\n) AS rn,\n\nROW_NUMBER() OVER (\n  PARTITION BY ...\n  ORDER BY ...\n) &lt;= 5 AS &quot;sys_qualify$_h$_predex$_0&quot;\n<\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\">The outer query then applies:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: sql; title: ; notranslate\" title=\"\">\nWHERE \"A1\".\"sys_qualify$_h$_predex$_0\" = TRUE\n<\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\">In other words, Oracle represents the <code>QUALIFY<\/code> condition as an additional Boolean expression in an inner query block and filters on that expression in the outer query.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">The CBO&#8217;s Final Query<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Let\u2019s compare this with the Final Query after Transformations from the CBO trace.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The <em>Final query after transformations<\/em> contains essentially the same <code>QUALIFY<\/code> transformation, but without the extensive expansion of the joins.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Here is the SQL query extracted from the CBO trace:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: sql; title: ; notranslate\" title=\"\">\nSELECT &quot;from$_subquery$_006&quot;.&quot;CALENDAR_YEAR&quot;     &quot;CALENDAR_YEAR&quot;,\n         &quot;from$_subquery$_006&quot;.&quot;PROD_NAME&quot;         &quot;PROD_NAME&quot;,\n         &quot;from$_subquery$_006&quot;.&quot;REVENUE&quot;           &quot;REVENUE&quot;,\n         &quot;from$_subquery$_006&quot;.&quot;RN&quot;                &quot;RN&quot;\n    FROM (  SELECT &quot;T&quot;.&quot;CALENDAR_YEAR&quot;\n                       &quot;CALENDAR_YEAR&quot;,\n                   &quot;P&quot;.&quot;PROD_NAME&quot;\n                       &quot;PROD_NAME&quot;,\n                   SUM (&quot;S&quot;.&quot;AMOUNT_SOLD&quot;)\n                       &quot;REVENUE&quot;,\n                   ROW_NUMBER ()\n                       OVER (PARTITION BY &quot;T&quot;.&quot;CALENDAR_YEAR&quot;\n                             ORDER BY SUM (&quot;S&quot;.&quot;AMOUNT_SOLD&quot;) DESC)\n                       &quot;RN&quot;,\n                   ROW_NUMBER ()\n                       OVER (PARTITION BY &quot;T&quot;.&quot;CALENDAR_YEAR&quot;\n                             ORDER BY SUM (&quot;S&quot;.&quot;AMOUNT_SOLD&quot;) DESC) &lt;=  5  &quot;sys_qualify$_h$_predex$_0&quot;,\n                   &quot;T&quot;.&quot;CALENDAR_YEAR&quot;\n                       &quot;sys_qualify$_h$_obyex$_00000&quot;,\n                   ROW_NUMBER ()\n                       OVER (PARTITION BY &quot;T&quot;.&quot;CALENDAR_YEAR&quot;\n                             ORDER BY SUM (&quot;S&quot;.&quot;AMOUNT_SOLD&quot;) DESC)\n                       &quot;sys_qualify$_h$_obyex$_00001&quot;\n              FROM &quot;SH&quot;.&quot;SALES&quot; &quot;S&quot;, &quot;SH&quot;.&quot;TIMES&quot; &quot;T&quot;, &quot;SH&quot;.&quot;PRODUCTS&quot; &quot;P&quot;\n             WHERE     &quot;P&quot;.&quot;PROD_ID&quot; = &quot;S&quot;.&quot;PROD_ID&quot;\n                   AND &quot;T&quot;.&quot;TIME_ID&quot; = &quot;S&quot;.&quot;TIME_ID&quot;\n          GROUP BY &quot;T&quot;.&quot;CALENDAR_YEAR&quot;, &quot;P&quot;.&quot;PROD_NAME&quot;) &quot;from$_subquery$_006&quot;\n   WHERE &quot;from$_subquery$_006&quot;.&quot;sys_qualify$_h$_predex$_0&quot; = TRUE\nORDER BY &quot;from$_subquery$_006&quot;.&quot;sys_qualify$_h$_obyex$_00000&quot;,\n         &quot;from$_subquery$_006&quot;.&quot;sys_qualify$_h$_obyex$_00001&quot;\n;\n<\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\"><strong><em>Listing 6: Final Query after Transformations from the CBO trace<\/em><\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Compared to the output of <code>EXPAND_SQL_TEXT<\/code>, the CBO&#8217;s final query is much easier to read. The transformation of the <code>QUALIFY<\/code> condition, however, is essentially the same.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Same Logic, Different Execution Plan<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">But what happens if we execute these statements directly?<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Interestingly, both produce almost identical execution plans:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: sql; title: ; notranslate\" title=\"\">\n---------------------------------------------------------------------------\n| Id  | Operation                         | Name     | Rows  | Cost (%CPU)|\n---------------------------------------------------------------------------\n|   0 | SELECT STATEMENT                  |          |   237 |  3884   (2)|\n|   1 |  SORT ORDER BY                    |          |   237 |  3884   (2)|\n|*  2 |   VIEW                            |          |   237 |  3883   (2)|\n|   3 |    WINDOW SORT                    |          |   237 |  3883   (2)|\n|   4 |     HASH GROUP BY                 |          |   237 |  3883   (2)|\n|*  5 |      HASH JOIN                    |          |   918K|  3847   (1)|\n|   6 |       PART JOIN FILTER CREATE     | :BF0000  |  1826 |    17   (0)|\n|   7 |        TABLE ACCESS FULL          | TIMES    |  1826 |    17   (0)|\n|*  8 |       HASH JOIN                   |          |   918K|  3828   (1)|\n|   9 |        TABLE ACCESS FULL          | PRODUCTS |    72 |     3   (0)|\n|  10 |        PARTITION RANGE JOIN-FILTER|          |   918K|  3823   (1)|\n|  11 |         TABLE ACCESS FULL         | SALES    |   918K|  3823   (1)|\n---------------------------------------------------------------------------\n \nPredicate Information (identified by operation id):\n---------------------------------------------------\n \n   2 - filter(\"from$_subquery$_006\".\"sys_qualify$_h$_predex$_0\"=TRUE)\n   5 - access(\"T\".\"TIME_ID\"=\"S\".\"TIME_ID\")\n   8 - access(\"P\".\"PROD_ID\"=\"S\".\"PROD_ID\")\n<\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\"><strong><em>Listing 7: Execution plan of the generated SQL<\/em><\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Notice something missing? Instead of <code>WINDOW SORT PUSHED RANK<\/code>, which we saw in the original execution plans in Listings 2 and 3, we now have a plain <code>WINDOW SORT<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">So, although the generated SQL produces the same results, it no longer benefits from the pushed-rank optimization. In my tests, this also resulted in measurably worse performance.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">But why?<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Let&#8217;s make a small change to the transformed SQL. Instead of filtering on the generated Boolean expression:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: sql; title: ; notranslate\" title=\"\">\nWHERE \"from$_subquery$_006\".\"sys_qualify$_h$_predex$_0\" = TRUE\n<\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\">we filter directly on the row number:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: sql; title: ; notranslate\" title=\"\">\nWHERE rn &lt;= 5\n<\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\">Everything else remains unchanged.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: sql; title: ; notranslate\" title=\"\">\n  SELECT &quot;from$_subquery$_006&quot;.&quot;CALENDAR_YEAR&quot;     &quot;CALENDAR_YEAR&quot;,\n         &quot;from$_subquery$_006&quot;.&quot;PROD_NAME&quot;         &quot;PROD_NAME&quot;,\n         &quot;from$_subquery$_006&quot;.&quot;REVENUE&quot;           &quot;REVENUE&quot;,\n         &quot;from$_subquery$_006&quot;.&quot;RN&quot;                &quot;RN&quot;\n    FROM (  SELECT &quot;T&quot;.&quot;CALENDAR_YEAR&quot;\n                       &quot;CALENDAR_YEAR&quot;,\n                   &quot;P&quot;.&quot;PROD_NAME&quot;\n                       &quot;PROD_NAME&quot;,\n                   SUM (&quot;S&quot;.&quot;AMOUNT_SOLD&quot;)\n                       &quot;REVENUE&quot;,\n                   ROW_NUMBER ()\n                       OVER (PARTITION BY &quot;T&quot;.&quot;CALENDAR_YEAR&quot;\n                             ORDER BY SUM (&quot;S&quot;.&quot;AMOUNT_SOLD&quot;) DESC)\n                       &quot;RN&quot;,\n                   ROW_NUMBER ()\n                       OVER (PARTITION BY &quot;T&quot;.&quot;CALENDAR_YEAR&quot;\n                             ORDER BY SUM (&quot;S&quot;.&quot;AMOUNT_SOLD&quot;) DESC) &lt;=  5  &quot;sys_qualify$_h$_predex$_0&quot;,\n                   &quot;T&quot;.&quot;CALENDAR_YEAR&quot;\n                       &quot;sys_qualify$_h$_obyex$_00000&quot;,\n                   ROW_NUMBER ()\n                       OVER (PARTITION BY &quot;T&quot;.&quot;CALENDAR_YEAR&quot;\n                             ORDER BY SUM (&quot;S&quot;.&quot;AMOUNT_SOLD&quot;) DESC)\n                       &quot;sys_qualify$_h$_obyex$_00001&quot;\n              FROM &quot;SH&quot;.&quot;SALES&quot; &quot;S&quot;, &quot;SH&quot;.&quot;TIMES&quot; &quot;T&quot;, &quot;SH&quot;.&quot;PRODUCTS&quot; &quot;P&quot;\n             WHERE     &quot;P&quot;.&quot;PROD_ID&quot; = &quot;S&quot;.&quot;PROD_ID&quot;\n                   AND &quot;T&quot;.&quot;TIME_ID&quot; = &quot;S&quot;.&quot;TIME_ID&quot;\n          GROUP BY &quot;T&quot;.&quot;CALENDAR_YEAR&quot;, &quot;P&quot;.&quot;PROD_NAME&quot;) &quot;from$_subquery$_006&quot;\n   WHERE rn &lt;= 5\nORDER BY &quot;from$_subquery$_006&quot;.&quot;sys_qualify$_h$_obyex$_00000&quot;,\n         &quot;from$_subquery$_006&quot;.&quot;sys_qualify$_h$_obyex$_00001&quot;\n;\n---------------------------------------------------------------------------\n| Id  | Operation                         | Name     | Rows  | Cost (%CPU)|\n---------------------------------------------------------------------------\n|   0 | SELECT STATEMENT                  |          |    25 |  3902   (2)|\n|   1 |  SORT ORDER BY                    |          |    25 |  3902   (2)|\n|*  2 |   VIEW                            |          |    25 |  3901   (2)|\n|*  3 |    WINDOW SORT PUSHED RANK        |          |   237 |  3901   (2)|\n|   4 |     HASH GROUP BY                 |          |   237 |  3901   (2)|\n|*  5 |      HASH JOIN                    |          |   918K|  3847   (1)|\n|   6 |       PART JOIN FILTER CREATE     | :BF0000  |  1826 |    17   (0)|\n|   7 |        TABLE ACCESS FULL          | TIMES    |  1826 |    17   (0)|\n|*  8 |       HASH JOIN                   |          |   918K|  3828   (1)|\n|   9 |        TABLE ACCESS FULL          | PRODUCTS |    72 |     3   (0)|\n|  10 |        PARTITION RANGE JOIN-FILTER|          |   918K|  3823   (1)|\n|  11 |         TABLE ACCESS FULL         | SALES    |   918K|  3823   (1)|\n---------------------------------------------------------------------------\nPredicate Information (identified by operation id):\n---------------------------------------------------\n \n   2 - filter(&quot;RN&quot;&lt;=5)\n   3 - filter(ROW_NUMBER() OVER ( PARTITION BY &quot;T&quot;.&quot;CALENDAR_YEAR&quot; ORDER BY SUM(&quot;S&quot;.&quot;AMOUNT_SOLD&quot;) \n              DESC )&lt;=5)\n   5 - access(&quot;T&quot;.&quot;TIME_ID&quot;=&quot;S&quot;.&quot;TIME_ID&quot;)\n   8 - access(&quot;P&quot;.&quot;PROD_ID&quot;=&quot;S&quot;.&quot;PROD_ID&quot;)\n \n<\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\"><strong><em>Listing 8: Restoring WINDOW SORT PUSHED RANK by filtering directly on RN<\/em><\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">And suddenly, the optimizer produces:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: sql; title: ; notranslate\" title=\"\">\nWINDOW SORT PUSHED RANK\n<\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\">So the difference is not caused by the additional inline view or the repeated <code>ROW_NUMBER()<\/code> expressions. The crucial difference appears to be the way the filtering condition is represented.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In this example, the optimizer recognizes the Top-N pattern when the filter references the row number directly. But when the same condition is expressed through a Boolean flag, the pushed-rank optimization is lost.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This is another example of why we should be careful when interpreting SQL produced by <code>DBMS_UTILITY.EXPAND_SQL_TEXT<\/code> or the CBO trace. Even the <em>Final Query after Transformations<\/em> is not necessarily optimizer-equivalent to the original statement when executed independently.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Apparently, there is more to the optimizer&#8217;s internal representation than what we can see in the generated SQL text.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Conclusion<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">I really like the new <code>QUALIFY<\/code> clause. It makes SQL more concise, avoids unnecessary query blocks, and expresses a very common analytical requirement directly where it belongs.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The experiment also revealed two interesting things. First, expressing a Top-N condition through a Boolean flag can prevent Oracle from applying the <code><strong>WINDOW SORT PUSHED RANK<\/strong><\/code> optimization. This is something to watch out for when manually filtering on Boolean flags derived from analytic functions. But then again, why would you want to do that anyway? You now have <code><strong>QUALIFY<\/strong><\/code>!<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Second, SQL produced by <code>DBMS_UTILITY.EXPAND_SQL_TEXT<\/code> or extracted from the CBO trace should not necessarily be treated as optimizer-equivalent to the original statement. Even the <em>Final Query after Transformations<\/em> may produce a different execution plan when executed independently.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">What looks like a straightforward query rewrite can apparently hide quite a bit of optimizer magic.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Analytic functions have been among my favorite Oracle SQL features ever since their introduction in Oracle 8i. I&#8217;ve used them extensively over the years, and I still remember how much easier they made many SQL problems that previously required rather complicated solutions. Tom Kyte once described analytic functions as &#8220;the coolest thing to happen to [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":2902,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[162,50,3],"tags":[165],"class_list":["post-2847","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-26ai","category-data-warehouse","category-sql","tag-query-optimizer"],"_links":{"self":[{"href":"https:\/\/blog.sqlora.com\/en\/wp-json\/wp\/v2\/posts\/2847","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.sqlora.com\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.sqlora.com\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.sqlora.com\/en\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.sqlora.com\/en\/wp-json\/wp\/v2\/comments?post=2847"}],"version-history":[{"count":71,"href":"https:\/\/blog.sqlora.com\/en\/wp-json\/wp\/v2\/posts\/2847\/revisions"}],"predecessor-version":[{"id":2923,"href":"https:\/\/blog.sqlora.com\/en\/wp-json\/wp\/v2\/posts\/2847\/revisions\/2923"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/blog.sqlora.com\/en\/wp-json\/wp\/v2\/media\/2902"}],"wp:attachment":[{"href":"https:\/\/blog.sqlora.com\/en\/wp-json\/wp\/v2\/media?parent=2847"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.sqlora.com\/en\/wp-json\/wp\/v2\/categories?post=2847"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.sqlora.com\/en\/wp-json\/wp\/v2\/tags?post=2847"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}