Silent ORA-904 on MERGE

From time to time looking at the tkprof’ed SQL traces I see this message:

The following statement encountered a error during parse:

SELECT 1 FROM DUAL WHERE some_predicate_here

Error encountered: ORA-00904

A some_predicate_here is the predicate from ON clause of a MERGE statement executed by the session. Up until recently I didn’t know why that happens, and since the error is not reported to the end-user there’s no big problem other than very little annoyance.


This is an example:

drop table t1 cascade constraints purge;
create table t1 (x, y, pad)
as
select rownum, mod(rownum, 100), lpad('x', 10, 'x') from dual
connect by level <= 1000;
exec dbms_stats.gather_table_stats(user, 't1')

exec dbms_session.session_trace_enable(false)
alter session set events '10053 trace name context forever, level 2';

merge into t1 
using (select 1 x, lpad('y', 10, 'y') pad from dual) t2
on (t1.x = t2.x)
when matched then update set t1.pad = t2.pad
when not matched then insert (x,y,pad) values (t2.x, t2.x, t2.pad);

alter session set events '10053 trace name context off';
exec dbms_session.session_trace_disable

And trace file excerpt:

PARSE ERROR #5:len=36 dep=1 uid=60 oct=3 lid=60 tim=1266258229533447 err=904
SELECT 1 FROM DUAL WHERE t1.x = t2.x
CLOSE #5:c=0,e=4,dep=1,type=0,tim=1266258229533578
Registered qb: MRG$1 0x84379c (PARSER)
---------------------
QUERY BLOCK SIGNATURE
---------------------
...
====================== END SQL Statement Dump ======================
=====================
PARSING IN CURSOR #2 len=198 dep=0 uid=60 oct=189 lid=60 tim=1266258229984031 hv=3137650586 ad='3afa49c8' sqlid='aq4m8daxh9gwu'
merge into t1
using (select 1 x, lpad('y', 10, 'y') pad from dual) t2
on (t1.x = t2.x)
when matched then update set t1.pad = t2.pad
when not matched then insert (x,y,pad) values (t2.x, t2.x, t2.pad)
END OF STMT
PARSE #2:c=45993,e=451460,p=0,cr=7,cu=0,mis=1,r=0,dep=0,og=1,plh=1461866594,tim=1266258229984030
EXEC #2:c=0,e=16490,p=0,cr=6,cu=3,mis=0,r=1,dep=0,og=1,plh=1461866594,tim=1266258230000570
STAT #2 id=1 cnt=0 pid=0 pos=1 obj=0 op='MERGE  T1 (cr=6 pr=0 pw=0 time=0 us)'
STAT #2 id=2 cnt=1 pid=1 pos=1 obj=0 op='VIEW  (cr=6 pr=0 pw=0 time=0 us)'
STAT #2 id=3 cnt=1 pid=2 pos=1 obj=0 op='NESTED LOOPS OUTER (cr=6 pr=0 pw=0 time=0 us cost=6 size=18 card=1)'
STAT #2 id=4 cnt=1 pid=3 pos=1 obj=0 op='FAST DUAL  (cr=0 pr=0 pw=0 time=0 us cost=2 size=0 card=1)'
STAT #2 id=5 cnt=1 pid=3 pos=2 obj=65766 op='TABLE ACCESS FULL T1 (cr=6 pr=0 pw=0 time=0 us cost=4 size=18 card=1)'
CLOSE #2:c=0,e=6,dep=0,type=0,tim=1266258230000987

Raw trace file says that dummy SELECT is executed right before the original query optimization begins, in the recursive context; but no other clues to why that is needed. After extensive searching over the MOS/web I managed to find the answer – and it was right here in the docs! :)

To insert all of the source rows into the table, you can use a constant filter predicate in the ON clause condition. An example of a constant filter predicate is ON (0=1). Oracle Database recognizes such a predicate and makes an unconditional insert of all source rows into the table. This approach is different from omitting the merge_update_clause. In that case, the database still must perform a join. With constant filter predicate, no join is performed.

Apparently constant filter predicate allows Oracle to structurally modify execution plan by removing the outer join. This is it:

SQL> explain plan for
  2  merge into t1
  3  using (select 1 x, lpad('y', 10, 'y') pad from dual) t2
  4  on (1 = 0)
  5  when matched then update set t1.pad = t2.pad
  6  when not matched then insert (x,y,pad) values (t2.x, t2.x, t2.pad);

Explained.

SQL> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------------------------
Plan hash value: 3550614331

------------------------------------------------------------------------
| Id  | Operation       | Name | Rows  | Bytes | Cost (%CPU)| Time     |
------------------------------------------------------------------------
|   0 | MERGE STATEMENT |      |     1 |    28 |     2   (0)| 00:00:01 |
|   1 |  MERGE          | T1   |       |       |            |          |
|   2 |   VIEW          |      |       |       |            |          |
|   3 |    FAST DUAL    |      |     1 |       |     2   (0)| 00:00:01 |
------------------------------------------------------------------------

10 rows selected.

Trace 10053 of the last query shows that CBO still costs the join (6 join orders were considered in my case), but includes hints to block it:

    BEGIN_OUTLINE_DATA
      IGNORE_OPTIM_EMBEDDED_HINTS
      OPTIMIZER_FEATURES_ENABLE('11.2.0.1')
      DB_VERSION('11.2.0.1')
      ALL_ROWS
      OUTLINE_LEAF(@"SEL$F5BB74E1")
      MERGE(@"SEL$2")
      OUTLINE_LEAF(@"SEL$3")
      OUTLINE_LEAF(@"MRG$1")
      OUTLINE(@"SEL$1")
      OUTLINE(@"SEL$2")
      NO_ACCESS(@"MRG$1" "from$_subquery$_007"@"MRG$1")
      NO_ACCESS(@"MRG$1" "T2"@"MRG$1")
      FULL(@"MRG$1" "T1"@"MRG$1")
      LEADING(@"MRG$1" "from$_subquery$_007"@"MRG$1" "T2"@"MRG$1" "T1"@"MRG$1")
      USE_MERGE_CARTESIAN(@"MRG$1" "T2"@"MRG$1")
      USE_MERGE_CARTESIAN(@"MRG$1" "T1"@"MRG$1")
    END_OUTLINE_DATA

According to the Doc ID 244055.1 the constant filter predicate is “an enhancement” of 10g. Well, I cannot say this is a good one since it’s implementation sucks:

  • additional query is executed on each hard parse of a MERGE statement
  • CBO is working strangely costing the join when it seems unnecessary
  • it is restricted to constant predicates only (you can’t just add 1=0 to an existing predicate)

In my view this “feature” should rather be part of join elimination.

About these ads

One Trackback

  1. [...] 13-Where does that select from dual comes from when doing merge insert ? Timur Akhmadeev-Silent ORA-904 on MERGE [...]

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

Join 290 other followers