Reputation: 194
MERGE INTO table1 t1
USING
(SELECT column1
FROM table2 join table3 on...
WHERE table2.column5 = 'xyz') t2
ON (t1.column1 = t2.column1 AND t1.column2 = somevalue
AND t1.column3 = someothervalue)
WHEN not matched
THEN
INSERT ...
The "on" part will reject most of the rows, but does merge query all the rows that are inside "using" first? In that case that part will be running uselessly most of the times because 95% of the rows will not match t1.column2 = somevalue AND t1.column3 = someothervalue. Or is Oracle smart enough to not do that?
Upvotes: 1
Views: 4570
Reputation: 21993
Yes, Oracle will rewrite the query to join table1 to the using view query. so if t1.column2 = somevalue AND t1.column3 = someothervalue
is selective and Oracle realises this, you should see in the plan that the query will drive from TABLE1 and then join into the tables in the using view. just run an explain plan to check it. i.e.
set linesize 200 pagesize 200
explain plan for
merge....;
select * from table(dbms_xplan.display());
and you should see that Oracle has done this for you.
eg:
SQL> create table table1(id number primary key, t2_id number, str varchar2(20), notes varchar2(20));
Table created.
SQL> create table table2(id number primary key, notes varchar2(20));
Table created.
SQL>
SQL> insert into table1
2 select rownum, rownum, case mod(rownum, 100) when 0 then 'ONE' else 'TWO' end, null
3 from dual connect by level <=1000000;
1000000 rows created.
SQL>
SQL> insert into table2
2 select rownum, dbms_random.string('x', 10)
3 from dual connect by level <=1000000;
1000000 rows created.
SQL>
SQL> create index table1_idx on table1(str);
Index created.
SQL> exec dbms_stats.gather_table_stats(user, 'TABLE1', method_opt=>'for all indexed columns size skewonly');
PL/SQL procedure successfully completed.
SQL> exec dbms_stats.gather_table_stats(user, 'TABLE2');
PL/SQL procedure successfully completed.
so ill add t1.str = 'ONE'
when is very selective:
SQL> explain plan for
2 merge into table1 t1
3 using (select * from table2 t where t.id > 1000) t2
4 on (t2.id = t1.t2_id and t1.str = 'ONE')
5 when matched then update
6 set t1.notes = t2.notes;
Explained.
SQL> @explain ""
Plan hash value: 2050534005
---------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------------
| 0 | MERGE STATEMENT | | 441 | 11025 | 929 (5)| 00:00:12 |
| 1 | MERGE | TABLE1 | | | | |
| 2 | VIEW | | | | | |
|* 3 | HASH JOIN | | 441 | 12348 | 929 (5)| 00:00:12 |
|* 4 | TABLE ACCESS BY INDEX ROWID| TABLE1 | 441 | 5733 | 69 (2)| 00:00:01 |
|* 5 | INDEX RANGE SCAN | TABLE1_IDX | 8828 | | 21 (0)| 00:00:01 |
|* 6 | TABLE ACCESS FULL | TABLE2 | 994K| 14M| 848 (4)| 00:00:11 |
-------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
3 - access("T"."ID"="T1"."T2_ID")
4 - filter("T1"."T2_ID">1000)
5 - access("T1"."STR"='ONE')
6 - filter("T"."ID">1000)
you can see its applied the index on table1
INDEX RANGE SCAN | TABLE1_IDX
thus removing a lot of rows scanned on table1 (hash join was more appropriate considering my table, but in your case you may see a nested loop approach on the join step).
Upvotes: 1