I can reproduce the following behavior both with Oracle 11 (see SQL Fiddle) and Oracle 12.
CREATE TYPE my_tab IS TABLE OF NUMBER(3);
CREATE TABLE test AS SELECT ROWNUM AS id FROM dual CONNECT BY ROWNUM <= 1000;
CREATE UNIQUE INDEX idx_test ON test( id );
CREATE VIEW my_view AS
SELECT id, COUNT(1) OVER ( PARTITION BY id ) AS cnt
FROM test;
The following case uses the index as expected:
SELECT * FROM my_view
WHERE id IN ( 1, 2 );
---------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 2 | 52 | 2 (0)| 00:00:01 |
| 1 | VIEW | MY_VIEW | 2 | 52 | 2 (0)| 00:00:01 |
| 2 | WINDOW BUFFER | | 2 | 8 | 2 (0)| 00:00:01 |
| 3 | INLIST ITERATOR | | | | | |
|* 4 | INDEX UNIQUE SCAN| IDX_TEST | 2 | 8 | 2 (0)| 00:00:01 |
---------------------------------------------------------------------------------
The following case does not use the index even though the cardinality hint is provided:
SELECT * FROM my_view
WHERE id IN ( SELECT /*+ CARDINALITY( tab 2 ) */ COLUMN_VALUE
FROM TABLE( NEW my_tab( 1, 2 ) ) tab );
--------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 28 | 33 (4)| 00:00:01 |
|* 1 | HASH JOIN RIGHT SEMI | | 1 | 28 | 33 (4)| 00:00:01 |
| 2 | COLLECTION ITERATOR CONSTRUCTOR FETCH| | 2 | 4 | 29 (0)| 00:00:01 |
| 3 | VIEW | MY_VIEW | 1000 | 26000 | 4 (25)| 00:00:01 |
| 4 | WINDOW SORT | | 1000 | 4000 | 4 (25)| 00:00:01 |
| 5 | TABLE ACCESS FULL | TEST | 1000 | 4000 | 3 (0)| 00:00:01 |
--------------------------------------------------------------------------------------------------
Edit:
Using an inline view and a JOIN instead of IN uses a similar plan:
SELECT /*+ CARDINALITY( tab, 2 ) */ *
FROM ( SELECT id, COUNT(1) OVER ( PARTITION BY id ) AS cnt FROM test ) t
JOIN TABLE( NEW my_tab( 1, 2 ) ) tab ON ( tab.COLUMN_VALUE = t.id );
-----------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 2 | 56 | 33 (4)| 00:00:01 |
|* 1 | HASH JOIN | | 2 | 56 | 33 (4)| 00:00:01 |
| 2 | COLLECTION ITERATOR CONSTRUCTOR FETCH| | 2 | 4 | 29 (0)| 00:00:01 |
| 3 | VIEW | | 1000 | 26000 | 4 (25)| 00:00:01 |
| 4 | WINDOW SORT | | 1000 | 4000 | 4 (25)| 00:00:01 |
| 5 | TABLE ACCESS FULL | TEST | 1000 | 4000 | 3 (0)| 00:00:01 |
-----------------------------------------------------------------------------------------------
Replacing the analytic function by a LEFT JOIN with GROUP BY does not help either:
SELECT *
FROM ( SELECT t.id, s.cnt
FROM test t
LEFT JOIN ( SELECT id, COUNT(*) AS cnt
FROM test
GROUP BY id
) s ON ( s.id = t.id )
)
WHERE id IN ( SELECT /*+ CARDINALITY( tab 2 ) */ COLUMN_VALUE
FROM TABLE( NEW my_tab( 1, 2 ) ) tab );
-----------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 2 | 64 | 34 (6)| 00:00:01 |
|* 1 | HASH JOIN OUTER | | 2 | 64 | 34 (6)| 00:00:01 |
| 2 | NESTED LOOPS | | 2 | 12 | 30 (4)| 00:00:01 |
| 3 | SORT UNIQUE | | 2 | 4 | 29 (0)| 00:00:01 |
| 4 | COLLECTION ITERATOR CONSTRUCTOR FETCH| | 2 | 4 | 29 (0)| 00:00:01 |
|* 5 | INDEX UNIQUE SCAN | IDX_TEST | 1 | 4 | 0 (0)| 00:00:01 |
| 6 | VIEW | | 1000 | 26000 | 4 (25)| 00:00:01 |
| 7 | HASH GROUP BY | | 1000 | 4000 | 4 (25)| 00:00:01 |
| 8 | TABLE ACCESS FULL | TEST | 1000 | 4000 | 3 (0)| 00:00:01 |
-----------------------------------------------------------------------------------------------------
Replacing the PL/SQL Collection by a subselect does not seem to help either. The CARDINALITY hint is considered (the plan says 2 rows), but the index is still ignored.
SELECT *
FROM ( SELECT id, cnt FROM my_view )
WHERE id IN ( SELECT /*+ CARDINALITY( tab 2 ) */ id FROM test tab );
---------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 2 | 60 | 4 (25)| 00:00:01 |
| 1 | NESTED LOOPS | | 2 | 60 | 4 (25)| 00:00:01 |
| 2 | VIEW | MY_VIEW | 1000 | 26000 | 4 (25)| 00:00:01 |
| 3 | WINDOW SORT | | 1000 | 4000 | 4 (25)| 00:00:01 |
| 4 | TABLE ACCESS FULL| TEST | 1000 | 4000 | 3 (0)| 00:00:01 |
|* 5 | INDEX UNIQUE SCAN | IDX_TEST | 1 | 4 | 0 (0)| 00:00:01 |
---------------------------------------------------------------------------------
Adding WHERE tab.id <= 2 to the in-list-subquery uses the index, so the optimizer seems to "not take the CARDINALITY hint serious enough" when selecting from a view with analytic functions (or another subselect) and filtering by a list of values.
How can I make these queries use the index as expected?
I think the one problem might be that the optimizer refuses to merge a view (and consider any indexes on the underlying tables) when the outer query block contains PL/SQL functions (e.g. TABLE()).
If you manually expand the view and query the table directly, it can access the index fine:
SELECT id, COUNT(1) OVER ( PARTITION BY id ) AS cnt
FROM test
WHERE id IN ( SELECT COLUMN_VALUE
FROM TABLE( NEW my_tab( 1, 2 ) ) tab )
;
----------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 6 | 31 (4)| 00:00:01 |
| 1 | WINDOW SORT | | 1 | 6 | 31 (4)| 00:00:01 |
|* 2 | HASH JOIN SEMI | | 1 | 6 | 30 (0)| 00:00:01 |
| 3 | INDEX FULL SCAN | IDX_TEST | 1000 | 4000 | 1 (0)| 00:00:01 |
| 4 | COLLECTION ITERATOR CONSTRUCTOR FETCH| | 8168 | 16336 | 29 (0)| 00:00:01 |
----------------------------------------------------------------------------------------------------
I'm not sure if there's a way to override this behavior, or if it's a limitation in the optimizer. I tried moving the TABLE function to a CTE, but that doesn't seem to help.
Related
I have an Oracle 19c database table with "resources" that are organized hierarchically like a nested folder tree. The table contains around 2.5 million rows and the tree is up to 10 levels deep.
create table RESOURCES (
ID_ NUMBER(10) not null constraint PK_RESOURCES primary key,
FOLDERID_ NUMBER(10) constraint FK_PARENTFOLDER references RESOURCES
);
create index FOLDERIDINDEX on RESOURCES (FOLDERID_);
I'm using SQL recursive common table expressions (aka recursive subquery factoring) to find all descendants of some given resources.
In general, this works quite nicely, but if I try to get descendants of multiple folders with one query, some queries don't perform at all using Oracle. I'd like to understand why this is the case, and if there's some easy way to speed things up (query hint?, bugfix?, ...?)
For example, this statement does not return within 60 minutes(!):
WITH cte1 (id_) AS (SELECT id_ FROM Resources where id_ = 11
UNION ALL
SELECT r.id_ FROM Resources r, cte1 c WHERE r.folderId_ = c.id_),
cte2 (id_) AS (SELECT id_ FROM Resources where id_ = 808965
UNION ALL
SELECT r.id_ FROM Resources r, cte2 c WHERE r.folderId_ = c.id_)
SELECT count(*)
FROM Resources r
WHERE (r.folderId_ IN (SELECT * FROM cte1) OR r.folderId_ IN (SELECT * FROM cte2));
If I replace the two sub-selects in the last line with a UNION, it just takes a few seconds:
WITH cte1 (id_) AS (SELECT id_ FROM Resources where id_ = 11
UNION ALL
SELECT r.id_ FROM Resources r, cte1 c WHERE r.folderId_ = c.id_),
cte2 (id_) AS (SELECT id_ FROM Resources where id_ = 808965
UNION ALL
SELECT r.id_ FROM Resources r, cte2 c WHERE r.folderId_ = c.id_)
SELECT count(*)
FROM Resources r
WHERE (r.folderId_ IN (SELECT * FROM cte1 UNION SELECT * FROM cte2));
While that could already be the solution, it's a bit hard to change in my project, because SQL is auto-generated by code from application queries, and at that point not so easy to change. Also, application queries could be much more complex and such a replacement might not even be possible. These are just simplified examples. Maybe there's some other way to speed things up?
Interestingly, the slow query works without any performance problems on other databases like MySQL 8, PostgreSQL 13, SQL Server 2016 (with small syntax changes for the databases). It's just Oracle which seems to have a problem here.
This is the query plan for the first query, i.e. the slow one:
------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 5 | 54G (1)|591:38:12 |
| 1 | SORT AGGREGATE | | 1 | 5 | | |
|* 2 | FILTER | | | | | |
| 3 | TABLE ACCESS FULL | RESOURCES | 2410K| 11M| 9389 (1)| 00:00:01 |
|* 4 | VIEW | | 239K| 3046K| 23128 (1)| 00:00:01 |
| 5 | UNION ALL (RECURSIVE WITH) BREADTH FIRST| | | | | |
|* 6 | INDEX UNIQUE SCAN | PK_RESOURCES | 1 | 6 | 2 (0)| 00:00:01 |
|* 7 | HASH JOIN | | 239K| 5623K| 23126 (1)| 00:00:01 |
| 8 | RECURSIVE WITH PUMP | | | | | |
| 9 | BUFFER SORT (REUSE) | | | | | |
| 10 | TABLE ACCESS FULL | RESOURCES | 2410K| 25M| 9386 (1)| 00:00:01 |
|* 11 | VIEW | | 239K| 3046K| 23128 (1)| 00:00:01 |
| 12 | UNION ALL (RECURSIVE WITH) BREADTH FIRST| | | | | |
|* 13 | INDEX UNIQUE SCAN | PK_RESOURCES | 1 | 6 | 2 (0)| 00:00:01 |
|* 14 | HASH JOIN | | 239K| 5623K| 23126 (1)| 00:00:01 |
| 15 | RECURSIVE WITH PUMP | | | | | |
| 16 | BUFFER SORT (REUSE) | | | | | |
| 17 | TABLE ACCESS FULL | RESOURCES | 2410K| 25M| 9386 (1)| 00:00:01 |
------------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
" 2 - filter( EXISTS (SELECT 0 FROM ""CTE1"" ""CTE1"" WHERE ""CTE1"".""ID_""=:B1) OR EXISTS (SELECT 0 "
" FROM ""CTE2"" ""CTE2"" WHERE ""CTE2"".""ID_""=:B2))"
" 4 - filter(""CTE1"".""ID_""=:B1)"
" 6 - access(""ID_""=11)"
" 7 - access(""R"".""FOLDERID_""=""C"".""ID_"")"
" 11 - filter(""CTE2"".""ID_""=:B1)"
" 13 - access(""ID_""=808965)"
" 14 - access(""R"".""FOLDERID_""=""C"".""ID_"")"
For comparison, the faster query using a UNION seems to use a better plan:
------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time |
------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 18 | | 55733 (1)| 00:00:03 |
| 1 | SORT AGGREGATE | | 1 | 18 | | | |
|* 2 | HASH JOIN | | 2806K| 48M| 11M| 55733 (1)| 00:00:03 |
| 3 | VIEW | VW_NSO_1 | 479K| 6092K| | 50820 (1)| 00:00:02 |
| 4 | SORT UNIQUE | | 479K| 6092K| 9424K| 50820 (1)| 00:00:02 |
| 5 | UNION-ALL | | | | | | |
| 6 | VIEW | | 239K| 3046K| | 23128 (1)| 00:00:01 |
| 7 | UNION ALL (RECURSIVE WITH) BREADTH FIRST| | | | | | |
|* 8 | INDEX UNIQUE SCAN | PK_RESOURCES | 1 | 6 | | 2 (0)| 00:00:01 |
|* 9 | HASH JOIN | | 239K| 5623K| | 23126 (1)| 00:00:01 |
| 10 | RECURSIVE WITH PUMP | | | | | | |
| 11 | BUFFER SORT (REUSE) | | | | | | |
| 12 | TABLE ACCESS FULL | RESOURCES | 2410K| 25M| | 9386 (1)| 00:00:01 |
| 13 | VIEW | | 239K| 3046K| | 23128 (1)| 00:00:01 |
| 14 | UNION ALL (RECURSIVE WITH) BREADTH FIRST| | | | | | |
|* 15 | INDEX UNIQUE SCAN | PK_RESOURCES | 1 | 6 | | 2 (0)| 00:00:01 |
|* 16 | HASH JOIN | | 239K| 5623K| | 23126 (1)| 00:00:01 |
| 17 | RECURSIVE WITH PUMP | | | | | | |
| 18 | BUFFER SORT (REUSE) | | | | | | |
| 19 | TABLE ACCESS FULL | RESOURCES | 2410K| 25M| | 9386 (1)| 00:00:01 |
| 20 | INDEX FAST FULL SCAN | FOLDERIDINDEX | 2410K| 11M| | 2392 (1)| 00:00:01 |
------------------------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
" 2 - access(""R"".""FOLDERID_""=""ID_"")"
" 8 - access(""ID_""=11)"
" 9 - access(""R"".""FOLDERID_""=""C"".""ID_"")"
" 15 - access(""ID_""=808965)"
" 16 - access(""R"".""FOLDERID_""=""C"".""ID_"")"
I have query something like
WITH
str_table as (
SELECT stringtext, stringnumberid
FROM STRING_TABLE
WHERE LANGID IN (23,62)
),
data as (
select *
from employee emp
left outer join str_table st on emp.nameid = st.stringnumberid
)
select * from data
I know With clause will work in this manner
Step 1 : The SQL Query within the with clause is executed at first step.
Step 2 : The output of the SQL query is stored into temporary relation of with clause.
Step 3 : The Main query is executed with temporary relation produced at the last stage.
Now I want to ask whether the indexes created on the actual STRING_TABLE are going to help in temporary str_table produce by the With clause? I want to ask whether the indexes also have impact on str_table or not?
Oracle will not process CTE one by one. It will analyze the SQL as a whole. Your SQL is most likely the same as following in the eye of Oracle optimizer
select emp.*
from employee emp left outer join STRING_TABLE st
on emp.nameid = st.stringnumberid
where st.LANGID IN (23,62);
Oracle can use index on STRING_TABLE. Whether it will depends on the table statistics. For example, if the table has few rows (say a few hundred), Oracle will likely not use index.
It depends.
First of all, with clause is not a temporary table. As documentation says:
Oracle Database optimizes the query by treating the query name as either an inline view or as a temporary table.
Optimizer decides to materialize with subquery if either you forse it to do so by using /*+materialize*/ hint inside the subquery or you reuse this with subquery more than once.
In the example below Oracle uses with clause as inline view and merges it within the main query:
explain plan for
with a as (
select
s.textid,
s.textvalue,
a.id,
a.other_column
from string_table s
join another_tab a
on s.textid = a.textid
where langid in (1)
)
select *
from big_table b
join a a_name
on b.name_textid = a_name.textid
and b.job_textid = a_name.id
| PLAN_TABLE_OUTPUT |
| :----------------------------------------------------------------------------------- |
| Plan hash value: 1854049435 |
| |
| ------------------------------------------------------------------------------------ |
| | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | |
| ------------------------------------------------------------------------------------ |
| | 0 | SELECT STATEMENT | | 1 | 1147 | 74 (0)| 00:00:01 | |
| |* 1 | HASH JOIN | | 1 | 1147 | 74 (0)| 00:00:01 | |
| | 2 | TABLE ACCESS FULL | ANOTHER_TAB | 39 | 3042 | 3 (0)| 00:00:01 | |
| |* 3 | HASH JOIN | | 31 | 33139 | 71 (0)| 00:00:01 | |
| | 4 | TABLE ACCESS FULL| BIG_TABLE | 19 | 10279 | 3 (0)| 00:00:01 | |
| |* 5 | TABLE ACCESS FULL| STRING_TABLE | 1143 | 589K| 68 (0)| 00:00:01 | |
| ------------------------------------------------------------------------------------ |
But depending on the statistics and hints it may evaluate subquery first and then add it to the main query:
explain plan for
with a as (
select
s.textid,
s.textvalue,
a.id,
a.other_column
from string_table s
join another_tab a
on s.textid = a.textid
where langid in (1)
)
select /*+NO_MERGE(a_name)*/ *
from big_table b
join a a_name
on b.name_textid = a_name.textid
and b.job_textid = a_name.id
| PLAN_TABLE_OUTPUT |
| :------------------------------------------------------------------------------------ |
| Plan hash value: 4105667421 |
| |
| ------------------------------------------------------------------------------------- |
| | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | |
| ------------------------------------------------------------------------------------- |
| | 0 | SELECT STATEMENT | | 101 | 110K| 74 (0)| 00:00:01 | |
| |* 1 | HASH JOIN | | 101 | 110K| 74 (0)| 00:00:01 | |
| | 2 | TABLE ACCESS FULL | BIG_TABLE | 19 | 10279 | 3 (0)| 00:00:01 | |
| | 3 | VIEW | | 64 | 37120 | 71 (0)| 00:00:01 | |
| |* 4 | HASH JOIN | | 64 | 38784 | 71 (0)| 00:00:01 | |
| | 5 | TABLE ACCESS FULL| ANOTHER_TAB | 39 | 3042 | 3 (0)| 00:00:01 | |
| |* 6 | TABLE ACCESS FULL| STRING_TABLE | 1143 | 589K| 68 (0)| 00:00:01 | |
| ------------------------------------------------------------------------------------- |
When you use with subquery twice, optimizer decides to materialize it:
explain plan for
with a as (
select
s.textid,
s.textvalue
from string_table s
where langid in (1)
)
select *
from big_table b
join a a_name
on b.name_textid = a_name.textid
join a a_job
on b.job_textid = a_job.textid
| PLAN_TABLE_OUTPUT |
| :--------------------------------------------------------------------------------------------------------------------- |
| Plan hash value: 1371454296 |
| |
| ---------------------------------------------------------------------------------------------------------------------- |
| | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | |
| ---------------------------------------------------------------------------------------------------------------------- |
| | 0 | SELECT STATEMENT | | 63 | 98973 | 67 (0)| 00:00:01 | |
| | 1 | TEMP TABLE TRANSFORMATION | | | | | | |
| | 2 | LOAD AS SELECT (CURSOR DURATION MEMORY)| SYS_TEMP_0FD9D7224_469C01 | | | | | |
| | 3 | TABLE ACCESS BY INDEX ROWID BATCHED | STRING_TABLE | 999 | 515K| 22 (0)| 00:00:01 | |
| |* 4 | INDEX RANGE SCAN | IX | 999 | | 4 (0)| 00:00:01 | |
| |* 5 | HASH JOIN | | 63 | 98973 | 45 (0)| 00:00:01 | |
| |* 6 | HASH JOIN | | 35 | 36960 | 24 (0)| 00:00:01 | |
| | 7 | TABLE ACCESS FULL | BIG_TABLE | 19 | 10279 | 3 (0)| 00:00:01 | |
| | 8 | VIEW | | 999 | 502K| 21 (0)| 00:00:01 | |
| | 9 | TABLE ACCESS FULL | SYS_TEMP_0FD9D7224_469C01 | 999 | 502K| 21 (0)| 00:00:01 | |
| | 10 | VIEW | | 999 | 502K| 21 (0)| 00:00:01 | |
| | 11 | TABLE ACCESS FULL | SYS_TEMP_0FD9D7224_469C01 | 999 | 502K| 21 (0)| 00:00:01 | |
| ---------------------------------------------------------------------------------------------------------------------- |
So when there are some indexes on tables inside with subquery they may be used in all above cases: before materialization, when subquery is not merged and when subquery is merged and some idexes provide better query plan on merged subquery (even when those indexes are not used when you execute subquery alone).
What about idexes: if they provide high selectivity (i.e. number of rows retrieved by index is small compared to the overall number of rows), then Oracle will consider to use it. Note, that index access has two steps: read index blocks and then read table blocks that contain rowids found by index. If table size is not much bigger than index size, then Oracle may use table scan instead of index scan even for quite selective predicate (because of doubled IO).
In the below example I've used "small" texts (100 chars) and big_table table of 20 rows and this index for text table:
create index ix
on string_table(langid, textid)
Optimizer decides to use index range scan and read only blocks of the first level (first column of the index):
explain plan for
with a as (
select
s.textid,
s.textvalue
from string_table s
where langid in (1)
)
select *
from big_table b
join a a_name
on b.name_textid = a_name.textid
| PLAN_TABLE_OUTPUT |
| :---------------------------------------------------------------------------------------------------- |
| Plan hash value: 1660330381 |
| |
| ----------------------------------------------------------------------------------------------------- |
| | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | |
| ----------------------------------------------------------------------------------------------------- |
| | 0 | SELECT STATEMENT | | 29 | 31001 | 26 (0)| 00:00:01 | |
| |* 1 | HASH JOIN | | 29 | 31001 | 26 (0)| 00:00:01 | |
| | 2 | TABLE ACCESS FULL | BIG_TABLE | 19 | 10279 | 3 (0)| 00:00:01 | |
| | 3 | TABLE ACCESS BY INDEX ROWID BATCHED| STRING_TABLE | 999 | 515K| 23 (0)| 00:00:01 | |
| |* 4 | INDEX RANGE SCAN | IX | 999 | | 4 (0)| 00:00:01 | |
| ----------------------------------------------------------------------------------------------------- |
| |
| Predicate Information (identified by operation id): |
| --------------------------------------------------- |
| |
| 1 - access("B"."NAME_TEXTID"="S"."TEXTID") |
| 4 - access("LANGID"=1) | |
But when we reduce the number of rows in big_table, it uses both the columns for index scan:
delete from big_table
where id > 4
explain plan for
with a as (
select
s.textid,
s.textvalue
from string_table s
where langid in (1)
)
select *
from big_table b
join a a_name
on b.name_textid = a_name.textid
| PLAN_TABLE_OUTPUT |
| :-------------------------------------------------------------------------------------------- |
| Plan hash value: 1766926914 |
| |
| --------------------------------------------------------------------------------------------- |
| | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | |
| --------------------------------------------------------------------------------------------- |
| | 0 | SELECT STATEMENT | | 6 | 18216 | 11 (0)| 00:00:01 | |
| | 1 | NESTED LOOPS | | 6 | 18216 | 11 (0)| 00:00:01 | |
| | 2 | NESTED LOOPS | | 6 | 18216 | 11 (0)| 00:00:01 | |
| | 3 | TABLE ACCESS FULL | BIG_TABLE | 4 | 4032 | 3 (0)| 00:00:01 | |
| |* 4 | INDEX RANGE SCAN | IX | 1 | | 1 (0)| 00:00:01 | |
| | 5 | TABLE ACCESS BY INDEX ROWID| STRING_TABLE | 2 | 4056 | 2 (0)| 00:00:01 | |
| --------------------------------------------------------------------------------------------- |
| |
| Predicate Information (identified by operation id): |
| --------------------------------------------------- |
| |
| 4 - access("LANGID"=1 AND "B"."NAME_TEXTID"="S"."TEXTID") |
| |
You may check above code snippets in the db<>fiddle.
This particular query was working fine till we were in 11i but as soon as we switched to 12c the query has been doing really bad.
I tried to remove the hint and it seems to be doing little better but our DBA wants us to remove all the multiple subqueries which cause serialization of the various sub-steps. I am at a loss on how to change it.
Any pointers on what I can do or start with?
WITH
-- Get all active accounts that have not been suspended and the suspension rules
-- apply for the account
TT1 AS (SELECT
pl.employer_id,
ma.employee_id,
ma.member_account_id,
pl.plan_id
FROM sa_plans pl,
sa_accounts ma
WHERE TRUNC(SYSDATE) BETWEEN pl.start_date AND NVL(pl.end_date, TRUNC(SYSDATE))
AND pl.employer_id = 23
AND pl.plan_type_id NOT IN (4
,1)
AND pl.plan_id = ma.plan_id
AND sa_mc_info.fn_ee_susp_exempt(ma.employee_id, DECODE(pl.plan_type_id, 1, 6 ,5 )) = 0
AND p_outstanding_threshold_perc > 0
AND NVL(ma.auto_suspension_flag, 0) = 0
AND TRUNC(SYSDATE) BETWEEN ma.account_effective_date AND NVL(ma.account_end_date, TRUNC(SYSDATE))
AND ma.account_effective_date != NVL(ma.account_end_date, ma.account_effective_date + 1)
),
-- Get all accounts that were active during the plan year for the current accounts
-- and the outstanding transactions
TT2 AS (SELECT /*+ MATERIALIZE */
ma1.member_account_id,
ma1.plan_id,
ma1.employee_id ,
wwsa.sa_mc_info.fn_acnt_unverified_txns(ma1.member_account_id) outstd_amount
FROM sa_accounts ma1,
TT1 ma
WHERE ma1.account_effective_date != NVL(ma1.account_end_date, ma1.account_effective_date + 1)
AND ma1.plan_id = ma.plan_id
AND ma1.employee_id = ma.employee_id
AND ma1.member_account_id = TT1.member_account_id),
-- Sum the outstanding transactions for the EE and plan
TT3 AS (SELECT /*+ MATERIALIZE ORDERED */
TT1.member_account_id,
SUM(TT2.outstd_amount) outstd_amount
FROM TT1,
TT2
WHERE TT1.employee_id = TT2.employee_id
AND TT1.plan_id = TT2.plan_id
GROUP BY TT1.member_account_id
HAVING SUM(TT2.outstd_amount) > 0),
-- Get the current account balance for accounts with outstanding transactions
TT4 AS (SELECT /*+ MATERIALIZE ORDERED */
TT1.*,
sa_bal_info.fn_account_balance(TT1.member_account_id) balance,
TT3.outstd_amount
FROM TT1,
TT3
WHERE TT1.member_account_id = TT3.member_account_id),
-- Get the list of accounts that need to be suspended
TT5 as (SELECT /*+ MATERIALIZE */
member_account_id,
employee_id
FROM TT4
WHERE outstd_amount > balance * p_outstanding_threshold_perc
AND outstd_amount > NVL(p_minimum_threshold_amount, 0) )
SELECT *
FROM TT5
ORDER BY employee_id;
This is the explain plan
-----------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 26 | 8622 (1)| 00:00:01 |
| 1 | TEMP TABLE TRANSFORMATION | | | | | |
| 2 | LOAD AS SELECT | SYS_TEMP_0FD9D666F_DCBC1560 | | | | |
| 3 | NESTED LOOPS | | 4 | 252 | 8600 (1)| 00:00:01 |
| 4 | NESTED LOOPS | | 1707 | 252 | 8600 (1)| 00:00:01 |
|* 5 | TABLE ACCESS BY INDEX ROWID BATCHED| SA_PLANS | 3 | 84 | 8452 (1)| 00:00:01 |
| 6 | INDEX FULL SCAN | SA_PLANS_PK | 19218 | | 75 (2)| 00:00:01 |
|* 7 | INDEX RANGE SCAN | SA_ACCOUNTS_N03 | 569 | | 2 (0)| 00:00:01 |
|* 8 | TABLE ACCESS BY INDEX ROWID | SA_ACCOUNTS | 1 | 35 | 67 (0)| 00:00:01 |
| 9 | LOAD AS SELECT | SYS_TEMP_0FD9D6670_DCBC1560 | | | | |
| 10 | NESTED LOOPS | | 3 | 177 | 8 (0)| 00:00:01 |
| 11 | NESTED LOOPS | | 3 | 177 | 8 (0)| 00:00:01 |
| 12 | VIEW | | 1 | 26 | 2 (0)| 00:00:01 |
| 13 | TABLE ACCESS FULL | SYS_TEMP_0FD9D666F_DCBC1560 | 1 | 22 | 2 (0)| 00:00:01 |
|* 14 | INDEX RANGE SCAN | SA_ACCOUNTS_N01 | 3 | | 2 (0)| 00:00:01 |
|* 15 | TABLE ACCESS BY INDEX ROWID | SA_ACCOUNTS | 3 | 99 | 6 (0)| 00:00:01 |
| 16 | LOAD AS SELECT | SYS_TEMP_0FD9D6671_DCBC1560 | | | | |
|* 17 | FILTER | | | | | |
| 18 | HASH GROUP BY | | 1 | 41 | 5 (20)| 00:00:01 |
|* 19 | HASH JOIN | | 1 | 41 | 4 (0)| 00:00:01 |
| 20 | VIEW | | 1 | 17 | 2 (0)| 00:00:01 |
| 21 | TABLE ACCESS FULL | SYS_TEMP_0FD9D666F_DCBC1560 | 1 | 22 | 2 (0)| 00:00:01 |
| 22 | VIEW | | 1 | 24 | 2 (0)| 00:00:01 |
| 23 | TABLE ACCESS FULL | SYS_TEMP_0FD9D6670_DCBC1560 | 1 | 30 | 2 (0)| 00:00:01 |
| 24 | LOAD AS SELECT | SYS_TEMP_0FD9D6672_DCBC1560 | | | | |
|* 25 | HASH JOIN | | 1 | 78 | 4 (0)| 00:00:01 |
| 26 | VIEW | | 1 | 52 | 2 (0)| 00:00:01 |
| 27 | TABLE ACCESS FULL | SYS_TEMP_0FD9D666F_DCBC1560 | 1 | 22 | 2 (0)| 00:00:01 |
| 28 | VIEW | | 1 | 26 | 2 (0)| 00:00:01 |
| 29 | TABLE ACCESS FULL | SYS_TEMP_0FD9D6671_DCBC1560 | 1 | 19 | 2 (0)| 00:00:01 |
| 30 | LOAD AS SELECT | SYS_TEMP_0FD9D6673_DCBC1560 | | | | |
|* 31 | VIEW | | 1 | 52 | 2 (0)| 00:00:01 |
| 32 | TABLE ACCESS FULL | SYS_TEMP_0FD9D6672_DCBC1560 | 1 | 48 | 2 (0)| 00:00:01 |
| 33 | SORT ORDER BY | | 1 | 26 | 3 (34)| 00:00:01 |
| 34 | VIEW | | 1 | 26 | 2 (0)| 00:00:01 |
| 35 | TABLE ACCESS FULL | SYS_TEMP_0FD9D6673_DCBC1560 | 1 | 12 | 2 (0)| 00:00:01 |
-----------------------------------------------------------------------------------------------------------------------
Try to execute TT1 and evaluate execution time. If it's OK, then continue with TT1+TT2 and so on. You need to understand where problem is before truing to solve it.
I would assume your problem is directed related to a bad plan.
So, as you said, it performed just fine on 11g database so there basic two things you could do to try to solve your problem in a short term manner:
Check the optimizer_features_enable parameter on 12c version. If it is set to 12.* or an upper version, try to downgrade the session who is running the query to the 11.* version of your old database.
Assuming you have Tuning Pack (licensing issues - take care) enabled into your production environment, you could use SQL Profiles. How? You could manually copy your the plan from the 11g and import it to the 12c database. Take a look at dbms_sqltune.import_sql_profile.
https://oracle-randolf.blogspot.com.br/2009/03/plan-stability-in-10g-using-existing.html
As I said before, these are short term action. It would be better to evaluate your SQL.
Oracle query.
The following query takes some time to execute:
SELECT GCCOM_ACCOUNT_CONTRACT.ID_ACCOUNT_CONTRACT
FROM GCCOM_ACCOUNT_CONTRACT, GCCOM_ACCOUNT_GROUP
WHERE
GCCOM_ACCOUNT_CONTRACT.ID_ACCOUNT_GROUP = GCCOM_ACCOUNT_GROUP.ID_ACCOUNT_GROUP (+) AND
EXISTS (SELECT 1 FROM GCCOM_CONTRACTED_SERVICE
WHERE ID_ACCOUNT_CONTRACT = GCCOM_ACCOUNT_CONTRACT.ID_ACCOUNT_CONTRACT AND
STATUS = 'ESTSC00002' AND
DROP_DATE IS NULL ) AND
EXISTS (SELECT 1 FROM GCCOM_SEND_SERVICE
WHERE (ID_ACCOUNT_CONTRACT = GCCOM_ACCOUNT_CONTRACT.ID_ACCOUNT_CONTRACT OR
ID_ACCOUNT_GROUP = GCCOM_ACCOUNT_GROUP.ID_ACCOUNT_GROUP)
) AND
(( GCCOM_ACCOUNT_CONTRACT.ACCOUNT_CODE between 200000001 AND 900468243))
ORDER BY
GCCOM_ACCOUNT_CONTRACT.ID_COMPANY,
GCCOM_ACCOUNT_GROUP.ID_ACCOUNT_GROUP,
GCCOM_ACCOUNT_CONTRACT.ACCOUNT_CODE
The explain plan shows as follows:
PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------------------------------------------------------------------
Plan hash value: 391653930
------------------------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time | Pstart| Pstop |
------------------------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 45 | | 570K (1)| 01:54:06 | | |
| 1 | SORT ORDER BY | | 1 | 45 | 12M| 570K (1)| 01:54:06 | | |
|* 2 | FILTER | | | | | | | | |
| 3 | NESTED LOOPS OUTER | | 255K| 10M| | 17381 (1)| 00:03:29 | | |
|* 4 | HASH JOIN RIGHT SEMI | | 255K| 9979K| 8648K| 17380 (1)| 00:03:29 | | |
| 5 | PARTITION HASH ALL | | 260K| 5592K| | 7175 (1)| 00:01:27 | 1 | 16 |
|* 6 | TABLE ACCESS FULL | GCCOM_CONTRACTED_SERVICE | 260K| 5592K| | 7175 (1)| 00:01:27 | 1 | 16 |
|* 7 | TABLE ACCESS FULL | GCCOM_ACCOUNT_CONTRACT | 810K| 13M| | 8627 (1)| 00:01:44 | | |
|* 8 | INDEX UNIQUE SCAN | PK_GCCOM_ACCOUNT_GROUP | 1 | 5 | | 1 (0)| 00:00:01 | | |
| 9 | CONCATENATION | | | | | | | | |
| 10 | TABLE ACCESS BY GLOBAL INDEX ROWID| GCCOM_SEND_SERVICE | 1 | 7 | | 1 (0)| 00:00:01 | ROWID | ROWID |
|* 11 | INDEX RANGE SCAN | IDX_GCCOMSENDSERVICE_27 | 1 | | | 1 (0)| 00:00:01 | | |
|* 12 | TABLE ACCESS BY GLOBAL INDEX ROWID| GCCOM_SEND_SERVICE | 2 | 14 | | 1 (0)| 00:00:01 | ROWID | ROWID |
|* 13 | INDEX RANGE SCAN | IDX_GCCOMSENDSERVICE_04 | 1 | | | 1 (0)| 00:00:01 | | |
------------------------------------------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------------------------------------------------------------------
---------------------------------------------------
2 - filter( EXISTS (SELECT 0 FROM "GCCOM_SEND_SERVICE" "GCCOM_SEND_SERVICE"<not feasible>)
4 - access("ID_ACCOUNT_CONTRACT"="GCCOM_ACCOUNT_CONTRACT"."ID_ACCOUNT_CONTRACT")
6 - filter("DROP_DATE" IS NULL AND "STATUS"='ESTSC00002')
7 - filter("GCCOM_ACCOUNT_CONTRACT"."ACCOUNT_CODE">=200000001 AND "GCCOM_ACCOUNT_CONTRACT"."ACCOUNT_CODE"<=900468243)
8 - access("GCCOM_ACCOUNT_CONTRACT"."ID_ACCOUNT_GROUP"="GCCOM_ACCOUNT_GROUP"."ID_ACCOUNT_GROUP"(+))
11 - access("ID_ACCOUNT_CONTRACT"=:B1)
12 - filter(LNNVL("ID_ACCOUNT_CONTRACT"=:B1))
13 - access("ID_ACCOUNT_GROUP"=:B1)
32 filas seleccionadas.
The average cardinality of table looks as follows:
select count(*) from GCCOM_ACCOUNT_CONTRACT >> rows: 810412
select avg(distinct ID_ACCOUNT_GROUP) from GCCOM_ACCOUNT_CONTRACT >> cardinality: 87173
Highly indexed.
Tried many things, but useless.
Any idea ?
After doing a select, I am doing a self-join and a distinct on the data. I am not sure if there is a better way to write this query. Could you suggest?
Below is the Query, and the explain plan output.
WITH data
AS ( SELECT san.S1FK_C_ID,
san.S1FK_CF_ID,
san.S1FK_A_ID,
san.S1FK_A_TYPE,
san.S1FK_S_TYPE,
san.S1FK_B_FUNC,
AU.AS1FK_A_ID,
AU.AS1FK_B_FUNC,
AU.AS1FK_S_TYPE,
AU.AS1FK_T_CODE,
A2FK_B_ID,
A2FK_NBR,
A2FK_C_CD,
A1FK_B_ID,
A1FK_NBR,
A1FK_C_CD,
AU.R_NAME,
IND
FROM N_RULE cnr,
SAN san,
ACCT_USG AU
WHERE AU.R_NAME = CNR.R_NAME
AND AU.A2FK_C_CD = CNR.C_CD
AND AU.AS1FK_B_FUNC = CNR.B_FUNC
AND AU.AS1FK_A_ID = san.AS1FK_ID || ''
AND AU.AS1FK_B_FUNC = san.AS1FK_B_FUNC
AND AU.AS1FK_S_TYPE = san.AS1FK_S_TYPE
AND AU.AS1FK_T_CODE = san.AS1FK_A_TYPE
AND CNR.FM_CODE = 'SP'
AND san.STATUS = 'A'
AND san.AS1FK_B_FUNC = CNR.B_FUNC
ORDER BY AU.AS1FK_A_ID)
SELECT DISTINCT A.A2FK_C_CD AS C_CD,
-- secondary id's
B.S1FK_C_ID AS C_ID_2,
B.S1FK_CF_ID AS CF_ID_2,
B.S1FK_A_ID AS A_ID_2,
B.S1FK_B_FUNC AS B_FUNC_2,
B.S1FK_S_TYPE AS S_TYPE_2,
B.S1FK_A_TYPE AS A_TYPE_2,
--primary id's
A.S1FK_A_ID AS A_ID_1,
A.S1FK_B_FUNC AS B_FUNC_1,
A.S1FK_S_TYPE AS S_TYPE_1,
A.S1FK_A_TYPE AS A_TYPE_1
FROM data A, --A is the set of primary and standalone id's
data B
--B is the primary and secondary id's
WHERE
B .R_NAME = A.R_NAME
--as join
AND B.AS1FK_B_FUNC = A.AS1FK_B_FUNC
AND B.AS1FK_S_TYPE = A.AS1FK_S_TYPE
AND B.AS1FK_T_CODE = A.AS1FK_T_CODE
--accts join
AND B.A2FK_NBR = A.A2FK_NBR
AND B.A2FK_B_ID = A.A2FK_B_ID
AND B.A2FK_C_CD = A.A2FK_C_CD
AND B.A1FK_NBR = A.A1FK_NBR
AND B.A1FK_B_ID = A.A1FK_B_ID
AND B.A1FK_C_CD = A.A1FK_C_CD
--s fk join
AND B.S1FK_C_ID = A.S1FK_C_ID
AND B.S1FK_CF_ID = A.S1FK_CF_ID
AND B.S1FK_S_TYPE = A.S1FK_S_TYPE
AND B.S1FK_B_FUNC = A.S1FK_B_FUNC
AND B.S1FK_A_TYPE = A.S1FK_A_TYPE
AND ( --join secondary ( N ) and primary id's ( Y ) to primary id's
( 'Y' = A.IND
AND B.IND IN ('N', 'Y') )
OR --join standalone ( ' ' ) id's to themselves
( ' ' = B.IND
AND ' ' = A.IND )
AND B.AS1FK_A_ID = A.AS1FK_A_ID
);
Plan:
PLAN_TABLE_OUTPUT
Plan hash value: 3120521488
----------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 450 | 18 (17)| 00:00:01 |
| 1 | TEMP TABLE TRANSFORMATION | | | | | |
| 2 | LOAD AS SELECT | SYS_TEMP_0FD9D949F_E879D9BC | | | | |
| 3 | SORT ORDER BY | | 1 | 296 | 12 (9)| 00:00:01 |
| 4 | NESTED LOOPS | | 1 | 296 | 11 (0)| 00:00:01 |
| 5 | NESTED LOOPS | | 7412 | 1889K| 11 (0)| 00:00:01 |
|* 6 | TABLE ACCESS FULL | SAN | 255 | 20910 | 11 (0)| 00:00:01 |
| 7 | TABLE ACCESS BY INDEX ROWID| ACCT_USG | 29 | 5191 | 0 (0)| 00:00:01 |
|* 8 | INDEX RANGE SCAN | ACCT_USG_PK | 1 | | 0 (0)| 00:00:01 |
|* 9 | INDEX UNIQUE SCAN | N_RULE_PK | 1 | 35 | 0 (0)| 00:00:01 |
| 10 | HASH UNIQUE | | 1 | 450 | 6 (34)| 00:00:01 |
|* 11 | HASH JOIN | | 1 | 450 | 5 (20)| 00:00:01 |
| 12 | VIEW | | 1 | 225 | 2 (0)| 00:00:01 |
| 13 | TABLE ACCESS FULL | SYS_TEMP_0FD9D949F_E879D9BC | 1 | 225 | 2 (0)| 00:00:01 |
| 14 | VIEW | | 1 | 225 | 2 (0)| 00:00:01 |
| 15 | TABLE ACCESS FULL | SYS_TEMP_0FD9D949F_E879D9BC | 1 | 225 | 2 (0)| 00:00:01 |
----------------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
6 - filter("SAN"."EFF_STATUS_CODE"='A')
8 - access("AU"."AS1FK_A_ID"="SAN"."AS1FK_ID"||'' AND
"AU"."AS1FK_T_CODE"="SAN"."AS1FK_A_TYPE" AND
"AU"."AS1FK_S_TYPE"="SAN"."AS1FK_S_TYPE" AND
"AU"."AS1FK_B_FUNC"="SAN"."AS1FK_B_FUNC")
9 - access("CNR"."FM_CODE"='SP' AND "SAN"."S1FK_C_ID"="CNR"."CLRG_ORG_ID" AND
"SAN"."AS1FK_B_FUNC"="CNR"."B_F_CODE" AND "AU"."A2FK_C_CD"="CNR"."C_CD" AND
"AU"."R_NAME"="CNR"."R_NAME")
filter("AU"."AS1FK_B_FUNC"="CNR"."B_F_CODE")
11 - access("B"."R_NAME"="A"."R_NAME" AND "B"."AS1FK_B_FUNC"="A"."AS1FK_B_FUNC" AND
"B"."AS1FK_S_TYPE"="A"."AS1FK_S_TYPE" AND "B"."AS1FK_T_CODE"="A"."AS1FK_T_CODE"
AND "B"."A2FK_NBR"="A"."A2FK_NBR" AND "B"."A2FK_B_ID"="A"."A2FK_B_ID" AND
"B"."A2FK_C_CD"="A"."A2FK_C_CD" AND "B"."A1FK_NBR"="A"."A1FK_NBR" AND
"B"."A1FK_B_ID"="A"."A1FK_B_ID" AND "B"."A1FK_C_CD"="A"."A1FK_C_CD" AND
"B"."S1FK_C_ID"="A"."S1FK_C_ID" AND "B"."S1FK_CF_ID"="A"."S1FK_CF_ID" AND
"B"."S1FK_S_TYPE"="A"."S1FK_S_TYPE" AND "B"."S1FK_B_FUNC"="A"."S1FK_B_FUNC" AND
"B"."STACO1FK_A_TYPE"="A"."STACO1FK_A_TYPE")
filter("A"."IND"='Y' AND ("B"."IND"='N' OR
"B"."IND"="A"."IND") OR "B"."IND"=' ' AND
"A"."IND"=' ' AND "B"."AS1FK_A_ID"="A"."AS1FK_A_ID" AND
"B"."IND"="A"."IND")
EDIT
~~~~
I edited the above SQL to remove self-joins and unnecessary group-by.
I am doing grouping only on primary_secondary rows based on join.
Between primary and secondary, I am deciding the primary acct that should be plugged in for secondary.
This is giving the same result. I am wondering if this can be further improved.
WITH data
AS ( SELECT sanw.STAC01FK_C_ID,
sanw.STAC01FK_CF_ID,
sanw.STAC01FK_A_ID,
sanw.STACO1FK_A_TYPE,
sanw.STAC01FK_S_TYPE,
sanw.STAC01FK_B_FUNC,
BAU.ASAC01FK_A_ID,
BAU.ASAC01FK_B_FUNC,
BAU.ASAC01FK_S_TYPE,
BAU.ASAC01FK_TYPE_CODE,
BAAC02FK_BANK_ID,
BAAC02FK_NBR,
BAAC02FK_CURR_CD,
BAAC01FK_BANK_ID,
BAAC01FK_NBR,
BAAC01FK_CURR_CD,
BAU.R_NAME,
PRIMARY_ACCT_IND
FROM N_RULE cnr,
SAN sanw,
ACCT_USG bau
WHERE BAU.R_NAME = CNR.R_NAME
AND BAU.BAAC02FK_CURR_CD = CNR.C_CD
AND BAU.ASAC01FK_B_FUNC = CNR.B_FUNC_CD
AND BAU.ASAC01FK_A_ID = sanw.ASAC01FK_ID
AND BAU.ASAC01FK_B_FUNC = sanw.ASAC01FK_B_FUNC
AND BAU.ASAC01FK_S_TYPE = sanw.ASAC01FK_S_TYPE
AND BAU.ASAC01FK_TYPE_CODE = sanw.ASAC01FK_A_TYPE
AND CNR.FM_CODE = 'SP'
AND sanw.EFF_STATUS_CODE = 'A'
AND sanw.ASAC01FK_B_FUNC = CNR.B_FUNC_CD),
primary_secondary
AS (SELECT PRIMARY_ACCT_IND,
B.BAAC02FK_CURR_CD AS C_CD,
B.STAC01FK_C_ID AS C_ID_2,
B.STAC01FK_CF_ID AS CF_ID_2,
-- secondary accounts
B.STAC01FK_A_ID AS A_ID_2,
B.STAC01FK_B_FUNC AS B_FUNC_2,
B.STAC01FK_S_TYPE AS S_TYPE_2,
B.STACO1FK_A_TYPE AS A_TYPE_2,
--primary accounts
B.STAC01FK_A_ID AS A_ID_1,
B.STAC01FK_B_FUNC AS B_FUNC_1,
B.STAC01FK_S_TYPE AS S_TYPE_1,
B.STACO1FK_A_TYPE AS A_TYPE_1,
RANK ()
OVER (
PARTITION BY
--bank acct join
B.BAAC02FK_CURR_CD,
--sa join
B.STAC01FK_S_TYPE,
B.STAC01FK_B_FUNC,
B.STACO1FK_A_TYPE,
B.STAC01FK_C_ID,
B.STAC01FK_CF_ID
ORDER BY
PRIMARY_ACCT_IND DESC)
AS d_rank
FROM data B
WHERE B.PRIMARY_ACCT_IND IN ('Y', 'N')),
stand_alone
AS (SELECT B.BAAC02FK_CURR_CD AS C_CD,
B.STAC01FK_C_ID AS C_ID_2,
B.STAC01FK_CF_ID AS CF_ID_2,
-- secondary accounts
B.STAC01FK_A_ID AS A_ID_2,
B.STAC01FK_B_FUNC AS B_FUNC_2,
B.STAC01FK_S_TYPE AS S_TYPE_2,
B.STACO1FK_A_TYPE AS A_TYPE_2,
--primary accounts
B.STAC01FK_A_ID AS A_ID_1,
B.STAC01FK_B_FUNC AS B_FUNC_1,
B.STAC01FK_S_TYPE AS S_TYPE_1,
B.STACO1FK_A_TYPE AS A_TYPE_1
FROM data B
WHERE B.PRIMARY_ACCT_IND = ' '),
PRIMARY
AS (SELECT C_CD,
C_ID_2,
CF_ID_2,
-- secondary accounts
A_ID_2,
B_FUNC_2,
S_TYPE_2,
A_TYPE_2,
--primary accounts
A_ID_1,
B_FUNC_1,
S_TYPE_1,
A_TYPE_1
FROM primary_secondary
WHERE D_RANK = 1),
SECONDARY
AS (SELECT ps.C_CD,
ps.C_ID_2,
ps.CF_ID_2,
-- secondary accounts
ps.A_ID_2,
ps.B_FUNC_2,
ps.S_TYPE_2,
ps.A_TYPE_2,
--primary accounts
p.A_ID_1,
p.B_FUNC_1,
p.S_TYPE_1,
p.A_TYPE_1
FROM primary_secondary ps, primary p
WHERE ps.D_RANK <> 1
AND ps.C_CD = p.C_CD
AND ps.C_ID_2 = p.C_ID_2
AND ps.CF_ID_2 = p.CF_ID_2
-- secondarary accounts
AND ps.B_FUNC_2 = p.B_FUNC_2
AND ps.S_TYPE_2 = p.S_TYPE_2
AND ps.A_TYPE_2 = p.A_TYPE_2)
SELECT * FROM secondary
UNION ALL
SELECT * FROM primary
UNION ALL
SELECT * FROM stand_alone
PLAN:
Plan hash value: 3159296610
----------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 33 | 3421 | 9 (56)| 00:00:01 |
| 1 | TEMP TABLE TRANSFORMATION | | | | | |
| 2 | LOAD AS SELECT | SYS_TEMP_0FD9DBAF1_9261CF31 | | | | |
|* 3 | HASH JOIN | | 11 | 3058 | 119 (1)| 00:00:02 |
|* 4 | HASH JOIN | | 96 | 18816 | 104 (1)| 00:00:02 |
|* 5 | INDEX RANGE SCAN | CNR_PK | 61 | 1769 | 1 (0)| 00:00:01 |
| 6 | TABLE ACCESS FULL | ACCT_USG | 8244 | 1344K| 102 (0)| 00:00:02 |
|* 7 | TABLE ACCESS FULL | SAN | 1444 | 115K| 15 (0)| 00:00:01 |
| 8 | LOAD AS SELECT | SYS_TEMP_0FD9DBAF2_9261CF31 | | | | |
| 9 | WINDOW SORT | | 11 | 594 | 3 (34)| 00:00:01 |
|* 10 | VIEW | | 11 | 594 | 2 (0)| 00:00:01 |
| 11 | TABLE ACCESS FULL | SYS_TEMP_0FD9DBAF1_9261CF31 | 11 | 2343 | 2 (0)| 00:00:01 |
| 12 | LOAD AS SELECT | SYS_TEMP_0FD9DBAF3_9261CF31 | | | | |
|* 13 | VIEW | | 11 | 1089 | 2 (0)| 00:00:01 |
| 14 | TABLE ACCESS FULL | SYS_TEMP_0FD9DBAF2_9261CF31 | 11 | 682 | 2 (0)| 00:00:01 |
| 15 | UNION-ALL | | | | | |
|* 16 | HASH JOIN | | 11 | 1672 | 5 (20)| 00:00:01 |
|* 17 | VIEW | | 11 | 792 | 2 (0)| 00:00:01 |
| 18 | TABLE ACCESS FULL | SYS_TEMP_0FD9DBAF2_9261CF31 | 11 | 682 | 2 (0)| 00:00:01 |
| 19 | VIEW | | 11 | 880 | 2 (0)| 00:00:01 |
| 20 | TABLE ACCESS FULL | SYS_TEMP_0FD9DBAF3_9261CF31 | 11 | 946 | 2 (0)| 00:00:01 |
| 21 | VIEW | | 11 | 1067 | 2 (0)| 00:00:01 |
| 22 | TABLE ACCESS FULL | SYS_TEMP_0FD9DBAF3_9261CF31 | 11 | 946 | 2 (0)| 00:00:01 |
|* 23 | VIEW | | 11 | 682 | 2 (0)| 00:00:01 |
| 24 | TABLE ACCESS FULL | SYS_TEMP_0FD9DBAF1_9261CF31 | 11 | 2343 | 2 (0)| 00:00:01 |
----------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
3 - access("BAU"."ASAC01FK_A_ID"="SANW"."ASAC01FK_ID"||'' AND
"BAU"."ASAC01FK_B_FUNC"="SANW"."ASAC01FK_B_FUNC" AND
"BAU"."ASAC01FK_S_TYPE"="SANW"."ASAC01FK_S_TYPE" AND
"BAU"."ASAC01FK_TYPE_CODE"="SANW"."ASAC01FK_A_TYPE" AND
"SANW"."ASAC01FK_B_FUNC"="CNR"."B_FUNC_CD")
4 - access("BAU"."R_NAME"="CNR"."R_NAME" AND
"BAU"."BAAC02FK_CURR_CD"="CNR"."C_CD" AND
"BAU"."ASAC01FK_B_FUNC"="CNR"."B_FUNC_CD")
5 - access("CNR"."FM_CODE"='SP')
7 - filter("SANW"."EFF_STATUS_CODE"='A')
10 - filter("B"."PRIMARY_ACCT_IND"='N' OR "B"."PRIMARY_ACCT_IND"='Y')
13 - filter("D_RANK"=1)
16 - access("PS"."C_CD"="P"."C_CD" AND "PS"."C_ID_2"="P"."C_ID_2" AND
"PS"."CF_ID_2"="P"."CF_ID_2" AND "PS"."B_FUNC_2"="P"."B_FUNC_2" AND
"PS"."S_TYPE_2"="P"."S_TYPE_2" AND "PS"."A_TYPE_2"="P"."A_TYPE_2")
17 - filter("PS"."D_RANK"<>1)
23 - filter("B"."PRIMARY_ACCT_IND"=' ')