views:

253

answers:

2

I'm using following update or insert Oracle statement at the moment:

BEGIN
  UPDATE DSMS
     SET SURNAME = :SURNAME
   WHERE DSM = :DSM;
  IF (SQL%ROWCOUNT = 0) THEN
    INSERT INTO DSMS
      (DSM, SURNAME)
    VALUES
      (:DSM, :SURNAME);
  END IF;
END;

This runs fine except that the update statement performs dummy update if the data is same as the parameter values provided. I would not mind the dummy update in normal situation, but there's a replication/synchronization system build over this table using triggers on tables to capture updated records and executing this statement frequently for many records simply means that I'd cause huge traffic in triggers and the sync system.

Is there any simple method how to reformulate this code that the update statement wouldn't update record if not necessary without using following IF-EXISTS check code which I find not sleek enough and maybe also not most efficient for this task?

DECLARE
  CNT NUMBER;
BEGIN
  SELECT COUNT(1) INTO CNT FROM DSMS WHERE DSM = :DSM;
  IF SQL%FOUND THEN
    UPDATE DSMS
       SET SURNAME = :SURNAME
     WHERE DSM = :DSM
       AND SURNAME != :SURNAME;
  ELSE
    INSERT INTO DSMS
      (DSM, SURNAME)
    VALUES
      (:DSM, :SURNAME);
  END IF;
END;

I also tried using MERGE INTO statement, but it does not work for updates when value is not modified (update does not modify anything and insert is executed, but PK violation occurs).

Full MERGE INTO sample:

CREATE TABLE DSMS(
  dsm VARCHAR2(10) NOT NULL PRIMARY KEY,
  surname VARCHAR2(10) NOT NULL
);
> Table created

-- :DSM = 'xx', :SURNAME = 'xx'
MERGE INTO DSMS D
USING (SELECT :DSM       AS DSM,
              :SURNAME   AS SURNAME
         FROM DUAL) V
ON (D.DSM = V.DSM)
WHEN MATCHED THEN
  UPDATE
     SET SURNAME = V.SURNAME
   WHERE D.SURNAME <> V.SURNAME
WHEN NOT MATCHED THEN
  INSERT (DSM, SURNAME)
  VALUES (V.DSM, V.SURNAME);

> Ok - record inserted

-- :DSM = 'xx', :SURNAME = 'xx'
MERGE INTO DSMS D
USING (SELECT :DSM       AS DSM,
              :SURNAME   AS SURNAME
         FROM DUAL) V
ON (D.DSM = V.DSM)
WHEN MATCHED THEN
  UPDATE
     SET SURNAME = V.SURNAME
   WHERE D.SURNAME <> V.SURNAME
WHEN NOT MATCHED THEN
  INSERT (DSM, SURNAME)
  VALUES (V.DSM, V.SURNAME);

> ORA-00001 - Unique constraint violated (PK violation)

It looks like that Oracle is using UPDATE...IF SQL%ROWCOUNT=0 THEN INSERT... internally for MERGE INTO clause? The second MERGE INTO statement fails, because update does not update anything and so INSERT is executed which results in PK violation, because row already exists just the values did not change.

+2  A: 
MERGE
INTO    dsms d
USING   (
        SELECT  :DSM AS dsm, :SURNAME AS surname, :FIRSTNAME AS firstname, :VALID AS valud
        FROM    dual
        ) v
ON      (d.dsm = q.dsm)
WHEN MATCHED THEN
UPDATE
SET     SURNAME = v.SURNAME, FIRSTNAME = v.FIRSTNAME, VALID = v.VALID
WHERE   d.surname <> v.surname
        OR d.firstname <> v.firstname
        OR d.valid <> v.valid
WHEN NOT MATCHED THEN
INSERT
INTO    (SURNAME, FIRSTNAME, VALID)
VALUES  (SURNAME, FIRSTNAME, VALID)

You may need to add extra NULL checks if your fields accept NULL values.

Quassnoi
Thanks. Unfortunately this does not work when row already exists, but values are same - insert is than fired and PK violation exception raised. I tried to add "WHERE D.SURNAME <> V.SURNAME..." to INTO clause, but I got "ORA-38102 Invalid column in the INSERT WHERE".
Buthrakaur
@Buth: sure, my fault. Please try now.
Quassnoi
DSM field is primary key so the insert fails, because DSM can not be null. You omitted the DSM field in the modified code...
Buthrakaur
@Buth: if the record already exists, then the `UPDATE` should be fired, not insert. I thought that `DSM` is filled with a sequence in a trigger. Could you please post a sample record and the values that don't behave like you expect when merging?
Quassnoi
@Quassnoi: I just added MERGE INTO sample code which simulates the failure.
Buthrakaur
+2  A: 

You can turn it on its head. Depends on the ratio of inserts to updates though, as with lots of updates you will be running a lot of inserts that fail.

BEGIN
  INSERT INTO DSMS
      (DSM, SURNAME)
  VALUES
      (:DSM, :SURNAME);
EXCEPTION WHEN DUP_VAL_ON_INDEX THEN
    UPDATE DSMS
       SET SURNAME = :SURNAME
     WHERE DSM = :DSM
       AND SURNAME != :SURNAME;
END;
Gary
you're right - nice idea.. although most of the statements will end in executing UPDATE without modifying any row (values won't change) in my scenario so I guess it may not be optimal solution. I will try it and see. thanks.
Buthrakaur