views:

1529

answers:

3

I need to be able to run an Oracle query which goes to insert a number of rows, but it also checks to see if a primary key exists and if it does, then it skips that insert. Something like:

INSERT ALL
    IF NOT EXISTS( SELECT 1 WHERE fo.primary_key='bar' )
    (
        INSERT INTO 
            schema.myFoo fo ( primary_key, value1, value2 )
        VALUES
            ('bar','baz','bat')
    ),

    IF NOT EXISTS( SELECT 1 WHERE fo.primary_key='bar1' )
    (
        INSERT INTO 
            schema.myFoo fo ( primary_key, value1, value2 )
        VALUES
            ('bar1','baz1','bat1')
    )
SELECT * FROM schema.myFoo;

Is this at all possible with Oracle?

Bonus points if you can tell me how to do this in PostgreSQL or MySQL.

A: 

It that code is on the client then you have many trips to the server so to eliminate that.

Insert all the data into a temportary table say T with the same structure as myFoo

Then

insert myFoo
  select *
     from t
       where t.primary_key not in ( select primary_key from myFoo)

This should work on other databases as well - I have done this on Sybase

It is not the best if very few of the new data is to be inserted as you have copied all the data over the wire.

Mark
Definitely cleaver +1. I'd have to create a temporary table first, but that really isn't a terrible difficulty.
Christopher W. Allen-Poole
correction *clever*.
Christopher W. Allen-Poole
+2  A: 

The statement is called MERGE. Look it up, I'm too lazy.

Beware, though, that MERGE is not atomic, which could cause the following effect (thanks, Marius):

SESS1:

create table t1 (pk int primary key, i int);
create table t11 (pk int primary key, i int);
insert into t1 values(1, 1);
insert into t11 values(2, 21);
insert into t11 values(3, 31);
commit;

SESS2: insert into t1 values(2, 2);

SESS1:

MERGE INTO t1 d
USING t11 s ON (d.pk = s.pk)
WHEN NOT MATCHED THEN INSERT (d.pk, d.i) VALUES (s.pk, s.i);

SESS2: commit;

SESS1: ORA-00001

erikkallen
Again, without locking the table(or the master record first), there is a race. This method always requires using a temp table. I wouldn't say it's wrong, but sometimes can be just too much.
Marius Burz
No, the merge should be atomic.
erikkallen
Yes, MERGE itself is atomic. But... Sess1: INSERT pk=1 INTO myFoo; Sess2: MERGE INTO myFoo d USING tmpTable s ON (d.pk = s.pk)... Sess1: COMMIT; Sess2: ORA-00001; For cases when the number of rows inserted is low, it really doesn't make sense to use a temp table. Everything has its price, and CREATE TABLE and MERGE don't come cheap(look at the required latches/locks and the like).
Marius Burz
You don't need a temp table. If you only have a few rows, (SELECT 1 FROM dual UNION SELECT 2 FROM dual) will do. Why would your example give ORA-0001? Wouldn't merge take the update lock on the index key and not continue until Sess1 has either committed or rolled back?
erikkallen
Erik, please see the answer below. There wasn't enough space to post it as a comment, nor was any formatting available.
Marius Burz
Under the read commited isolation level the MERGEing session cannot see the inserted row from the other session, therefore it attempts an insert which is then blocked waiting for the other session to commit or rollback. A commit causes a PK violation to be raised. entirely correct behaviour.
David Aldridge
100% correct behavior. Where did I write something else? Not all people out there know about what I wrote, that's why I decided to write about it.
Marius Burz
I don't have any problem with the information you wrote about the MERGE, I was just supplying some "theoretical" background to clarify that it is the correct behaviour, not (as erik suggested) a bug. It is of course exactly the same behaviour in theory as two sessions attempting to insert duplicate PK values.
David Aldridge
@David: I understand this. However, MERGE could also try to take the update lock on all keys it uses, rather than (as it obviously does) take a read lock to determine which operation to perform, and then take the update lock to perform the operation.
erikkallen
I might not be understanding your point correctly Erik, but it will obviously take out a lock on records that it matches because it will update them, while there really isn't a row-level read lock (other than SELECT ... FOR UPDATE). If I had to guess I'd say that a MERGE takes an immediate row-level exclusive lock on every matching row.
David Aldridge
A: 

This is an answer to the comment posted by erikkallen:

You don't need a temp table. If you only have a few rows, (SELECT 1 FROM dual UNION SELECT 2 FROM dual) will do. Why would your example give ORA-0001? Wouldn't merge take the update lock on the index key and not continue until Sess1 has either committed or rolled back? – erikkallen

Well, try it yourself and tell me whether you get the same error or not:

SESS1:

create table t1 (pk int primary key, i int);
create table t11 (pk int primary key, i int);
insert into t1 values(1, 1);
insert into t11 values(2, 21);
insert into t11 values(3, 31);
commit;

SESS2: insert into t1 values(2, 2);

SESS1:

MERGE INTO t1 d
USING t11 s ON (d.pk = s.pk)
WHEN NOT MATCHED THEN INSERT (d.pk, d.i) VALUES (s.pk, s.i);

SESS2: commit;

SESS1: ORA-00001

Marius Burz
I don't have access to Oracle, so I can't try it, but I believe you. I do think, though, that this should be considered a bug.
erikkallen
this is correct behaviour under the read commited transaction isolation level, not a bug. the behaviour of MERGE is entirely consistent with the behaviour of an update that affects no rows followed by an attempted insert.
David Aldridge
@David: I realize that those things are equivalend, but I wonder how many people know this. I sure didn't, and I really expected it to work without problem. If I want the semantics of an INSERT which inserts no rows, then an UPDATE, then I write an INSERT and then an UPDATE.
erikkallen
And why -1 for this? It's (apparently) correct, and it taught me something. +1.
erikkallen
-1 because it doesn't answer the question, although I agree that it's interesting information. You might add the information into your answer or make yours community wiki so that others can. Also it certainly shouldn't be considered as a bug.
David Aldridge
If you would have read what I wrote you would have understood this answer was meant to be a reply to Erik not an answer to the original question, which I answered on the other answer I posted here(and which of course you also voted down).
Marius Burz
I voted this down because it is not an answer to the question, as I mention above, so I did read it in exactly the context that you suggest I didn't. I voted your other answer down because it is horrible RDBMS development practice.
David Aldridge
OK, I inserted this content into my answer.
erikkallen
Shweet, but you might correct the "is not atomic".
David Aldridge