Oracle - UPSERT с обновлением, не выполненным для неизмененных значений - PullRequest
3 голосов
/ 26 мая 2010

Я использую следующее обновление или вставьте оператор Oracle в данный момент:

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;

Это работает нормально, за исключением того, что оператор update выполняет фиктивное обновление, если данные совпадают с предоставленными значениями параметров. Я не возражаю против фиктивного обновления в нормальной ситуации, но есть система репликации / синхронизации, построенная на этой таблице с использованием триггеров для таблиц для захвата обновленных записей, и выполнение этого оператора часто для многих записей просто означает, что я вызову огромный трафик в триггерах и система синхронизации.

Есть ли какой-нибудь простой способ переформулировать этот код, чтобы оператор update не обновлял запись, если бы в этом не было необходимости, без использования следующего контрольного кода IF-EXISTS, который, на мой взгляд, недостаточно изящен и, возможно, также не самый эффективный для этой задачи? 1006 *

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;

Я также пытался использовать оператор MERGE INTO, но он не работает для обновлений, когда значение не изменено (обновление ничего не изменяет и вставка выполняется, но происходит нарушение PK).

Полный MERGE INTO образец:

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)

Похоже, что Oracle использует UPDATE ... IF SQL% ROWCOUNT = 0 THEN INSERT ... внутренне для предложения MERGE INTO? Второй оператор MERGE INTO завершается неудачно, потому что обновление ничего не обновляет, поэтому выполняется INSERT, что приводит к нарушению PK, поскольку строка уже существует, а значения не изменились.

Ответы [ 2 ]

4 голосов
/ 26 мая 2010
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)

Вам может потребоваться добавить дополнительные NULL проверки, если ваши поля принимают NULL значения.

2 голосов
/ 27 мая 2010

Вы можете включить его с ног на голову. Однако зависит от соотношения вставок и обновлений, так как при большом количестве обновлений у вас будет много неудачных вставок.

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;
...