Optimize oracle update query - oracle

How to optimize an update query:
UPDATE frst_usage vfm1 SET
(vfm1.account_dn,
vfm1.usage_date,
vfm1.country,
vfm1.feature_name,
vfm1.hu_type,
vfm1.make,
vfm1.region,
vfm1.service_hits,
vfm1.maint_last_ts,
vfm1.accountdn_hashcode) = (
SELECT
(SELECT vst.account_dn FROM services_track vst WHERE vst.accountdn_hashcode = vrd1.account_dn_hashcode AND rownum = 1),
min(usage_date),
country,
feature_name,
hu_type,
make,
region,
service_hits,
SYSDATE,
account_dn_hashcode
FROM raw_data vrd1
WHERE vrd1.vin_hashcode = vfm1.vin_hashcode
AND vrd1.usage_date IS NOT NULL AND rownum = 1
GROUP BY account_dn, country, feature_name, hu_type, make, region, service_hits, vfm1.maint_last_ts, account_dn_hashcode
);
the tables have indexes on all the columns available in the where conditions.
Still the execution is taking more than 4 hours. Below is the explain plan
From the execution plan i could see that the select is good but the update is consuming more time resources, Is there a way i could optimize this.

I think correlated subqueries may be an issue:
WHERE vrd1.vin_hashcode = vfm1.vin_hashcode
You should try merge clause, it could have dramatic impact on performance
http://docs.oracle.com/cd/E11882_01/server.112/e41084/statements_9016.htm
Below is example similar to yours. 10k sample rows, all columns indexed and statistics gathered:
Update (16s)
SQL> update x1 set (v1, v2, v3, v4) =
2 (
3 select v1, v2, v3, min(v4)
4 from x2
5 where x1.nr = x2.nr
6 group by v1,v2,v3
7 );
9999 rows updated.
Elapsed: 00:00:16.56
Execution Plan
----------------------------------------------------------
Plan hash value: 3497322513
----------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------
| 0 | UPDATE STATEMENT | | 9999 | 859K| 1679K (5)| 05:35:59 |
| 1 | UPDATE | X1 | | | | |
| 2 | TABLE ACCESS FULL | X1 | 9999 | 859K| 40 (0)| 00:00:01 |
| 3 | SORT GROUP BY | | 1 | 88 | 41 (3)| 00:00:01 |
|* 4 | TABLE ACCESS FULL| X2 | 1 | 88 | 40 (0)| 00:00:01 |
----------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
4 - filter("X2"."NR"=:B1)
Merge(1,5s)
SQL> merge into x1 using (
2 select nr, v1, v2, v3, min(v4) v4
3 from x2
4 group by nr, v1,v2,v3
5 ) xx2
6 on (x1.nr = xx2.nr)
7 when matched then update set
8 x1.v1 = xx2.v1, x1.v2 = xx2.v2, x1.v3 = xx2.v3, x1.v4 = xx2.v4;
9999 rows merged.
Elapsed: 00:00:01.25
Execution Plan
----------------------------------------------------------
Plan hash value: 1113810112
---------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time |
---------------------------------------------------------------------------------------
| 0 | MERGE STATEMENT | | 9999 | 58M| | 285 (1)| 00:00:04 |
| 1 | MERGE | X1 | | | | | |
| 2 | VIEW | | | | | | |
|* 3 | HASH JOIN | | 9999 | 58M| | 285 (1)| 00:00:04 |
| 4 | TABLE ACCESS FULL | X1 | 9999 | 859K| | 40 (0)| 00:00:01 |
| 5 | VIEW | | 9999 | 57M| | 244 (1)| 00:00:03 |
| 6 | SORT GROUP BY | | 9999 | 859K| 1040K| 244 (1)| 00:00:03 |
| 7 | TABLE ACCESS FULL| X2 | 9999 | 859K| | 40 (0)| 00:00:01 |
---------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
3 - access("X1"."NR"="XX2"."NR")

Indexes on the the target table reduce the performance.Disable the index before updating the table and rebuild the index once the update completed.

Able to solve this using below query, and now the explain plan looks good.
merge INTO FRST_USAGE vfu USING
(SELECT tmp1.*
FROM
(SELECT ROW_NUMBER() OVER (PARTITION BY tmp.vin_hash ORDER BY tmp.usage_date) AS rn,
tmp.*
FROM
(SELECT vrd.VIN_HASHCODE AS vin_hash,
vrd.ACCOUNT_DN_HASHCODE AS actdn_hash,
vst.ACCOUNT_DN AS actdn,
vrd.FEATURE_NAME AS feature,
vrd.MAKE AS make,
vrd.COUNTRY AS country,
vrd.HU_TYPE AS hu,
vrd.REGION AS region,
vrd.SERVICE_HITS AS hits,
MIN(vrd.USAGE_DATE) AS usage_date,
sysdate AS maintlastTs
FROM RAW_DATA vrd,
SERVICES_TRACK vst
WHERE vrd.ACCOUNT_DN_HASHCODE=vst.ACCOUNTDN_HASHCODE
GROUP BY vrd.VIN_HASHCODE,
vrd.ACCOUNT_DN_HASHCODE,
vst.ACCOUNT_DN,
vrd.FEATURE_NAME,
vrd.MAKE,
vrd.COUNTRY,
vrd.HU_TYPE,
vrd.REGION,
vrd.SERVICE_HITS,
sysdate
ORDER BY vrd.VIN_HASHCODE,
MIN(vrd.USAGE_DATE)
) tmp
)tmp1
WHERE tmp1.rn =1
) tmp2 ON (vfu.VIN_HASHCODE = tmp2.vin_hash)
WHEN matched THEN
UPDATE
SET vfu.ACCOUNTDN_HASHCODE=tmp2.actdn_hash,
vfu.account_dn =tmp2.actdn,
vfu.FEATURE_NAME =tmp2.feature,
vfu.MAKE =tmp2.make,
vfu.COUNTRY =tmp2.country,
vfu.HU_TYPE =tmp2.hu,
vfu.REGION =tmp2.region,
vfu.SERVICE_HITS =tmp2.hits,
vfu.usage_date =tmp2.usage_date,
vfu.MAINT_LAST_TS =tmp2.maintlastTs;
below is the Explan plan:
Suggestions are allowed if there is any more optimizations i can do on this.

Related

Limit rows examined in Oracle

My table has millions of records. In this query below, can I make Oracle 12c examine the first X rows only instead of doing a full table scan?
The value of X, I imagine should be Offset + Fetch Next , so in this case 15
SELECT * FROM table OFFSET 5 ROWS FETCH NEXT 10 ROWS ONLY;
Thanks in advance
Edit 1
These are the tables involved and this is the actual query
Orders - This table has 113k records in my test DB ( and over 8 million in prod db like my original question mentioned)
--------------------------
| Id | SKUField1|SKUField2|
--------------------------
| 1 | Value1 | Value2 |
| 2 | Value2 | Value2 |
| 3 | Value1 | Value3 |
--------------------------
Products - This table has 2 million records in my test DB ( prod db is similar)
---------------
| PId| SKU_NUM|
---------------
| 1 | Value1 |
| 2 | Value2 |
| 3 | Value3 |
---------------
Note that values of Orders.SKUField1 and Orders.SKUField2 come from the Products.SKU_NUM values
Actual Query:
SELECT /*+ gather_plan_statistics */ Id, PId, SKUField1, SKUField2, SKU_NUM
FROM Orders
LEFT JOIN (
-- this inner query reduces size of Products from 2 million rows down to 1462 rows
select * from Products where SKU_NUM in (
select SKUField1 from Orders
)
) p1 ON SKUField1 = p1.SKU_NUM
LEFT JOIN (
-- this inner query reduces size of table B from 2 million rows down to 459 rows
select * from Products where SKU_NUM in (
select SKUField2 from Orders
)
) p4 ON SKUField2 = p4.SKU_NUM
OFFSET 5 ROWS FETCH NEXT 10 ROWS ONLY
Execution Plan:
--------------------------------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Time | A-Rows | A-Time | Buffers | OMem | 1Mem | Used-Mem |
--------------------------------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 10 |00:00:00.06 | 8013 | | | |
|* 1 | VIEW | | 1 | 00:00:01 | 10 |00:00:00.06 | 8013 | | | |
|* 2 | WINDOW NOSORT STOPKEY | | 1 | 00:00:01 | 15 |00:00:00.06 | 8013 | 27M| 1904K| |
|* 3 | HASH JOIN RIGHT OUTER | | 1 | 00:00:01 | 15 |00:00:00.06 | 8013 | 1162K| 1162K| 1344K (0)|
| 4 | VIEW | | 1 | 00:00:01 | 1462 |00:00:00.04 | 6795 | | | |
| 5 | NESTED LOOPS | | 1 | 00:00:01 | 1462 |00:00:00.04 | 6795 | | | |
| 6 | NESTED LOOPS | | 1 | 00:00:01 | 1462 |00:00:00.04 | 5333 | | | |
| 7 | SORT UNIQUE | | 1 | 00:00:01 | 1469 |00:00:00.04 | 3010 | 80896 | 80896 |71680 (0)|
| 8 | TABLE ACCESS FULL | Orders | 1 | 00:00:01 | 113K|00:00:00.02 | 3010 | | | |
|* 9 | INDEX UNIQUE SCAN | UIX_Product_SKU_NUM | 1469 | 00:00:01 | 1462 |00:00:00.01 | 2323 | | | |
| 10 | TABLE ACCESS BY INDEX ROWID | Products | 1462 | 00:00:01 | 1462 |00:00:00.01 | 1462 | | | |
|* 11 | HASH JOIN RIGHT OUTER | | 1 | 00:00:01 | 15 |00:00:00.02 | 1218 | 1142K| 1142K| 1335K (0)|
| 12 | VIEW | | 1 | 00:00:01 | 459 |00:00:00.02 | 1213 | | | |
| 13 | NESTED LOOPS | | 1 | 00:00:01 | 459 |00:00:00.02 | 1213 | | | |
| 14 | NESTED LOOPS | | 1 | 00:00:01 | 459 |00:00:00.02 | 754 | | | |
| 15 | SORT UNIQUE | | 1 | 00:00:01 | 462 |00:00:00.02 | 377 | 24576 | 24576 |22528 (0)|
| 16 | INDEX FAST FULL SCAN | Orders_SKUField2_IDX6 | 1 | 00:00:01 | 113K|00:00:00.01 | 377 | | | |
|* 17 | INDEX UNIQUE SCAN | UIX_Product_SKU_NUM | 462 | 00:00:01 | 459 |00:00:00.01 | 377 | | | |
| 18 | TABLE ACCESS BY INDEX ROWID| Products | 459 | 00:00:01 | 459 |00:00:00.01 | 459 | | | |
| 19 | TABLE ACCESS FULL | Orders | 1 | 00:00:01 | 15 |00:00:00.01 | 5 | | | |
--------------------------------------------------------------------------------------------------------------------------------------------------
Hence, based on the "A-Rows" column values for row Ids 8 and 16 in the execution plan, it seems like there are full table scans on the Orders table (though row id 16 atleast seems to be using an index). So my question is is it true that there is a full table scan on the orders table even though I am using Offset/Fetch Next
Although your FETCH clause may use a full table scan, Oracle will still only fetch the first X rows from the table.
In the following example, the "TABLE ACCESS FULL" operation does start to read the entire table, but it gets cutoff part of the way through by the "WINDOW NOSORT STOPKEY" operation. Not all full table scans actually scan the full table. You would see similar behavior if your code ended with WHERE ROWNUM <= 50.
CREATE TABLE some_table AS SELECT * FROM all_objects;
EXPLAIN PLAN FOR SELECT * FROM some_table OFFSET 5 ROWS FETCH NEXT 10 ROWS ONLY;
SELECT * FROM TABLE(dbms_xplan.display);
Plan hash value: 2559837639
-------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 15 | 7410 | 2 (0)| 00:00:01 |
|* 1 | VIEW | | 15 | 7410 | 2 (0)| 00:00:01 |
|* 2 | WINDOW NOSORT STOPKEY| | 15 | 2010 | 2 (0)| 00:00:01 |
| 3 | TABLE ACCESS FULL | SOME_TABLE | 15 | 2010 | 2 (0)| 00:00:01 |
-------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter("from$_subquery$_002"."rowlimit_$$_rownumber"<=15 AND
"from$_subquery$_002"."rowlimit_$$_rownumber">5)
2 - filter(ROW_NUMBER() OVER ( ORDER BY NULL )<=15)
The performance implications get more complicated if you also want to order the results. If that is the case, you may want to post the full query and execution plan.
(EDIT: 2022-09-25)
Yes, there is a full table scan on the ORDERS table happening on line 8 of the execution plan. As you mentioned, you can look at the "A-rows" column to tell what's really happening.
But the third full table scan of ORDERS, on line 19, is not a "full" full table scan. The operation "WINDOW NOSORT STOPKEY" stops that full table scan as soon as the 15 necessary rows are read. So the FETCH syntax is helping at least a little.
Applying a FETCH to a query does not mean that every single table will be limited. Although, in your query, it does seem like there ought to be a way to reduce the full table scans. Perhaps an index on SKUField1 would help?
Since Oracle as I know don't provide something like limit or top you can created by yourself like the following:
what is happening here, the inner query gets all the first 10 records and the outer query get those, you can still use any clauses like where or order or any others
SELECT * FROM (
SELECT * FROM Customers WHERE CustomerID <= 10 ORDER BY CustomerID
)
The full article will be found about this topic here at Oracle-Fetch
I am using Online Oracle so you can try it from your end, please let me know if you still have a problem.

Oracle Compound Join Predicate Causes Row Estimate to be Incorrect

In the example below Oracle's optimizer's estimated rows is incorrect by two orders of magnitude. How do I improve the estimated rows?
Table A has rows with numbers 1 through 1,000 for each of the 10 letters A through J.
Table C has 100 copies of table A.
So, table A has a cardinality of 10K and table C has a cardinality of 1M.
A given single-valued predicate on the number in table A will yield 1/1000 of the rows in table A (same for table C).
A given single-valued predicate on the letter in table A will yield 1/10 of the rows in table A (same for table C).
Setup script.
drop table C;
drop table A;
create table A
( num NUMBER
, val VARCHAR2(3 byte)
, pad CHAR(40 byte)
)
;
insert /*+ append enable_parallel_dml parallel (auto) */
into A (num, val, pad)
select mod(level-1, 1000) +1
, chr(mod(ceil(level/1000) - 1, 10) + ascii('A'))
, ' '
from dual
connect by level <= 10*1000
;
create table C
( id NUMBER
, num NUMBER
, val VARCHAR2(3 byte)
, pad CHAR(40 byte)
)
;
insert /*+ append enable_parallel_dml parallel (auto) */
into C (id, num, val, pad)
with
"D1" as
( select /*+ materialize */ null from dual connect by level <= 100 --320
)
, "D" as
( select /*+ materialize */
level rn
, mod(level-1, 1000) + 1 num
, chr(mod(ceil(level/1000) - 1, 10) + ascii('A')) val
, ' ' pad
from dual
connect by level <= 10*1000
order by 1 offset 0 rows
)
select rownum id
, num num
, val val
, pad pad
from "D1", "D"
;
commit;
exec dbms_stats.gather_table_stats(OwnName => null, TabName => 'A', cascade => true);
exec dbms_stats.gather_table_stats(OwnName => null, TabName => 'C', cascade => true);
Consider the explain plan to the following query.
select *
from A
join C
on A.num = C.num
and A.val = C.val
where A.num = 1
and A.val = 'A'
;
---------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 100 | 9900 | 2209 (1)| 00:00:01 |
|* 1 | HASH JOIN | | 100 | 9900 | 2209 (1)| 00:00:01 |
|* 2 | TABLE ACCESS FULL| A | 1 | 47 | 23 (0)| 00:00:01 |
|* 3 | TABLE ACCESS FULL| C | 100 | 5200 | 2185 (1)| 00:00:01 |
---------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - access("A"."NUM"="C"."NUM" AND "A"."VAL"="C"."VAL")
2 - filter("A"."NUM"=1 AND "A"."VAL"='A')
3 - filter("C"."NUM"=1 AND "C"."VAL"='A')
The row cardinality of each step makes sense to me.
ID=2 --> (1/1,000) * (1/10) * 10,000 = 1
ID=3 --> (1/1,000) * (1/10) * 1,000,000 = 100
ID=1 --> 100 is correct. Predicates in ID=2 and ID=3 are the same, every row from ID=2 will have one and only one match in the row source from ID=3.
Now consider the explain plan to the slightly modified query below.
select *
from A
join C
on A.num = C.num
and A.val = C.val
where A.num in(1,2)
and A.val = 'A'
;
---------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 2 | 198 | 2209 (1)| 00:00:01 |
|* 1 | HASH JOIN | | 2 | 198 | 2209 (1)| 00:00:01 |
|* 2 | TABLE ACCESS FULL| A | 2 | 94 | 23 (0)| 00:00:01 |
|* 3 | TABLE ACCESS FULL| C | 200 | 10400 | 2185 (1)| 00:00:01 |
---------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - access("A"."NUM"="C"."NUM" AND "A"."VAL"="C"."VAL")
2 - filter("A"."VAL"='A' AND ("A"."NUM"=1 OR "A"."NUM"=2))
3 - filter("C"."VAL"='A' AND ("C"."NUM"=1 OR "C"."NUM"=2))
The row cardinality of each step ID=2 and ID=3 makes sense to me, but now ID=1 is incorrect by two orders of magnitude.
ID=2 --> (1/1,000)(1/10) * 10,000 = 1
ID=3 --> (1/1,000)(1/10) * 1,000,000 = 100
ID=1 --> The optimizer's estimate is two orders of magnitude different from the actual.
Adding unique and foreign constraints and extended statistics did not improve the estimated row counts.
create unique index IU_A on A (num, val);
alter table A add constraint UK_A unique (num, val) rely using index IU_A enable validate;
alter table C add constraint R_C foreign key (num, val) references A (num, val) rely enable validate;
create index IR_C on C (num, val);
select dbms_stats.create_extended_stats(null,'A','(num, val)') from dual;
select dbms_stats.create_extended_stats(null,'C','(num, val)') from dual;
exec dbms_stats.gather_table_stats(OwnName => null, TabName => 'A', cascade => true);
exec dbms_stats.gather_table_stats(OwnName => null, TabName => 'C', cascade => true);
---------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 2 | 198 | 10 (0)| 00:00:01 |
| 1 | NESTED LOOPS | | | | | |
| 2 | NESTED LOOPS | | 2 | 198 | 10 (0)| 00:00:01 |
| 3 | INLIST ITERATOR | | | | | |
| 4 | TABLE ACCESS BY INDEX ROWID| A | 2 | 94 | 5 (0)| 00:00:01 |
|* 5 | INDEX UNIQUE SCAN | IU_A | 2 | | 3 (0)| 00:00:01 |
|* 6 | INDEX RANGE SCAN | IR_C | 1 | | 2 (0)| 00:00:01 |
| 7 | TABLE ACCESS BY INDEX ROWID | C | 1 | 52 | 3 (0)| 00:00:01 |
---------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
5 - access(("A"."NUM"=1 OR "A"."NUM"=2) AND "A"."VAL"='A')
6 - access("A"."NUM"="C"."NUM" AND "C"."VAL"='A')
filter("C"."NUM"=1 OR "C"."NUM"=2)
What do I need to do to make the estimated rows better match reality?
Using Oracle Enterprise Edition 19c.
Thanks in advance.
Edit
After ensuring the most recent optimizer_features_enable was used and modifying one of the predicates, we still have an explain plan whose estimated row count is short by two orders of magnitude.
ID=6 ought to have an estimated rows of 100. It seems it is applying the predicate factor twice. Once for the access and again for the filter.
select /*+ optimizer_features_enable('19.1.0') */
*
from A
join C
on A.num = C.num
and A.val = C.val
where A.num in(1,2)
and A.val in('A','B')
;
-----------------------------------------------------------------------------------------------
| id | Operation | name | rows | Bytes | cost (%CPU)| time |
-----------------------------------------------------------------------------------------------
| 0 | select statement | | 4 | 396 | 16 (0)| 00:00:01 |
| 1 | nested LOOPS | | 4 | 396 | 16 (0)| 00:00:01 |
| 2 | nested LOOPS | | 4 | 396 | 16 (0)| 00:00:01 |
| 3 | INLIST ITERATOR | | | | | |
| 4 | table access by index ROWID BATCHED| A | 4 | 188 | 7 (0)| 00:00:01 |
|* 5 | index range scan | IU_A | 4 | | 3 (0)| 00:00:01 |
|* 6 | index range scan | IR_C | 1 | | 2 (0)| 00:00:01 |
| 7 | table access by index ROWID | C | 1 | 52 | 3 (0)| 00:00:01 |
-----------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
5 - access("A"."NUM"=1 or "A"."NUM"=2)
filter("A"."VAL"='A' or "A"."VAL"='B')
6 - access("A"."NUM"="C"."NUM" and "A"."VAL"="C"."VAL")
filter(("C"."NUM"=1 or "C"."NUM"=2) and ("C"."VAL"='A' or "C"."VAL"='B'))

Oracle Optimizer Extraneous Filter Predicate?

Why does Oracle still apply a filter predicate on an index even after the access predicate for that same index guarantees the filter predicate is always true?
drop table index_filter_child
;
drop table index_filter_parent
;
create table index_filter_parent
as
select level id, chr(mod(level - 1, 26) + ascii('A')) code from dual connect by level <= 26
;
create table index_filter_child
as
with
"C" as (select chr(mod(level - 1, 26) + ascii('A')) code from dual connect by level <= 26)
select rownum id, C1.code from C C1, C C2
;
exec dbms_stats.gather_table_stats('USER','INDEX_FILTER_PARENT')
;
exec dbms_stats.gather_table_stats('USER','INDEX_FILTER_CHILD')
;
create index ix_index_filter_parent on index_filter_parent(code)
;
create index ix_index_filter_child on index_filter_child(code)
;
select P.*
from index_filter_parent "P"
join index_filter_child "C"
on C.code = P.code
where P.code in('A','Z') --same result if we predicate instead on C.code in('A','Z')
;
--------------------------------------------------------------------------------------------------------------
| id | Operation | name | rows | Bytes | cost (%CPU)| time |
--------------------------------------------------------------------------------------------------------------
| 0 | select statement | | 5 | 35 | 4 (0)| 00:00:01 |
| 1 | nested LOOPS | | 5 | 35 | 4 (0)| 00:00:01 |
| 2 | INLIST ITERATOR | | | | | |
| 3 | table access by index ROWID| INDEX_FILTER_PARENT | 2 | 10 | 2 (0)| 00:00:01 |
|* 4 | index range scan | IX_INDEX_FILTER_PARENT | 2 | | 1 (0)| 00:00:01 |
|* 5 | index range scan | IX_INDEX_FILTER_CHILD | 2 | 4 | 1 (0)| 00:00:01 |
--------------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
4 - access("P"."CODE"='A' or "P"."CODE"='Z')
5 - access("C"."CODE"="P"."CODE")
filter("C"."CODE"='A' or "C"."CODE"='Z') <========== why is this needed?
Why is the filter predicate in 5 needed in light of the access("C"."CODE"="P"."CODE") guaranteeing C.code is 'A' or 'Z'?
Thank you in advance.
Oracle 12.1 enterprise Edition.
This is a result of "transitive closure" transformation: you can read more about here:
Transitivity and Transitive Closure (Doc ID 68979.1) Doc id 68979.1
Jonathan Lewis - Cartesian Merge Join
Jonathan Lewis - Transitive Closure (or, even better, in his book "Cost Based Oracle Fundamentals")
If you get CBO trace (alter session set events '10053 trace name context forever, level 1' or alter session set events 'trace[SQL_Optimizer.*]), you will see that the transformation happens before choosing join method and access paths. It allows CBO to analyze more different access paths and choose the best available plan. Moreover, in case of adaptive plans, it allows oracle to change join method on the fly.
For example, you can get a plan like this:
----------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 52 | 364 | 4 (0)| 00:00:01 |
|* 1 | HASH JOIN | | 52 | 364 | 4 (0)| 00:00:01 |
| 2 | INLIST ITERATOR | | | | | |
| 3 | TABLE ACCESS BY INDEX ROWID BATCHED| INDEX_FILTER_PARENT | 2 | 10 | 2 (0)| 00:00:01 |
|* 4 | INDEX RANGE SCAN | IX_INDEX_FILTER_PARENT | 2 | | 1 (0)| 00:00:01 |
| 5 | INLIST ITERATOR | | | | | |
|* 6 | INDEX RANGE SCAN | IX_INDEX_FILTER_CHILD | 52 | 104 | 2 (0)| 00:00:01 |
----------------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - access("C"."CODE"="P"."CODE")
4 - access("P"."CODE"='A' OR "P"."CODE"='Z')
6 - access("C"."CODE"='A' OR "C"."CODE"='Z')
In fact, you can disable it using the event 10155: CBO disable generation of transitive OR-chains.
Your example:
alter session set events '10155';
explain plan for
select P.*
from index_filter_parent "P"
join index_filter_child "C"
on C.code = P.code
where P.code in('A','Z');
Results:
SQL> alter session set events '10155';
Session altered.
SQL> explain plan for
2 select P.*
3 from index_filter_parent "P"
4 join index_filter_child "C"
5 on C.code = P.code
6 where P.code in('A','Z') ;
Explained.
SQL> #xplan typical
PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------------------------------------
Plan hash value: 2543178509
----------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 52 | 364 | 4 (0)| 00:00:01 |
| 1 | NESTED LOOPS | | 52 | 364 | 4 (0)| 00:00:01 |
| 2 | INLIST ITERATOR | | | | | |
| 3 | TABLE ACCESS BY INDEX ROWID BATCHED| INDEX_FILTER_PARENT | 2 | 10 | 2 (0)| 00:00:01 |
|* 4 | INDEX RANGE SCAN | IX_INDEX_FILTER_PARENT | 2 | | 1 (0)| 00:00:01 |
|* 5 | INDEX RANGE SCAN | IX_INDEX_FILTER_CHILD | 26 | 52 | 1 (0)| 00:00:01 |
----------------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
4 - access("P"."CODE"='A' OR "P"."CODE"='Z')
5 - access("C"."CODE"="P"."CODE")
Note
-----
- this is an adaptive plan
22 rows selected.
As you can see, that predicate has disappeared.
PS. Other events for transitive predicates:
ORA-10155: CBO disable generation of transitive OR-chains
ORA-10171: CBO disable transitive join predicates
ORA-10179: CBO turn off transitive predicate replacement
ORA-10195: CBO don't use check constraints for transitive predicates

Materialized view is not showing up in plan table output from explain plan statement spool?

I have created a materialized view on SH2 but, when I run my explain plan statement I can't see the materialized view being used in the plan table output. I'm not sure if it's a more complex materialized view with additional key columns to join to other dimensions, so I'm a bit confused why the materialized view isn't being utilized as I'm refering to the SH2 prefix in my select query.
CREATE MATERIALIZED VIEW fweek_pscat_sales_mv
PCTFREE 5
BUILD IMMEDIATE
REFRESH COMPLETE
ENABLE QUERY REWRITE
AS
SELECT t.week_ending_day
, p.prod_subcategory
, sum(s.amount_sold) AS Money
, s.channel_id
, s.promo_id
FROM sales s
, times t
, products p
WHERE s.time_id = t.time_id
AND s.prod_id = p.prod_id
GROUP BY t.week_ending_day
, p.prod_subcategory
, s.channel_id
, s.promo_id;
CREATE BITMAP INDEX FW_PSC_S_MV_SUBCAT_BIX
ON fweek_pscat_sales_mv(prod_subcategory);
CREATE BITMAP INDEX FW_PSC_S_MV_CHAN_BIX
ON fweek_pscat_sales_mv(channel_id);
CREATE BITMAP INDEX FW_PSC_S_MV_PROMO_BIX
ON fweek_pscat_sales_mv(promo_id);
CREATE BITMAP INDEX FW_PSC_S_MV_WD_BIX
ON fweek_pscat_sales_mv(week_ending_day);
spool &data_dir.EXP_query_on_SH2_2.txt
alter session set query_rewrite_integrity = TRUSTED;
alter session set query_rewrite_enabled = TRUE;
set timing on
EXPLAIN PLAN FOR
SELECT t.week_ending_day
, p.prod_subcategory
, sum(s.amount_sold) AS Money
, s.channel_id
, s.promo_id
FROM SH2.sales s
, SH2.times t
, SH2.products p
WHERE s.time_id = t.time_id
AND s.prod_id = p.prod_id
GROUP BY t.week_ending_day
, p.prod_subcategory
, s.channel_id
, s.promo_id;
REM Now Let us Display the Output of the Explain Plan
SET pagesize 9999
set linesize 250
set markup html preformat on
select * from table(dbms_xplan.display());
set linesize 80
spool off
----------------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time | Pstart| Pstop |
----------------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1016K| 60M| | 17365 (1)| 00:00:01 | | |
| 1 | HASH GROUP BY | | 1016K| 60M| 70M| 17365 (1)| 00:00:01 | | |
|* 2 | HASH JOIN | | 1016K| 60M| | 2178 (1)| 00:00:01 | | |
| 3 | VIEW | index$_join$_003 | 10000 | 224K| | 74 (0)| 00:00:01 | | |
|* 4 | HASH JOIN | | | | | | | | |
| 5 | INDEX FAST FULL SCAN | PRODUCTS_PK | 10000 | 224K| | 41 (0)| 00:00:01 | | |
| 6 | INDEX FAST FULL SCAN | PRODUCTS_PROD_SUBCAT_IX | 10000 | 224K| | 51 (0)| 00:00:01 | | |
|* 7 | HASH JOIN | | 1016K| 37M| | 2101 (1)| 00:00:01 | | |
| 8 | PART JOIN FILTER CREATE | :BF0000 | 1016K| 37M| | 2101 (1)| 00:00:01 | | |
| 9 | TABLE ACCESS FULL | TIMES | 1461 | 23376 | | 13 (0)| 00:00:01 | | |
| 10 | PARTITION RANGE JOIN-FILTER| | 1016K| 22M| | 2086 (1)| 00:00:01 |:BF0000|:BF0000|
| 11 | TABLE ACCESS FULL | SALES | 1016K| 22M| | 2086 (1)| 00:00:01 |:BF0000|:BF0000|
----------------------------------------------------------------------------------------------------------------------------------
Why would you expect reference to the MV from the baseline query. For that to happen Oracle would have to compare the query to all MVs to find a match. Even further it would require every query be compared to every MV. MVs are typically created to avoid running the baseline query by accessing the MV directly (that is why MVs are the stored results of a query). If you want the MV just select from it directly.
SELECT week_ending_day
, prod_subcategory
, Money
, channel_id
, promo_id
from fweek_pscat_sales_mv;

Query does not use specified parallel degree

In my Oracle 12c database I want a statement to be executed with parallel degree 2 without the use of a hint. Note: this is a sample table so there is no improvement in cost or time.
Execution Plan with parallelism 1
PLAN_TABLE_OUTPUT
-----------------
Plan hash value: 2671887276
-----------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------
| 0 | SELECT STATEMENT | | 1 | 674 | 2 (0)| 00:00:01 |
| 1 | TABLE ACCESS BY INDEX ROWID| EVENT | 1 | 674 | 2 (0)| 00:00:01 |
|* 2 | INDEX UNIQUE SCAN | EVENT_PK | 1 | | 1 (0)| 00:00:01 |
--------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("EVENT_PK"='zjmtzhjrth')
Note
-----
- automatic DOP: Computed Degree of Parallelism is 1 because of parallel threshold
Execution plan with hint /*+parallel(2) */ where DoP works fine
PLAN_TABLE_OUTPUT
---------------
Plan hash value: 2851389777
----------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | TQ |IN-OUT| PQ Distrib |
---------------
| 0 | SELECT STATEMENT | | 1 | 674 | 2 (0)| 00:00:01 | | | |
| 1 | PX COORDINATOR | | | | | | | | |
| 2 | PX SEND QC (RANDOM) | :TQ10001 | 1 | 674 | 2 (0)| 00:00:01 | Q1,01 | P->S | QC (RAND) |
| 3 | TABLE ACCESS BY INDEX ROWID | EVENT | 1 | 674 | 2 (0)| 00:00:01 | Q1,01 | PCWP | |
| 4 | BUFFER SORT | | | | | | Q1,01 | PCWC | |
| 5 | PX RECEIVE | | 1 | | 1 (0)| 00:00:01 | Q1,01 | PCWP | |
| 6 | PX SEND HASH (BLOCK ADDRESS)| :TQ10000 | 1 | | 1 (0)| 00:00:01 | Q1,00 | S->P | HASH (BLOCK|
| 7 | PX SELECTOR | | | | | | Q1,00 | SCWC | |
|* 8 | INDEX UNIQUE SCAN | EVENT_PK | 1 | | 1 (0)| 00:00:01 | Q1,00 | SCWP | |
--------------------
Predicate Information (identified by operation id):
---------------------------------------------------
8 - access("EVENT_PK"='zjmtzhjrth')
Note
-----
- Degree of Parallelism is 2 because of hint
Then I executed the following statements
alter system set parallel_degree_policy=MANUAL;
alter table event parallel 2;
But when I execute the statement without the hint, it doesn't use parallelism. It doesn't even give me the Note about the DoP in the execution plan.
PLAN_TABLE_OUTPUT
----------------
Plan hash value: 2671887276
-----------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 674 | 2 (0)| 00:00:01 |
| 1 | TABLE ACCESS BY INDEX ROWID| EVENT | 1 | 674 | 2 (0)| 00:00:01 |
|* 2 | INDEX UNIQUE SCAN | EVENT_PK | 1 | | 1 (0)| 00:00:01 |
-------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("EVENT_PK"='zjmtzhjrth')
Can anyone tell my why this is not working?
Regarding the questions in the comments:
PARALLEL_DEGREE_LIMIT=CPU
When I set PARALLEL_DEGREE_POLICY back to AUTO it gives me the note again:
Note
-----
- automatic DOP: Computed Degree of Parallelism is 1 because of parallel threshold
The statement I issued for my tests is
select * from event where event_pk = 'swdfklwe';
Following Cyrille's comment I tried every combination of selected columns and columns in the where clause. The statement just won't use DoP 2 when an index unique scan is used.
select event_pk, result form event where event_pk = 'swdfklwe'
select event_pk form event where event_pk = 'swdfklwe'
select event_pk, result form event where event_pk = 'swdfklwe' and result = 0
select event_pk form event where event_pk = 'swdfklwe' and result = 0
Parallel execution is for speeding up queries which traverse a large number of records. It divided the total set of records to be searched into smaller sets and processes multiple sets concurrently. This trades off increased consumption of system resource - primarily CPU - for a reduced total response time.
Your table has a unique index on the searched column. So there can be only one record which matches 'EVENT_PK"='zjmtzhjrth'. There is no way parallelism can make that faster.
The optimizer has chosen the most efficient access path to retrieve one row. Be happy that it has.
Why wouldn't
It work like expected on my side:
SQL> create table t1 (id number);
Table created.
SQL> alter table t1 parallel 2;
Table altered.
SQL> explain plan for select * from t1;
Explained.
SQL> #?/rdbms/admin/utlxpls
PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Plan hash value: 2494645258
---------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 13 | 2 (0)| 00:00:01 |
| 1 | PX COORDINATOR | | | | | |
| 2 | PX SEND QC (RANDOM)| :TQ10000 | 1 | 13 | 2 (0)| 00:00:01 |
| 3 | PX BLOCK ITERATOR | | 1 | 13 | 2 (0)| 00:00:01 |
| 4 | TABLE ACCESS FULL| T1 | 1 | 13 | 2 (0)| 00:00:01 |
---------------------------------------------------------------------------------
Note
-----
- dynamic statistics used: dynamic sampling (level=2)
- Degree of Parallelism is 2 because of table property
and here are the parameters I have (all defaults)
SQL> show parameter parallel
NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
containers_parallel_degree integer 65535
fast_start_parallel_rollback string LOW
parallel_adaptive_multi_user boolean FALSE
parallel_degree_limit string CPU
parallel_degree_policy string MANUAL
parallel_execution_message_size integer 16384
parallel_force_local boolean FALSE
parallel_instance_group string
parallel_max_servers integer 40
parallel_min_percent integer 0
parallel_min_servers integer 4
NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
parallel_min_time_threshold string AUTO
parallel_servers_target integer 16
parallel_threads_per_cpu integer 2
recovery_parallelism integer 0
SQL>

Resources