{"id":999,"date":"2016-09-13T17:12:48","date_gmt":"2016-09-13T17:12:48","guid":{"rendered":"http:\/\/nenadnoveljic.com\/blog\/?p=999"},"modified":"2020-08-24T13:35:13","modified_gmt":"2020-08-24T13:35:13","slug":"disjunctive-subquery-optimization","status":"publish","type":"post","link":"https:\/\/nenadnoveljic.com\/blog\/disjunctive-subquery-optimization\/","title":{"rendered":"Disjunctive Subquery Optimization"},"content":{"rendered":"<h1>Disjunctive Subqueries<\/h1>\n<p>Disjunctive subqueries are the subqueries which are applied to the <em>or<\/em> operator, like the following one:<\/p>\n<pre><code>select a from t_large l \n  where id in ( select b from t_small s ) <span style=\"color: #ff0000;\">or<\/span> a = 1 <\/code><\/pre>\n<p>Given that the <em>not null<\/em> constraint on the <em>t_large.a<\/em> column is defined, an alternative way to write this query is:<\/p>\n<pre><code>select a from t_large l where a = 1 \n<span style=\"color: #ff0000;\">union all<\/span>\nselect a from t_large l \n  where id in ( select b from t_small s ) <span style=\"color: #ff0000;\">and a != 1 <\/span><\/code><\/pre>\n<p>Ideally, the optimizer should apply cost-based query transformations to rewrite the original query with the <em>or<\/em> operator as the <em>union all<\/em> variant when appropriate. In this blog post I&#8217;ll investigate, how successfully these algorithms work in SQL Server and Oracle.<\/p>\n<h1>SQL Server<\/h1>\n<h2>Setting the Scene<\/h2>\n<p>Let me start with SQL Server. I&#8217;ll create two test tables, one large with a milion records and the other small, containing just two records:<\/p>\n<pre><code>\nSELECT TOP (1000000)\nid = CONVERT(INT, ROW_NUMBER() OVER (ORDER BY s1.[object_id])),\na = CONVERT(INT, ROW_NUMBER() OVER (ORDER BY s1.[object_id]))\nINTO t_large\nFROM sys.all_objects AS s1 CROSS JOIN sys.all_objects AS s2\nOPTION (MAXDOP 1);\n\nalter table t_large alter column a integer not null\n\ncreate table t_small (b integer,c integer) ;\ninsert into t_small values (1,1) ;\ninsert into t_small values (2,2) ;\n<\/code><\/pre>\n<p>The <a href=\"http:\/\/sqlperformance.com\/2013\/01\/t-sql-queries\/generate-a-set-1\" target=\"_blank\" rel=\"noopener noreferrer\">query to populate a large table with numbers<\/a> is used by courtesy of Aaron Bertrand.<\/p>\n<h2>Query Transformation<\/h2>\n<p>First, I&#8217;ll examine the execution plan and the statistics of the original query with the <em>or<\/em> operator:<\/p>\n<pre><code>select a from t_large l where id in ( select b from t_small s ) <span style=\"color: #ff0000;\">or<\/span> a = 1 \nStmtText\tEstimateRows\t<span style=\"color: #ff0000;\">TotalSubtreeCost<\/span>\tEstimateExecutions\nselect a from t_large l where id in ( select b from t_small s ) or a = 1\t1000000\t<span style=\"color: #ff0000;\">9.70106<\/span>\tNULL\n  |--<span style=\"color: #ff0000;\">Nested Loops<\/span>(Left Semi Join, OUTER REFERENCES:([l].[id], [l].[a]))\t1000000\t9.70106\t1\n       |--Table Scan(OBJECT:([master].[dbo].[t_large] AS [l]))\t1000000\t3.94106\t1\n       |--Concatenation\t1\t1.58\t1000000\n            |--Filter(WHERE:(STARTUP EXPR([master].[dbo].[t_large].[a] as [l].[a]=(1))))\t1\t1.48\t1000000\n            |    |--Constant Scan\t1\t1\t1000000\n            |--<span style=\"color: #ff0000;\">Table Scan<\/span>(OBJECT:([master].[dbo].[t_small] AS [s]), WHERE:([master].[dbo].[t_large].[id] as [l].[id]=[master].[dbo].[t_small].[b] as [s].[b]))\t1\t9.70106\t1000000\n<\/code><\/pre>\n<p>At the first glance, there are two things in the execution plan above that look suspicious. One is the <em>Table Scan<\/em> in the <em>Nested Loop Join (NLJ)<\/em>. Usually, the Query Optimizer uses <a href=\"http:\/\/nenadnoveljic.com\/blog\/nested-loop-join-heuristics\/\" target=\"_blank\" rel=\"noopener noreferrer\">Nested Loop Join Heuristics<\/a> to pre-empt <em>Table Scans<\/em> in NLJs. The other is, that the <em>Concatenation<\/em> operator, which implements <em>union all<\/em>, is not on the top of the execution plan. Instead, it is placed inside the NLJ. As a consequence, there will be repeated <em>Table Scans<\/em> of the inner table <em>t_small<\/em>.<\/p>\n<p>So, let&#8217;s examine the execution plan of the equivalent query with the <em>union all<\/em> operator:<\/p>\n<pre><code>select a from t_large l where a = 1 \n<span style=\"color: #ff0000;\">union all<\/span>\nselect a from t_large l where id in ( select b from t_small s ) and a != 1\nStmtText\t<span style=\"color: #ff0000;\">TotalSubtreeCost<\/span>\tEstimateExecutions\nselect a from t_large l where id in ( select b from t_small s ) and a != 1\t<span style=\"color: #ff0000;\">15.3989<\/span>\tNULL\n  |--<span style=\"color: #ff0000;\">Concatenation<\/span>\t15.3989\t1\n       |--Table Scan(OBJECT:([master].[dbo].[t_large] AS [l]), WHERE:([master].[dbo].[t_large].[a] as [l].[a]=(1)))\t3.94106\t1\n       |--<span style=\"color: #ff0000;\">Hash Match<\/span>(Right Semi Join, HASH:([s].[b])=([l].[id]), RESIDUAL:([master].[dbo].[t_large].[id] as [l].[id]=[master].[dbo].[t_small].[b] as [s].[b]))\t<span style=\"color: #ff0000;\">10.97784<\/span>\t1\n            |--Table Scan(OBJECT:([master].[dbo].[t_small] AS [s]))\t0.0032842\t1\n            |--Table Scan(OBJECT:([master].[dbo].[t_large] AS [l]), WHERE:([master].[dbo].[t_large].[a] as [l].[a]&lt;&gt;(1)))\t3.94106\t1\n<\/code><\/pre>\n<p>As expected, the union query uses the <em>Hash Join (HJ)<\/em>, and the <em>Concatenation<\/em> operator is on the top of the exection plan. However, its estimated cost is significantly higher than the cost of the NLJ query, <span style=\"color: #ff0000;\">15.3989<\/span> and <span style=\"color: #ff0000;\">9.70106<\/span>, respectively.<br \/>\nFor this reason, it makes sense to cross-check the cost calculation with the IO and timing statistics for both queries:<\/p>\n<pre><code><span style=\"color: #ff0000;\">NLS (or query) <\/span>:\n\nTable 't_small'. Scan count 1, logical reads <span style=\"color: #ff0000;\">999999<\/span>, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.\nTable 't_large'. Scan count 1, logical reads 3832, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.\n\n(1 row(s) affected)\n\n SQL Server Execution Times:\n   CPU time = 3766 ms,  elapsed time = 5143 ms.\n   \n<span style=\"color: #ff0000;\">HJ (union all query)<\/span>:\nTable 'Workfile'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.\nTable 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.\nTable 't_large'. Scan count 2, logical reads <span style=\"color: #ff0000;\">7664<\/span>, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.\nTable 't_small'. Scan count 1, logical reads <span style=\"color: #ff0000;\">1<\/span>, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.\n\n(1 row(s) affected)\n\n SQL Server Execution Times:\n   CPU time = 328 ms,  elapsed time = 493 ms.\nSQL Server parse and compile time: \n   CPU time = 0 ms, elapsed time = 0 ms.\n<\/code><\/pre>\n<p>Curiously, despite its more expensive plan, the union all query with the HJ is by orders of magnitude more efficient than the original query with the <em>or<\/em> operator which does NLJ. What I mean by that is, considering the high number of logical reads (<span style=\"color: #ff0000;\">999999<\/span>) on <em>t_small<\/em>, its estimated cost (<span style=\"color: #ff0000;\">9.70106<\/span>) seems quite small when compared to the cost of the HJ (<span style=\"color: #ff0000;\">10.97784<\/span>), which performed in total only <span style=\"color: #ff0000;\">7665<\/span> logical reads. To get an idea, by how much the cost of the NLJ was underestimated, I&#8217;ll isolate the join in a separate query forcing the join order and NLJ with hints:<\/p>\n<pre><code>select a from t_large l \n  where id in ( select b from t_small s ) \n  option (loop join , force order)\n\nStmtText\tTotalSubtreeCost\nselect a from t_large l \t\n  where id in ( select b from t_small s ) <span style=\"color: #ff0000;\">option (loop join , force order)<\/span>\t93.60426\n  |--Nested Loops(Left Semi Join, WHERE:([master].[dbo].[t_large].[id] as [l].[id]=[master].[dbo].[t_small].[b] as [s].[b]))\t93.60426\n       |--Table Scan(OBJECT:([master].[dbo].[t_large] AS [l]))\t3.94106\n       |--Table Scan(OBJECT:([master].[dbo].[t_small] AS [s]))\t<span style=\"color: #ff0000;\">80.7032 \n<\/span><\/code><\/pre>\n<p>As opposed to the original query with the <em>or<\/em> operator, the cost of the scan of the inner table in the NLJ in the query above &#8211; <span style=\"color: #ff0000;\">80.7032<\/span> &#8211; reflects much better the high number of logical reads performed. In conclusion, if this cost had been caculated correctly in the original query, it is not unreasonable to assume that the query would have been transformed correctly.<\/p>\n<h2>Indexing<\/h2>\n<p>So, how can we overcome the wrong cost calculation?<\/p>\n<p>As the wrong cost calculation applies to the <em>Table Scan<\/em>, let&#8217;s try to eliminate it by creating the following index:<\/p>\n<pre><code>CREATE NONCLUSTERED INDEX [idx_t_large_id]\nON [dbo].[t_large] ([id])\nINCLUDE ([a])<\/code><\/pre>\n<p>Indeed, the transformation is done as expected for the original query with the or clause:<\/p>\n<pre><code>\n  |--<span style=\"color: #ff0000;\">Concatenation<\/span>\n       |--Index Scan(OBJECT:([master].[dbo].[t_large].[idx_t_large_id] AS [l]),  WHERE:([master].[dbo].[t_large].[a] as [l].[a]=(1)))\n       |--Nested Loops(Inner Join, OUTER REFERENCES:([s].[b]))\n            |--Sort(DISTINCT ORDER BY:([s].[b] ASC))\n            |    |--Table Scan(OBJECT:([master].[dbo].[t_small] AS [s]))\n            |--Index Seek(OBJECT:([master].[dbo].[t_large].[idx_t_large_id] AS [l]), SEEK:([l].[id]=[master].[dbo].[t_small].[b] as [s].[b]),  WHERE:([master].[dbo].[t_large].[a] as [l].[a]&lt;&gt;(1)) ORDERED FORWARD)\n<\/code><\/pre>\n<p>Also, the number of logical reads is identical for both the or and union all query:<\/p>\n<pre><code>Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.\nTable 't_large'. Scan count 3, logical reads <span style=\"color: #ff0000;\">2735<\/span>, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.\nTable 't_small'. Scan count 1, logical reads 1, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.<\/code><\/pre>\n<p>Therefore, we can conclude, that the transformation is now successful.<\/p>\n<h1>Oracle<\/h1>\n<p>Now, let&#8217;s see how all of this works (or doesn&#8217;t work) in an Oracle database:<\/p>\n<pre><code>create user u identified by Temp_12345 ;\ngrant dba to u ;\n\nconnect u\/Temp_12345\n\ncreate table t_large ( id number , a number not null ) ;\ncreate table t_small ( b number , c number ) ;\n\ninsert into t_large\nSELECT level,level\nFROM   dual\nCONNECT BY level &lt;= 1000000;\n\ninsert into t_small values (1,1) ;\ninsert into t_small values (2,2) ;\n\ncommit ;\n\nCREATE INDEX idx_t_large_a ON t_large(id) ;\n\nexec dbms_stats.gather_schema_stats('U');<\/code><\/pre>\n<p>First, I&#8217;ll inspect the execution plan and the statistics of the original query containing <em>or<\/em>:<\/p>\n<pre><code>select \/*+ gather_plan_statistics *\/ a from t_large l \n  where id in ( select b from t_small s ) <span style=\"color: #ff0000;\">or<\/span> a = 1  ;\n\n\n------------------------------------------------------------------------------------------------------------------------\n| Id  | Operation          | Name    | Starts | E-Rows |E-Bytes| Cost (%CPU)| E-Time   | A-Rows |   A-Time   | <span style=\"color: #ff0000;\">Buffers<\/span> |\n------------------------------------------------------------------------------------------------------------------------\n|   0 | SELECT STATEMENT   |         |      1 |        |       |   280 (100)|          |      2 |00:00:00.01 |    <span style=\"color: #ff0000;\">5000K<\/span>|\n|*  1 |  <span style=\"color: #ff0000;\">FILTER<\/span>            |         |      1 |        |       |            |          |      2 |00:00:00.01 |    5000K|\n|   2 |   TABLE ACCESS FULL| T_LARGE |      1 |   1000K|  9765K|   280   (2)| 00:00:01 |   1000K|00:00:00.20 |     538 |\n|*  3 |   TABLE ACCESS FULL| T_SMALL |    999K|      1 |     3 |     <span style=\"color: #ff0000;\">4<\/span>   (0)| 00:00:01 |      1 |00:00:09.17 |    <span style=\"color: #ff0000;\">4999K<\/span>|\n------------------------------------------------------------------------------------------------------------------------\n\nPredicate Information (identified by operation id):\n---------------------------------------------------\n   1 - filter((\"A\"=1 OR  IS NOT NULL))\n   3 - filter(\"B\"=:B1)<\/code><\/pre>\n<p>The presence of the filter operation indicates that the subquery was not unnested, which in turn means that it was repeatidly executed for each record in <em>t_large<\/em>. Moreover, the full table scan of <em>t_small<\/em> was not costed properly. What I mean by this is, there were <span style=\"color: #ff0000;\">4999K<\/span> logical reads on the table, but the cost was estimated to only <span style=\"color: #ff0000;\">4<\/span>.<\/p>\n<p>At this point, we might ask ourselves, whether the absence of the query unnesting is the consequence of the wrong cost calculation, or is the Oracle optimizer inherently incapable of transforming disjunctive queries.<\/p>\n<p>We can easily answer this question by verifying whether the execution plan changes after embedding the unnest hint in the subquery:<\/p>\n<pre><code>select \/*+ gather_plan_statistics *\/ a from t_large l \n  where id in ( select \/*+ <span style=\"color: #ff0000;\">unnest<\/span> *\/ b from t_small s ) <span style=\"color: #ff0000;\">or<\/span> a = 1  ;\n\n------------------------------------------------------------------------------\n| Id  | Operation          | Name    | Rows  | Bytes | Cost (%CPU)| Time     |\n------------------------------------------------------------------------------\n|   0 | SELECT STATEMENT   |         |       |       |   280 (100)|          |\n|*  1 |  <span style=\"color: #ff0000;\">FILTER<\/span>            |         |       |       |            |          |\n|   2 |   TABLE ACCESS FULL| T_LARGE |  1000K|  9765K|   280   (2)| 00:00:01 |\n|*  3 |   TABLE ACCESS FULL| T_SMALL |     1 |     3 |     4   (0)| 00:00:01 |\n------------------------------------------------------------------------------\n\nPredicate Information (identified by operation id):\n---------------------------------------------------\n\n   1 - filter((\"A\"=1 OR  IS NOT NULL))\n   3 - filter(\"B\"=:B1)\n<\/code><\/pre>\n<p>Unfortunately, the execution plan hasn&#8217;t improved, which leads us to the conclusion that the Oracle optimizer just doesn&#8217;t do unnesting of disjunctive subqueries. Actually, Jonathan Lewis and Mohamed Houry have already elaborated on this topic in their blog posts <a href=\"https:\/\/jonathanlewis.wordpress.com\/2007\/02\/26\/subquery-with-or\/\" target=\"_blank\" rel=\"noopener noreferrer\">Subquery with OR<\/a> and <a href=\"http:\/\/www.toadworld.com\/platforms\/oracle\/w\/wiki\/11081.tuning-a-disjunctive-subquery\" target=\"_blank\" rel=\"noopener noreferrer\">Tuning a disjunctive subquery<\/a>, repectively.<\/p>\n<p>Finally, let&#8217;s quantify the benefit of manually rewriting the query:<\/p>\n<pre><code>select \/*+ gather_plan_statistics *\/ a from t_large l where a = 1 \n<span style=\"color: #ff0000;\">union all<\/span>\nselect a from t_large l where id in ( select b from t_small s ) and a != 1 ;\n\n--------------------------------------------------------------------------------------------------------------------------------------------------------------------\n| Id  | Operation                     | Name          | Starts | E-Rows |E-Bytes| Cost (%CPU)| E-Time   | A-Rows |   A-Time   | <span style=\"color: #ff0000;\">Buffers<\/span> |  OMem |  1Mem | Used-Mem |\n--------------------------------------------------------------------------------------------------------------------------------------------------------------------\n|   0 | SELECT STATEMENT              |               |      1 |        |       |   286 (100)|          |      2 |00:00:00.01 |     <span style=\"color: #ff0000;\">548<\/span> |       |       |          |\n|   1 |  <span style=\"color: #ff0000;\">UNION-ALL<\/span>                    |               |      1 |        |       |            |          |      2 |00:00:00.01 |     548 |       |       |          |\n|*  2 |   TABLE ACCESS FULL           | T_LARGE       |      1 |      1 |     5 |   279   (2)| 00:00:01 |      1 |00:00:00.01 |     538 |       |       |          |\n|   3 |   NESTED LOOPS                |               |      1 |      2 |    26 |     7  (15)| 00:00:01 |      1 |00:00:00.01 |      10 |       |       |          |\n|   4 |    NESTED LOOPS               |               |      1 |      2 |    26 |     7  (15)| 00:00:01 |      2 |00:00:00.01 |       9 |       |       |          |\n|   5 |     SORT UNIQUE               |               |      1 |      2 |     6 |     4   (0)| 00:00:01 |      2 |00:00:00.01 |       5 |  2048 |  2048 | 2048  (0)|\n|   6 |      TABLE ACCESS FULL        | T_SMALL       |      1 |      2 |     6 |     4   (0)| 00:00:01 |      2 |00:00:00.01 |       5 |       |       |          |\n|*  7 |     INDEX RANGE SCAN          | IDX_T_LARGE_A |      2 |      1 |       |     1   (0)| 00:00:01 |      2 |00:00:00.01 |       4 |       |       |          |\n|*  8 |    TABLE ACCESS BY INDEX ROWID| T_LARGE       |      2 |      1 |    10 |     2   (0)| 00:00:01 |      1 |00:00:00.01 |       1 |       |       |          |\n--------------------------------------------------------------------------------------------------------------------------------------------------------------------\n<\/code><\/pre>\n<p>Looking into the buffers statistics, we can see that the number of logical reads slumped from <span style=\"color: #ff0000;\">5 million<\/span> to just <span style=\"color: #ff0000;\">548<\/span>. Sadly, Oracle misses a huge optimizing potential by not implementing the unnesting of disjunctive subqueries.<\/p>\n<h1>Conclusion<\/h1>\n<p>In summary, the unnesting of disjunctive subqueries and merging them into the main query can boost query performance.<\/p>\n<p>Unfortunately, the algorithm hasn&#8217;t been implemented in the Oracle optimizer yet. Therefore, developers have to put effort in assisting the optimizer in deriving a good execution plan.<\/p>\n<p>In contrast, the SQL Server optimizer is able to do this transformation all by itself. However, beware of some edge cases where, due to a wrong cost calculation, the optimization process doesn&#8217;t yield the expected result, which in turn may result in suboptimal plans. The good news is, that such plans can be easily recognized by <em>Table Scans<\/em> in <em>Nested Loop Joins<\/em> and therefore avoided by a good indexing strategy.<\/p>\n<h1>Updates<\/h1>\n<h2>March 9, 2017 &#8211; 12.2<\/h2>\n<p>The behavior hasn&#8217;t changed in 12.2: the disjunctive subquery is still not being unnested.<\/p>\n<h2>August 24, 2020 &#8211; 19c<\/h2>\n<p>Jonathan Lewis shows us <a href=\"https:\/\/jonathanlewis.wordpress.com\/2020\/08\/19\/subquery-with-or-3\/\">here<\/a> that ORE works with disjunctive queries in 19c. The optimizer team implemented the unnesting of disjunctive subqueries. Keep in mind that every column of the table in the main query block which is referenced in OR must have an index. This means that you have to create an index on t_large.a in the test case above.<\/p>\n<p>The feature isn&#8217;t implemented in 18c (tested on 18.5.0.0.190115).<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Disjunctive subqueries are the subqueries which are applied to the or operator. Alternatively, these queries can be rewritten to use the union all operator which may be a more efficient way to retrieve the records from the database. This blog post compares the efficiency of SQL Server and Oracle optimizer when dealing with the transformations of disjunctive subqueries.<br \/>\n <a href=\"https:\/\/nenadnoveljic.com\/blog\/disjunctive-subquery-optimization\/\" class=\"more-link\">Continue Reading <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"inline_featured_image":false,"footnotes":""},"categories":[8,21,11,33,5,17],"tags":[],"class_list":["post-999","post","type-post","status-publish","format-standard","hentry","category-12c","category-comparison-oracle-sql-server","category-cost-based-optimizer","category-or-expansion","category-oracle","category-sql-server"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.4 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Disjunctive Subquery Optimization - All-round Database Topics<\/title>\n<meta name=\"description\" content=\"Disjunctive subqueries are the subqueries which are applied to the or operator. Alternatively, these queries can be rewritten to use the union all operator which may be a more efficient way to retrieve the records from the database. This blog post compares the efficiency of SQL Server and Oracle optimizer when dealing with the transformations of disjunctive subqueries.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/nenadnoveljic.com\/blog\/disjunctive-subquery-optimization\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Disjunctive Subquery Optimization - All-round Database Topics\" \/>\n<meta property=\"og:description\" content=\"Disjunctive subqueries are the subqueries which are applied to the or operator. Alternatively, these queries can be rewritten to use the union all operator which may be a more efficient way to retrieve the records from the database. This blog post compares the efficiency of SQL Server and Oracle optimizer when dealing with the transformations of disjunctive subqueries.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/nenadnoveljic.com\/blog\/disjunctive-subquery-optimization\/\" \/>\n<meta property=\"og:site_name\" content=\"All-round Database Topics\" \/>\n<meta property=\"article:published_time\" content=\"2016-09-13T17:12:48+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2020-08-24T13:35:13+00:00\" \/>\n<meta name=\"author\" content=\"Nenad Noveljic\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:creator\" content=\"@NenadNoveljic\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Nenad Noveljic\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"10 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/nenadnoveljic.com\\\/blog\\\/disjunctive-subquery-optimization\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/nenadnoveljic.com\\\/blog\\\/disjunctive-subquery-optimization\\\/\"},\"author\":{\"name\":\"Nenad Noveljic\",\"@id\":\"https:\\\/\\\/nenadnoveljic.com\\\/blog\\\/#\\\/schema\\\/person\\\/51458d9dd86dbbdd19f5add451d44efa\"},\"headline\":\"Disjunctive Subquery Optimization\",\"datePublished\":\"2016-09-13T17:12:48+00:00\",\"dateModified\":\"2020-08-24T13:35:13+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/nenadnoveljic.com\\\/blog\\\/disjunctive-subquery-optimization\\\/\"},\"wordCount\":999,\"commentCount\":0,\"articleSection\":[\"12c\",\"Comparison Oracle-SQL Server\",\"cost based optimizer\",\"OR-expansion\",\"Oracle\",\"SQL Server\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\\\/\\\/nenadnoveljic.com\\\/blog\\\/disjunctive-subquery-optimization\\\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/nenadnoveljic.com\\\/blog\\\/disjunctive-subquery-optimization\\\/\",\"url\":\"https:\\\/\\\/nenadnoveljic.com\\\/blog\\\/disjunctive-subquery-optimization\\\/\",\"name\":\"Disjunctive Subquery Optimization - All-round Database Topics\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/nenadnoveljic.com\\\/blog\\\/#website\"},\"datePublished\":\"2016-09-13T17:12:48+00:00\",\"dateModified\":\"2020-08-24T13:35:13+00:00\",\"author\":{\"@id\":\"https:\\\/\\\/nenadnoveljic.com\\\/blog\\\/#\\\/schema\\\/person\\\/51458d9dd86dbbdd19f5add451d44efa\"},\"description\":\"Disjunctive subqueries are the subqueries which are applied to the or operator. Alternatively, these queries can be rewritten to use the union all operator which may be a more efficient way to retrieve the records from the database. This blog post compares the efficiency of SQL Server and Oracle optimizer when dealing with the transformations of disjunctive subqueries.\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/nenadnoveljic.com\\\/blog\\\/disjunctive-subquery-optimization\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/nenadnoveljic.com\\\/blog\\\/disjunctive-subquery-optimization\\\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/nenadnoveljic.com\\\/blog\\\/disjunctive-subquery-optimization\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/nenadnoveljic.com\\\/blog\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Disjunctive Subquery Optimization\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/nenadnoveljic.com\\\/blog\\\/#website\",\"url\":\"https:\\\/\\\/nenadnoveljic.com\\\/blog\\\/\",\"name\":\"All-round Database Topics\",\"description\":\"Nenad Noveljic\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/nenadnoveljic.com\\\/blog\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/nenadnoveljic.com\\\/blog\\\/#\\\/schema\\\/person\\\/51458d9dd86dbbdd19f5add451d44efa\",\"name\":\"Nenad Noveljic\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/a97b796613ea48ec8a7b79c8ffe1c685dcffc920c68121f6238d5caab5070670?s=96&d=mm&r=g\",\"url\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/a97b796613ea48ec8a7b79c8ffe1c685dcffc920c68121f6238d5caab5070670?s=96&d=mm&r=g\",\"contentUrl\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/a97b796613ea48ec8a7b79c8ffe1c685dcffc920c68121f6238d5caab5070670?s=96&d=mm&r=g\",\"caption\":\"Nenad Noveljic\"},\"sameAs\":[\"nenad-noveljic-9b746a6\",\"https:\\\/\\\/x.com\\\/NenadNoveljic\"],\"url\":\"https:\\\/\\\/nenadnoveljic.com\\\/blog\\\/author\\\/nenad\\\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Disjunctive Subquery Optimization - All-round Database Topics","description":"Disjunctive subqueries are the subqueries which are applied to the or operator. Alternatively, these queries can be rewritten to use the union all operator which may be a more efficient way to retrieve the records from the database. This blog post compares the efficiency of SQL Server and Oracle optimizer when dealing with the transformations of disjunctive subqueries.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/nenadnoveljic.com\/blog\/disjunctive-subquery-optimization\/","og_locale":"en_US","og_type":"article","og_title":"Disjunctive Subquery Optimization - All-round Database Topics","og_description":"Disjunctive subqueries are the subqueries which are applied to the or operator. Alternatively, these queries can be rewritten to use the union all operator which may be a more efficient way to retrieve the records from the database. This blog post compares the efficiency of SQL Server and Oracle optimizer when dealing with the transformations of disjunctive subqueries.","og_url":"https:\/\/nenadnoveljic.com\/blog\/disjunctive-subquery-optimization\/","og_site_name":"All-round Database Topics","article_published_time":"2016-09-13T17:12:48+00:00","article_modified_time":"2020-08-24T13:35:13+00:00","author":"Nenad Noveljic","twitter_card":"summary_large_image","twitter_creator":"@NenadNoveljic","twitter_misc":{"Written by":"Nenad Noveljic","Est. reading time":"10 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/nenadnoveljic.com\/blog\/disjunctive-subquery-optimization\/#article","isPartOf":{"@id":"https:\/\/nenadnoveljic.com\/blog\/disjunctive-subquery-optimization\/"},"author":{"name":"Nenad Noveljic","@id":"https:\/\/nenadnoveljic.com\/blog\/#\/schema\/person\/51458d9dd86dbbdd19f5add451d44efa"},"headline":"Disjunctive Subquery Optimization","datePublished":"2016-09-13T17:12:48+00:00","dateModified":"2020-08-24T13:35:13+00:00","mainEntityOfPage":{"@id":"https:\/\/nenadnoveljic.com\/blog\/disjunctive-subquery-optimization\/"},"wordCount":999,"commentCount":0,"articleSection":["12c","Comparison Oracle-SQL Server","cost based optimizer","OR-expansion","Oracle","SQL Server"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/nenadnoveljic.com\/blog\/disjunctive-subquery-optimization\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/nenadnoveljic.com\/blog\/disjunctive-subquery-optimization\/","url":"https:\/\/nenadnoveljic.com\/blog\/disjunctive-subquery-optimization\/","name":"Disjunctive Subquery Optimization - All-round Database Topics","isPartOf":{"@id":"https:\/\/nenadnoveljic.com\/blog\/#website"},"datePublished":"2016-09-13T17:12:48+00:00","dateModified":"2020-08-24T13:35:13+00:00","author":{"@id":"https:\/\/nenadnoveljic.com\/blog\/#\/schema\/person\/51458d9dd86dbbdd19f5add451d44efa"},"description":"Disjunctive subqueries are the subqueries which are applied to the or operator. Alternatively, these queries can be rewritten to use the union all operator which may be a more efficient way to retrieve the records from the database. This blog post compares the efficiency of SQL Server and Oracle optimizer when dealing with the transformations of disjunctive subqueries.","breadcrumb":{"@id":"https:\/\/nenadnoveljic.com\/blog\/disjunctive-subquery-optimization\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/nenadnoveljic.com\/blog\/disjunctive-subquery-optimization\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/nenadnoveljic.com\/blog\/disjunctive-subquery-optimization\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/nenadnoveljic.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Disjunctive Subquery Optimization"}]},{"@type":"WebSite","@id":"https:\/\/nenadnoveljic.com\/blog\/#website","url":"https:\/\/nenadnoveljic.com\/blog\/","name":"All-round Database Topics","description":"Nenad Noveljic","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/nenadnoveljic.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Person","@id":"https:\/\/nenadnoveljic.com\/blog\/#\/schema\/person\/51458d9dd86dbbdd19f5add451d44efa","name":"Nenad Noveljic","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/secure.gravatar.com\/avatar\/a97b796613ea48ec8a7b79c8ffe1c685dcffc920c68121f6238d5caab5070670?s=96&d=mm&r=g","url":"https:\/\/secure.gravatar.com\/avatar\/a97b796613ea48ec8a7b79c8ffe1c685dcffc920c68121f6238d5caab5070670?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/a97b796613ea48ec8a7b79c8ffe1c685dcffc920c68121f6238d5caab5070670?s=96&d=mm&r=g","caption":"Nenad Noveljic"},"sameAs":["nenad-noveljic-9b746a6","https:\/\/x.com\/NenadNoveljic"],"url":"https:\/\/nenadnoveljic.com\/blog\/author\/nenad\/"}]}},"_links":{"self":[{"href":"https:\/\/nenadnoveljic.com\/blog\/wp-json\/wp\/v2\/posts\/999","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/nenadnoveljic.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/nenadnoveljic.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/nenadnoveljic.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/nenadnoveljic.com\/blog\/wp-json\/wp\/v2\/comments?post=999"}],"version-history":[{"count":1,"href":"https:\/\/nenadnoveljic.com\/blog\/wp-json\/wp\/v2\/posts\/999\/revisions"}],"predecessor-version":[{"id":3470,"href":"https:\/\/nenadnoveljic.com\/blog\/wp-json\/wp\/v2\/posts\/999\/revisions\/3470"}],"wp:attachment":[{"href":"https:\/\/nenadnoveljic.com\/blog\/wp-json\/wp\/v2\/media?parent=999"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/nenadnoveljic.com\/blog\/wp-json\/wp\/v2\/categories?post=999"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/nenadnoveljic.com\/blog\/wp-json\/wp\/v2\/tags?post=999"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}