views:

96

answers:

3

I have the following code that works fine in MS SQL Server:

delete grp
from grp
left join my_data
on grp.id1 = my_data.id1
and grp.id2 = my_data.id2
and grp.id3 = my_data.id3
and grp.id4 = my_data.id4
where my_data.id1 is NULL

Basically, I want to delete all occurrence that can be found in grp and don't have any equivalence in my_data. Sadly, it doesn't work in Oracle 10g. I tried using the old syntax for left join (+) but it doesn't work either. Like this:

delete grp
from grp,
my_data
where grp.id1 = my_data.id1 (+)
and grp.id2 = my_data.id2 (+)
and grp.id3 = my_data.id3 (+)
and grp.id4 = my_data.id4 (+)
and my_data.id1 is NULL

A IN clause would works if I didn't have multiple keys but I don't see how I could use it with my data. So, what is the alternative?

+6  A: 

Tables and data:

SQL> create table grp (id1 number null, id2 number null, id3 number null, id4 number null);    
Table created.

SQL> create table my_data (id1 number null, id2 number null, id3 number null, id4 number null);

Table created.

SQL> insert into grp values (1, 2, 3, 4);

1 row created.

SQL> insert into grp values (10, 20, 30, 40);

1 row created.

SQL> insert into grp values (1, 2, 30, 40);

1 row created.

SQL> insert into my_data values (1, 2, 3, 4);

1 row created.

SQL> commit;

Commit complete.

Using in. Note Do not use if the IDs in the subquery can be null. Not in of null never returns true.

SQL> delete grp where (id1, id2, id3, id4) not in (select id1, id2, id3, id4 from my_data);

2 rows deleted.

SQL> select * from grp;

       ID1        ID2        ID3        ID4
---------- ---------- ---------- ----------
         1          2          3          4

Using exists

SQL> rollback;

Rollback complete.

SQL> delete grp where not exists (select * from my_data where grp.id1 = my_data.id1 and grp.id2 = my_data.id2 and grp.id3 = my_data.id3 and grp.id4 = my_data.id4);

2 rows deleted.

SQL> select * from grp;

       ID1        ID2        ID3        ID4
---------- ---------- ---------- ----------
         1          2          3          4

SQL> 
Shannon Severance
I implemented all three solution proposed so far. the `where not exists` is almost 3 times faster than the `not in` and the `left join` solutions in the precise case I tested it on Oracle. I don't know if any of those solutions follow SQL standards but the `where not exists` solution is the only one that works for both Oracle and MS SQL Server. I'm surprise that the `not in` solution can be use with multiple fields since there is not explicit association. Does it works because they share the same name? Is it possible to make it works using alias?
Simon T.
`not in` with multiple fields is defined in some SQL standard (SQL-92?), but last I checked had not been implemented in SQL Server. `(w, x, y, z) in (select a, b, c, d from ....)` takes the tuple on the left side, `(w, x, y, z)` And checks it against every row (tuple) returned on the right side: `w=a and x=b and y=c and z=d`. It does not depend on the names being the same.)
Shannon Severance
+2  A: 

Hi Simon,

Shannon's solution is the way to go: use the operator NOT IN (or NOT EXISTS).

You can however delete or update a join in Oracle, but the synthax is not the same as MS SQL Server:

SQL> DELETE FROM (SELECT grp.*
  2                  FROM grp
  3                  LEFT JOIN my_data ON grp.id1 = my_data.id1
  4                                   AND grp.id2 = my_data.id2
  5                                   AND grp.id3 = my_data.id3
  6                                   AND grp.id4 = my_data.id4
  7                 WHERE my_data.id1 IS NULL);

2 rows deleted

Additionally, Oracle will only let you update a join if there is no ambiguity as to which base row will be accessed by the statement. In particular, Oracle won't risk an update or a delete (the statement will fail) if there is a possibility that a row may appear twice in the join. In this case, the delete will only work if there is a UNIQUE constraint on my_data(id1, id2, id3, id4).

Vincent Malgrat
+2  A: 

If you want to ensure there is no ambiguity in what's being deleted, you could change Vincent's solution to:

delete from grp where row_id in
    (
    select
         grp.rowid row_id
    from
         grp left outer join my_data on
            grp.id1 = my_data.id1
        and grp.id2 = my_data.id2
        and grp.id3 = my_data.id3
        and grp.id4 = my_data.id4
    where
        my_data.id1 is NULL
    )
Nick Pierpoint