{"id":73303,"date":"2012-07-05T11:52:28","date_gmt":"2012-07-05T11:52:28","guid":{"rendered":"https:\/\/www.red-gate.com\/simple-talk\/uncategorized\/dynamic-sampling-iii-real-life-data-part-ii\/"},"modified":"2021-07-14T13:07:55","modified_gmt":"2021-07-14T13:07:55","slug":"dynamic-sampling-iii-real-life-data-part-ii","status":"publish","type":"post","link":"https:\/\/www.red-gate.com\/simple-talk\/databases\/oracle-databases\/dynamic-sampling-iii-real-life-data-part-ii\/","title":{"rendered":"Dynamic Sampling (III) &#8211; Real-Life Data &#8211; Part II"},"content":{"rendered":"<p>In the <a title=\"Part 1\" href=\"https:\/\/allthingsoracle.com\/an-organizational-constraint-that-diminishes-software-quality\/\" target=\"_blank\">previous installment<\/a> of this series we saw that Dynamic Sampling wasn&#8217;t of great help when dealing with certain types of data patterns. We tried to add a suitable index to improve the Dynamic Sampling results, but no luck so far.<\/p>\n<p>The solution in this case is that with statistics in place the index only gets used when adding the <strong>DYNAMIC_SAMPLING_EST_CDN<\/strong> hint (see the <a title=\"Dynamic Sampling (II) \u2013 Controlling the Activity \u2013 Part 2\" href=\"https:\/\/allthingsoracle.com\/dynamic-sampling-ii-controlling-the-activity-part-2\/\">previous parts<\/a> of the series for an explanation of that hint). I&#8217;m not sure why this is as in my understanding it is only used to <strong>improve<\/strong> the selectivity estimate and not to <strong>overwrite<\/strong> existing cardinality\/blocks statistics, but that&#8217;s the way the code at present works.<\/p>\n<p>Re-running the experiment now looks like this:<\/p>\n<pre>set echo on\r\n\r\nalter session set tracefile_identifier = 'dyn_samp';\r\n\r\nalter session set optimizer_dynamic_sampling = 4;\r\n\r\nalter session set events '10053 trace name context forever, level 1';\r\n\r\nexplain plan for\r\nselect  \/*+ <span style=\"color: #ff0000\"><strong>DYNAMIC_SAMPLING_EST_CDN(pcl)<\/strong><\/span> *\/\r\n        *\r\nfrom\r\n        parcels pcl\r\nwhere\r\n        pcl.pickup_date &gt;= sysdate - 1\r\nand     pcl.delivery_status = 'In-transit'\r\n;\r\n\r\nselect * from table(dbms_xplan.display);<\/pre>\n<p>And the results:<\/p>\n<pre>-------------------------------------------------------------------------------------------\r\n| Id\u00a0 | Operation\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 | Name\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 | Rows\u00a0 | Bytes | Cost (%CPU)| Time\u00a0\u00a0\u00a0\u00a0 |\r\n-------------------------------------------------------------------------------------------\r\n|\u00a0\u00a0 0 | SELECT STATEMENT\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 |\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 |\u00a0 1000 |\u00a0\u00a0 180K|\u00a0\u00a0\u00a0 42\u00a0\u00a0 (0)| 00:00:01 |\r\n|\u00a0\u00a0 1 |\u00a0 TABLE ACCESS BY INDEX ROWID| PARCELS\u00a0\u00a0\u00a0\u00a0 |\u00a0 <span style=\"color: #ff0000\"><strong>1000<\/strong><\/span> |\u00a0\u00a0 180K|\u00a0\u00a0\u00a0 42\u00a0\u00a0 (0)| 00:00:01 |\r\n|*\u00a0 2 |\u00a0\u00a0 INDEX RANGE SCAN\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 | PARCELS_IDX |\u00a0 1282 |\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 |\u00a0\u00a0\u00a0\u00a0 8\u00a0\u00a0 (0)| 00:00:01 |\r\n-------------------------------------------------------------------------------------------\r\n\r\nPredicate Information (identified by operation id):\r\n---------------------------------------------------\r\n\r\n   2 - access(\"PCL\".\"DELIVERY_STATUS\"='In-transit' AND \"PCL\".\"PICKUP_DATE\"&gt;=SYSDATE@!-1 AND \"PCL\".\"PICKUP_DATE\" IS NOT NULL)\r\n\r\nNote\r\n-----\r\n   - dynamic sampling used for this statement (level=4)<\/pre>\n<p>And the trace file shows:<\/p>\n<pre>** Dynamic sampling initial checks returning TRUE (level = 4).\r\n** Dynamic sampling <span style=\"color: #ff0000\"><strong>updated index stats<\/strong><\/span>.: PARCELS_IDX, blocks=4065\r\n** Dynamic sampling index access candidate : PARCELS_IDX\r\n** Dynamic sampling <span style=\"color: #ff0000\"><strong>updated table stats.<\/strong><\/span>: blocks=26518\r\n\r\n*** 2012-05-29 19:00:40.282\r\n** Generated dynamic sampling query:\r\nquery text :\r\nSELECT \/* OPT_DYN_SAMP *\/ \/*+ ALL_ROWS IGNORE_WHERE_CLAUSE NO_PARALLEL(SAMPLESUB) opt_param('parallel_execution_enabled', 'false') NO_PARALLEL_INDEX(SAMPLESUB) NO_SQL_TUNE *\/ NVL(SUM(C1),0), NVL(SUM(C2),0) FROM (SELECT \/*+ IGNORE_WHERE_CLAUSE NO_PARALLEL(\"PCL\") FULL(\"PCL\") NO_PARALLEL_INDEX(\"PCL\") *\/ 1 AS C1, CASE WHEN \"PCL\".\"DELIVERY_STATUS\"='In-transit' AND \"PCL\".\"PICKUP_DATE\"&gt;=SYSDATE@!-1 THEN 1 ELSE 0 END AS C2 FROM \"PARCELS\" SAMPLE BLOCK (0.116902 , 1) SEED (1) \"PCL\") SAMPLESUB\r\n\r\n*** 2012-05-29 19:00:40.297\r\n** Executed dynamic sampling query:\r\nlevel : 4\r\nsample pct. : 0.116902\r\nactual sample size : 912\r\nfiltered sample card. : 0\r\norig. card. : 1000000\r\nblock cnt. table stat. : 26518\r\nblock cnt. for sampling: 26518\r\nmax. sample block cnt. : 32\r\nsample block cnt. : 31\r\nmin. sel. est. : 0.00000102\r\n<span style=\"color: #ff0000\"><strong>** Using recursive dynamic sampling card. est. : 780142.451613<\/strong><\/span>\r\n\r\n*** 2012-05-29 19:00:40.297\r\n** Generated dynamic sampling query:\r\nquery text :\r\nSELECT \/* OPT_DYN_SAMP *\/ \/*+ ALL_ROWS opt_param('parallel_execution_enabled', 'false') NO_PARALLEL(SAMPLESUB) NO_PARALLEL_INDEX(SAMPLESUB) NO_SQL_TUNE *\/ NVL(SUM(C1),0), NVL(SUM(C2),0), NVL(SUM(C3),0) FROM (SELECT \/*+ NO_PARALLEL(\"PCL\") INDEX(\"PCL\" PARCELS_IDX) NO_PARALLEL_INDEX(\"PCL\") *\/ 1 AS C1, 1 AS C2, 1 AS C3\u00a0 FROM \"PARCELS\" \"PCL\" WHERE \"PCL\".\"DELIVERY_STATUS\"='In-transit' AND \"PCL\".\"PICKUP_DATE\"&gt;=SYSDATE@!-1 AND ROWNUM &lt;= 2500) SAMPLESUB\r\n\r\n*** 2012-05-29 19:00:40.297\r\n** Executed dynamic sampling query:\r\nlevel : 4\r\nsample pct. : 100.000000\r\nactual sample size : 780142\r\n<span style=\"color: #ff0000\"><strong>filtered sample card. : 1000<\/strong><\/span>\r\n<span style=\"color: #ff0000\"><strong>filtered sample card. (index PARCELS_IDX): 1000<\/strong><\/span>\r\norig. card. : 780142\r\nblock cnt. table stat. : 26518\r\nblock cnt. for sampling: 26518\r\nmax. sample block cnt. : 4294967295\r\nsample block cnt. : 26518\r\nmin. sel. est. : 0.00000102\r\nindex PARCELS_IDX selectivity est.: 0.00128182\r\n<span style=\"color: #ff0000\"><strong>** Using dynamic sampling card. : 780142<\/strong> <strong>** Using single table dynamic sel. est. : 0.00128182<\/strong><\/span>\r\nTable: PARCELS\u00a0 Alias: PCL\r\nCard: Original: 780142.451613\u00a0 Rounded: 1000\u00a0 Computed: 1000.00\u00a0 Non Adjusted: 1000.00<\/pre>\n<p>That looks very promising &#8211; with a <strong>very small overhead<\/strong> we get a <strong>spot on<\/strong> cardinality estimate. Note that by small overhead I mean the sampling overhead, of course there is the additional overhead of maintaining the index if it was added only for that specific purpose.<\/p>\n<p>If you check the optimizer trace file carefully, you&#8217;ll notice <strong>two recursive queries<\/strong> issued by the Dynamic Sampling code. The first one corresponds to those we&#8217;ve already seen, but the second one is different: It explicitly uses the index <strong>PARCELS_IDX<\/strong>; doesn&#8217;t use the <strong>SAMPLE<\/strong> clause but restricts the result set to the first <strong>2,500 rows<\/strong>.<\/p>\n<p>The idea seems to be that if Oracle finds a <strong>suitable index<\/strong> and decides to use it (more on that in a moment) it can very cheaply check for a <strong>very precise<\/strong> selectivity \/ cardinality estimate in case of rare values (which in this case means less than <strong>2,500 occurrences<\/strong>) based on the index.<\/p>\n<p>What happens if the index query identifies 2,500 rows (or more)? In that case Oracle doesn&#8217;t know exactly how many rows are identified and it uses the <strong>greater cardinality<\/strong> of the two queries to arrive at the cardinality estimate. This means that depending on the data pattern and the results of the two queries you might end up with a pretty <strong>incorrect<\/strong> estimate, but it might at least be better than the result of the single query used by default.<\/p>\n<blockquote>\n<p>Note: If you happen to use <strong>partitioned tables<\/strong> there are a number of nasty bugs related to Dynamic Sampling in versions prior to <strong>11.2.0.2 \/ 11.2.0.3<\/strong> respectively. In particular the cardinality estimate based on the additional index-based query can be completely <strong>misleading<\/strong>, for more information see this <a title=\"Dynamic Sampling Multiple Partitions Bugs\" href=\"http:\/\/oracle-randolf.blogspot.com\/2012\/01\/dynamic-sampling-on-multiple-partitions.html\" target=\"_blank\">blog post<\/a>.<\/p>\n<\/blockquote>\n<p>Unfortunately the code is at present pretty <strong>picky<\/strong> when deciding whether to <strong>use indexes<\/strong> or not.<\/p>\n<p>The first point is that for <strong>multi-column indexes<\/strong> to be used all predicates need to be <strong>index access<\/strong> predicates, which means the index won&#8217;t be used if at least one of the predicates can only be applied by <strong>filtering<\/strong> the index.<\/p>\n<p>For example, if the above index was created using the two columns in a different order (PICKUP_DATE, DELIVERY_STATUS), then it won&#8217;t be used since DELIVERY_STATUS cannot be used as an index access predicate due to the range comparison on PICKUP_DATE (this is implied by the <strong>different order<\/strong> of the index entries).<\/p>\n<p>I can understand this decision to a certain extent as otherwise it becomes <strong>unpredictable<\/strong> how many index blocks have to effectively be read to scan for <strong>2,500 rows<\/strong> in the index.<\/p>\n<p>This also means that certain kinds of <strong>complex predicates<\/strong>, like for example:<\/p>\n<pre>pickup_date &gt;= sysdate - 1 and some_other_date &gt;= sysdate - 5 and delivery_status = 'In-transit'<\/pre>\n<p><strong>cannot<\/strong> be covered by Dynamic Sampling using indexes, since it is not possible to use all three predicates as access predicates <strong>no matter<\/strong> how the index is defined.<\/p>\n<p>What I can&#8217;t understand is the fact that adding another index that <strong>starts with<\/strong> one of the columns used as predicates <strong>disables<\/strong> the usage of the indexes <strong>completely<\/strong>.<\/p>\n<p>For example, if I add this index:<\/p>\n<pre>create index parcels_idx2 on parcels (delivery_status);<\/pre>\n<p>this is the result for the previous, unchanged test case I get:<\/p>\n<pre>-------------------------------------------------------------------------------------------\r\n| Id\u00a0 | Operation\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 | Name\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 | Rows\u00a0 | Bytes | Cost (%CPU)| Time\u00a0\u00a0\u00a0\u00a0 |\r\n-------------------------------------------------------------------------------------------\r\n|\u00a0\u00a0 0 | SELECT STATEMENT\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 |\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 |\u00a0\u00a0\u00a0\u00a0 1 |\u00a0\u00a0 193 |\u00a0\u00a0\u00a0\u00a0 4\u00a0\u00a0 (0)| 00:00:01 |\r\n|\u00a0\u00a0 1 |\u00a0 TABLE ACCESS BY INDEX ROWID| PARCELS\u00a0\u00a0\u00a0\u00a0 |\u00a0\u00a0\u00a0\u00a0 <span style=\"color: #ff0000\"><strong>1<\/strong><\/span> |\u00a0\u00a0 193 |\u00a0\u00a0\u00a0\u00a0 4\u00a0\u00a0 (0)| 00:00:01 |\r\n|*\u00a0 2 |\u00a0\u00a0 INDEX RANGE SCAN\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 | PARCELS_IDX |\u00a0\u00a0\u00a0\u00a0 1 |\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 |\u00a0\u00a0\u00a0\u00a0 3\u00a0\u00a0 (0)| 00:00:01 |\r\n-------------------------------------------------------------------------------------------\r\n\r\nPredicate Information (identified by operation id):\r\n---------------------------------------------------\r\n\r\n   2 - access(\"PCL\".\"DELIVERY_STATUS\"='In-transit' AND \"PCL\".\"PICKUP_DATE\"&gt;=SYSDATE@!-1 AND \"PCL\".\"PICKUP_DATE\" IS NOT NULL)\r\n\r\nNote\r\n-----\r\n   - dynamic sampling used for this statement (level=4)<\/pre>\n<p>So we are back to square one. And this is what the optimizer trace file tells us:<\/p>\n<pre>** Dynamic sampling initial checks returning TRUE (level = 4).\r\n** Dynamic sampling <span style=\"color: #ff0000\"><strong>updated index stats<\/strong><\/span>.: PARCELS_IDX, blocks=4066\r\n** Dynamic sampling <span style=\"color: #ff0000\"><strong>updated index stats<\/strong><\/span>.: PARCELS_IDX2, blocks=2934\r\n** Dynamic sampling <span style=\"color: #ff0000\"><strong>updated table stats<\/strong><\/span>.: blocks=27984\r\n\r\n*** 2012-05-29 19:20:54.473\r\n** Generated dynamic sampling query:\r\nquery text :\r\nSELECT \/* OPT_DYN_SAMP *\/ \/*+ ALL_ROWS IGNORE_WHERE_CLAUSE NO_PARALLEL(SAMPLESUB) opt_param('parallel_execution_enabled', 'false') NO_PARALLEL_INDEX(SAMPLESUB) NO_SQL_TUNE *\/ NVL(SUM(C1),0), NVL(SUM(C2),0), NVL(SUM(C3),0), NVL(SUM(C4),0) FROM (SELECT \/*+ IGNORE_WHERE_CLAUSE NO_PARALLEL(\"PCL\") FULL(\"PCL\") NO_PARALLEL_INDEX(\"PCL\") *\/ 1 AS C1, CASE WHEN \"PCL\".\"DELIVERY_STATUS\"='In-transit' AND \"PCL\".\"PICKUP_DATE\"&gt;=SYSDATE@!-1 THEN 1 ELSE 0 END AS C2, CASE WHEN \"PCL\".\"DELIVERY_STATUS\"='In-transit' THEN 1 ELSE 0 END AS C3, CASE WHEN \"PCL\".\"PICKUP_DATE\"&gt;=SYSDATE@!-1 AND \"PCL\".\"DELIVERY_STATUS\"='In-transit' THEN 1 ELSE 0 END AS C4 FROM \"PARCELS\" SAMPLE BLOCK (0.110778 , 1) SEED (1) \"PCL\") SAMPLESUB\r\n\r\n*** 2012-05-29 19:20:54.488\r\n** Executed dynamic sampling query:\r\nlevel : 4\r\nsample pct. : 0.110778\r\n<span style=\"color: #ff0000\"><strong>actual sample size : 1260<\/strong><\/span>\r\n<span style=\"color: #ff0000\"><strong>filtered sample card. : 0<\/strong><\/span>\r\n<span style=\"color: #ff0000\"><strong>filtered sample card. (index PARCELS_IDX2): 0<\/strong><\/span>\r\n<span style=\"color: #ff0000\"><strong>filtered sample card. (index PARCELS_IDX): 0<\/strong><\/span>\r\norig. card. : 1000000\r\nblock cnt. table stat. : 27984\r\nblock cnt. for sampling: 27984\r\nmax. sample block cnt. : 32\r\n<span style=\"color: #ff0000\"><strong>sample block cnt. : 31<\/strong><\/span>\r\nmin. sel. est. : 0.00000064\r\n<span style=\"color: #ff0000\"><strong>** Using dynamic sampling card. : 1137414<\/strong><\/span>\r\n\r\nTable: PARCELS\u00a0 Alias: PCL\r\nCard: Original: 1137414.193548\u00a0 Rounded: 1\u00a0 Computed: 0.73\u00a0 Non Adjusted: 0.73<\/pre>\n<p>So both indexes were <strong>recognized<\/strong>, but none of them were used to run the index-based query. I believe this could be classified as a <strong>bug<\/strong>, but all versions up to the latest one available at the time of writing this (11.2.0.3) show this behaviour.<\/p>\n<blockquote>\n<p>Note: You&#8217;ll notice that using the <strong>DYNAMIC_SAMPLING_EST_CDN<\/strong> hint seems to implicitly override the sanity checks &#8211; the results of the Dynamic Sampling get used even with questionable results. This is something to keep in mind when using the approach outlined here.<\/p>\n<\/blockquote>\n<h2>Beyond Dynamic Sampling And Indexes<\/h2>\n<p>If you happen to have such cases that you <strong>can&#8217;t cover<\/strong> with either Dynamic Sampling, potentially supported by indexes, nor Extended Statistics on Column Groups, then you might be able to get improved cardinality estimates by using suitable Virtual Columns or Extended Statistics <strong>expressions<\/strong>.<\/p>\n<p>This is, however, only applicable in cases where you can <strong>change<\/strong> the query the application submits, and where you know the expressions <strong>upfront<\/strong>.<\/p>\n<p>For our example here, assuming the index option is not feasible for whatever reason, you could try to add the following Virtual Column or Extended Statistics expression (only from 11g on, pre-11g would need a corresponding <strong>Function-Based Index<\/strong>):<\/p>\n<pre>alter table parcels add (rare_vals as (cast(case when pickup_date &gt;= sysdate - 1 and delivery_status = 'In-transit' then 'Y' else null end) as varchar2(1));<\/pre>\n<p>only to find out that you get an <strong>error<\/strong> &#8220;only pure functions can be specified in a virtual column expression&#8221;, due to the SYSDATE expression.<\/p>\n<p>Assuming a suitable expression, just as an example here (adjust the literal expression as necessary):<\/p>\n<pre>alter table parcels add (rare_vals as (cast(case when pickup_date &gt;= to_date('30.05.2012 09:13:16', 'DD.MM.YYYY HH24:MI:SS') and delivery_status = 'In-transit' then 'Y' else null end) as varchar2(1));<\/pre>\n<p>you can add such a virtual column and after gathering statistics on that column:<\/p>\n<pre>exec dbms_stats.gather_table_stats(null, 'parcels', method_opt =&gt; 'for columns size 1 rare_vals')<\/pre>\n<blockquote>\n<p>Note: It&#8217;s very important to remember that Extended Statistics, Virtual Columns (or the Virtual Columns added implicitly via Function-Based Indexes) need to <strong>have statistics gathered<\/strong> for, otherwise the optimizer can&#8217;t leverage the added value of such expressions. Although default METHOD_OPT options like &#8220;FOR ALL COLUMNS SIZE AUTO&#8221; will include <strong>such additional columns<\/strong> the next time statistics are gathered, it is important to <strong>explicitly<\/strong> gather statistics after you&#8217;ve <strong>just added<\/strong> such columns if you want to take full advantage of them<\/p>\n<\/blockquote>\n<p>You could run a query like the following, <strong>without<\/strong> the need for Dynamic Sampling, indexes or even histograms:<\/p>\n<pre>explain plan for\r\nselect\r\n        *\r\nfrom\r\n        parcels pcl\r\nwhere\r\n        rare_vals = 'Y'\r\n;\r\n\r\nselect * from table(dbms_xplan.display);\r\n\r\n-----------------------------------------------------------------------------\r\n| Id\u00a0 | Operation\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 | Name\u00a0\u00a0\u00a0 | Rows\u00a0 | Bytes | Cost (%CPU)| Time\u00a0\u00a0\u00a0\u00a0 |\r\n-----------------------------------------------------------------------------\r\n|\u00a0\u00a0 0 | SELECT STATEMENT\u00a0 |\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 |\u00a0 1000 |\u00a0\u00a0 189K|\u00a0 7743\u00a0\u00a0 (3)| 00:01:33 |\r\n|*\u00a0 1 |\u00a0 TABLE ACCESS FULL| PARCELS |\u00a0 <span style=\"color: #ff0000\"><strong>1000<\/strong><\/span> |\u00a0\u00a0 189K|\u00a0 7743\u00a0\u00a0 (3)| 00:01:33 |\r\n-----------------------------------------------------------------------------\r\n\r\nPredicate Information (identified by operation id):\r\n---------------------------------------------------\r\n\r\n   1 - filter(\"RARE_VALS\"='Y')<\/pre>\n<p>So the cardinality estimate is spot on, but you need to know the expressions upfront, they need to be <strong>static<\/strong> and the expression needs to be <strong>supported<\/strong> by the Virtual Column code. Of course this approach could be further refined to serve more complex expressions, but then again histograms might be required to handle the virtual column data statistics properly.<\/p>\n<h2>Summary<\/h2>\n<p>Dynamic Sampling can be very useful, but if you&#8217;re looking for <strong>rare values<\/strong>, or if the data you&#8217;re looking for is <strong>not uniformly distributed<\/strong> across the table but <strong>clustered together<\/strong>, the default Dynamic Sampling might fail to produce helpful results.<\/p>\n<p>Sometimes you might be able to get Dynamic Sampling working by <strong>adding a suitable index<\/strong>, but you need to check carefully if the Dynamic Sampling code decides to actually <strong>use the index<\/strong> or not, and if statistics exist, you need to explicitly specify the <strong>DYNAMIC_SAMPLING_EST_CDN<\/strong> hint for the table in question, which is a pity, because it makes generic implementations more complex.<\/p>\n<p>In other cases where you can influence the query issued you might able to overcome such issues by using corresponding Virtual Column expressions, if these are <strong>static<\/strong> in nature and <strong>supported<\/strong> by the Virtual Column code.<\/p>\n<p>The <strong>purpose<\/strong> of the whole exercise is to arrive at more <strong>reasonable cardinality estimates<\/strong> &#8211; based on the knowledge that <strong>incorrect<\/strong> cardinality estimates are very often the <strong>root cause<\/strong> of inefficient execution plans (If you want to learn more why this is so important, you could watch my <a title=\"Oracle Cost-Based Optimizer Basics\" href=\"https:\/\/allthingsoracle.com\/oracle-cost-based-optimizer-basics-webinar\/\" target=\"_blank\">Webinar recording<\/a> available here on Cost-Based Optimizer Basics).<\/p>\n<p>Go to <a title=\"Part 1\" href=\"https:\/\/allthingsoracle.com\/introduction-to-oracle-flashback-technology-part-3-flashback-database\/\" target=\"_blank\">Dynamic Sampling (III) &#8211; Real-Life Data &#8211; Part I<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>In the previous installment of this series we saw that Dynamic Sampling wasn&#8217;t of great help when dealing with certain types of data patterns. We tried to add a suitable index to improve the Dynamic Sampling results, but no luck so far. The solution in this case is that with statistics in place the index only gets used when adding&hellip;<\/p>\n","protected":false},"author":316197,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[143533],"tags":[],"coauthors":[],"class_list":["post-73303","post","type-post","status-publish","format-standard","hentry","category-oracle-databases"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/73303","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\/316197"}],"replies":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/comments?post=73303"}],"version-history":[{"count":1,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/73303\/revisions"}],"predecessor-version":[{"id":91755,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/73303\/revisions\/91755"}],"wp:attachment":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media?parent=73303"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/categories?post=73303"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/tags?post=73303"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/coauthors?post=73303"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}