2010-05-26 2 views
3

я использую следующее обновление или вставить заявление Oracle на данный момент:Oracle - UPSERT с обновлением не выполняется для немодифицированных значений

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; 

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

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

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 заявления, но он не работает для обновлений, когда значение не изменяется (обновление не меняет ничего и вставка выполнена, но нарушение ПК происходит).

Полный сливаться в образце:

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 ... если SQL% ROWCOUNT = 0 THEN INSERT ... внутренне для сливаться в статье? Второй оператор MERGE INTO терпит неудачу, поскольку обновление ничего не обновляет и поэтому выполняется INSERT, что приводит к нарушению PK, потому что строка уже существует, только значения не изменялись.

+0

после замены 'DSMS' на' TestMerge' в вашем примере, оба оператора отлично работали для меня. – Quassnoi

+0

@Quassnoi: Я нашел некоторую информацию об ошибках в реализациях Oracle MERGE INTO в некоторых версиях Oracle DB, поэтому я думаю, что я могу столкнуться с некоторой ошибкой, потому что я действительно получаю PK-нарушение при выполнении команды слияния во второй раз. В какой версии OraDB вы успешно запускали образец? Я на 10.2.0.1.0 ... – Buthrakaur

+0

Я запускаю версию '10.2.0.1.0' тоже. Созданная вами таблица образцов называется «TestMerge», но в настройках «MERGE» вы используете таблицу «DSMS»: «MERGE INTO DSMS D ...». – Quassnoi

ответ

4
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 значения.

+0

Спасибо. К сожалению, это не работает, когда строка уже существует, но значения одинаковы - вставка больше, чем срабатывание и исключение нарушения PK. Я попытался добавить «WHERE D.SURNAME <> V.SURNAME ...» в пункт INTO, но я получил «ORA-38102 Недопустимый столбец в INSERT WHERE». – Buthrakaur

+0

@Buth: Конечно, моя вина. Попробуйте сейчас. – Quassnoi

+0

Поле DSM является первичным ключом, поэтому вставка терпит неудачу, поскольку DSM не может быть нулевым. Вы опустили поле DSM в измененном коде ... – Buthrakaur

2

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

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; 
+0

вы правы - хорошая идея .. хотя большинство заявлений закончится исполнением UPDATE без изменения любой строки (значения не изменятся) в моем сценарии, поэтому я думаю, что это может быть не оптимальное решение. Я попробую и посмотрю. Благодарю. – Buthrakaur