sqloracle-databaseinsertmultirow

Oracle multi-row 'insert if not exists' from select joined with table literal


Suppose I have the following table structure:

a (a_id number primary key, a_code varchar unique not null)
b (b_id number primary key, a_id number foreign key references a(a_id), b_value varchar

I need to insert on table b a couple hundred pairs of (a_id, b_value) if they do not already exist, and the information I have is (a_code, b_value). I'm coming from a Postgres background where doing this would be trivial, but I'm having trouble finding a way of pulling it off in Oracle without resorting to creating and subsequently dropping a materialized view, or doing one insert per row.

This was the least ugly solution I was able to come up with:

insert /*+ IGNORE_ROW_ON_DUPKEY_INDEX(b_value, iun_b_value) */
into b (b_id, a_id, b_value)
select b_id.nextval, a_id, b_value
from a
inner join

(select 'code1' a_code, 'value1' b_value from dual union all
select 'code2' a_code, 'value2' b_value from dual union all
select 'code3' a_code, 'value3' b_value from dual union all
select 'code4' a_code, 'value4' b_value from dual) new

on a.a_code = new.b_value

But I was told I can't use that semantic hint for handling duplicate rows on production, so I tried this:

merge into b using
  (select a_id, b_value
  from a inner join 

  (select 'code1' a_code, 'value1' b_value from dual union all
  select 'code2' a_code, 'value2' b_value from dual union all
  select 'code3' a_code, 'value3' b_value from dual union all
  select 'code4' a_code, 'value4' b_value from dual) new

  new on a.a_code = new.a_code) insert_codes

on (insert_codes.a_code = b.a_code and insert_codes.b_value = b.b_value)
when not matched then
  insert (b.b_id, b.a_id, b.b_value)
  values (b_id.nextval, insert_codes.a_id, 1, insert_codes.b_value)

But I get the same "ORA-00980: synonym translation is no longer valid" error I was getting when I was referencing an alias in a subselect while trying to solve this using a where not exists clause.

Is there any way to do it without materialized views or multiple inserts?


Solution

  • Looking at my simplified example made me realize what was wrong. The on clause was

    on (insert_codes.a_code = b.a_code and insert_codes.b_value = b.b_value)
    

    but it should be

    on (insert_codes.a_id = b.a_id and insert_codes.b_value = b.b_value)
    

    So this works:

    merge into b using
      (select a_id, b_value
      from a inner join 
    
      (select 'code1' a_code, 'value1' b_value from dual union all
      select 'code2' a_code, 'value2' b_value from dual union all
      select 'code3' a_code, 'value3' b_value from dual union all
      select 'code4' a_code, 'value4' b_value from dual) new
    
      new on a.a_code = new.a_code) insert_codes
    
    on (insert_codes.a_id = b.a_id and insert_codes.b_value = b.b_value)
    when not matched then
      insert (b.b_id, b.a_id, b.b_value)
      values (b_id.nextval, insert_codes.a_id, 1, insert_codes.b_value)
    

    Posting here in case someone else has trouble pulling off the same kind of insert in the future.