2008-10-26 1 views
237

В либо обновлениях операции UPSERT или вставляет строку в таблице, в зависимости, если таблица уже имеет ряд, который соответствует данному:Oracle: как UPSERT (обновление или вставить в таблицу?)

if table t has a row exists that has key X: 
    update t set mystuff... where mykey=X 
else 
    insert into t mystuff... 

Поскольку у Oracle нет конкретного оператора UPSERT, как лучше всего это сделать?

ответ

33

Альтернативой MERGE ("старомодным способом"):

begin 
    insert into t (mykey, mystuff) 
     values ('X', 123); 
exception 
    when dup_val_on_index then 
     update t 
     set mystuff = 123 
     where mykey = 'X'; 
end; 
182

MERGE statement объединяет данные между двумя таблицами. Использование DUAL позволяет использовать эту команду. Обратите внимание, что это не защищено от одновременного доступа.

create or replace 
procedure ups(xa number) 
as 
begin 
    merge into mergetest m using dual on (a = xa) 
     when not matched then insert (a,b) values (xa,1) 
      when matched then update set b = b+1; 
end ups; 
/
drop table mergetest; 
create table mergetest(a number, b number); 
call ups(10); 
call ups(10); 
call ups(20); 
select * from mergetest; 

A      B 
---------------------- ---------------------- 
10      2 
20      1 
+45

По-видимому, "сливаются в" statem ent не является атомарным. Это может привести к «ORA-0001: уникальному ограничению» при одновременном использовании. Проверка наличия совпадения и вставка новой записи не защищены блокировкой, поэтому существует условие гонки. Чтобы сделать это надежно, вам нужно поймать это исключение и либо повторно запустить слияние, либо сделать простое обновление. В Oracle 10 вы можете использовать предложение «log errors», чтобы продолжить его с остальными строками при возникновении ошибки и занести в журнал нарушающую строку в другую таблицу, а не просто останавливаться. – 2009-07-13 05:15:15

+1

Привет, я попытался использовать тот же шаблон запроса в моем запросе, но каким-то образом мой запрос вставляет повторяющиеся строки. Я не могу найти больше информации о таблице DUAL. Может кто-нибудь, пожалуйста, скажите мне, где я могу получить информацию о DUAL, а также о синтаксисе слияния? – Shekhar 2010-10-15 06:12:05

+5

@Shekhar Dual - это фиктивная таблица с одним рядом и столбцом http://www.adp-gmbh.ch/ora/misc/dual.html – YogoZuno 2010-11-19 04:56:16

42

Другой вариант без проверки исключения:

UPDATE tablename 
    SET val1 = in_val1, 
     val2 = in_val2 
    WHERE val3 = in_val3; 

IF (sql%rowcount = 0) 
    THEN 
    INSERT INTO tablename 
     VALUES (in_val1, in_val2, in_val3); 
END IF; 
-5

От http://www.praetoriate.com/oracle_tips_upserts.htm:

«В Oracle9i, UPSERT может выполнить эту задачу в одном заявлении: «

INSERT 
FIRST WHEN 
    credit_limit >=100000 
THEN INTO 
    rich_customers 
VALUES(cust_id,cust_credit_limit) 
    INTO customers 
ELSE 
    INTO customers SELECT * FROM new_customers; 
91

Двойной пример выше, который находится в PL/SQL, был отличным, потому что я хотел сделать что-то подобное, но я хотел его на стороне клиента ... так что вот SQL, который я использовал для отправки аналогичного заявления непосредственно из некоторых C#

MERGE INTO Employee USING dual ON ("id"=2097153) 
WHEN MATCHED THEN UPDATE SET "last"="smith" , "name"="john" 
WHEN NOT MATCHED THEN INSERT ("id","last","name") 
    VALUES (2097153,"smith", "john") 

Однако с точки зрения C# эти обеспечить, чтобы быть медленнее, чем делать обновление и увидеть, если строки затрагиваемых было 0 и делать вставку, если она была.

-2

Попробуйте это,

insert into b_building_property (
    select 
    'AREA_IN_COMMON_USE_DOUBLE','Area in Common Use','DOUBLE', null, 9000, 9 
    from dual 
) 
minus 
(
    select * from b_building_property where id = 9 
) 
; 
8

записку о двух решениях, которые предполагают:

1) Вставьте, если исключение затем обновить,

или

2) Обновление, если sql% rowcount = 0, затем введите

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

Если вы выберете неправильный, вы получите кучу ненужных указателей. Не огромная сделка, но все же что-то, что нужно учитывать.

13

Я бы хотел, чтобы Громмит ответил, за исключением того, что он требует значений обмана.Я нашел решение, в котором он может появиться сразу: http://forums.devshed.com/showpost.php?p=1182653&postcount=2

MERGE INTO KBS.NUFUS_MUHTARLIK B 
USING (
    SELECT '028-01' CILT, '25' SAYFA, '6' KUTUK, '46603404838' MERNIS_NO 
    FROM DUAL 
) E 
ON (B.MERNIS_NO = E.MERNIS_NO) 
WHEN MATCHED THEN 
    UPDATE SET B.CILT = E.CILT, B.SAYFA = E.SAYFA, B.KUTUK = E.KUTUK 
WHEN NOT MATCHED THEN 
    INSERT ( CILT, SAYFA, KUTUK, MERNIS_NO) 
    VALUES (E.CILT, E.SAYFA, E.KUTUK, E.MERNIS_NO); 
21
  1. вставки, если не существует
  2. обновление:
  
INSERT INTO mytable (id1, t1) 
    SELECT 11, 'x1' FROM DUAL 
    WHERE NOT EXISTS (SELECT id1 FROM mytble WHERE id1 = 11); 

UPDATE mytable SET t1 = 'x1' WHERE id1 = 11; 
18

Ни один из ответов до сих пор не безопасности в лице от одновременного доступа, как указано в комментарии Тима Сильвестра, и будет вызывать исключения в случае рас. Чтобы исправить это, комбинация вставки/обновления должна быть завернута в какой-то оператор цикла, так что в случае исключения все это будет повторено.

В качестве примера, вот как код Grommit может быть обернут в петлю, чтобы сделать его безопасным при запуске одновременно:

PROCEDURE MyProc (
... 
) IS 
BEGIN 
LOOP 
    BEGIN 
    MERGE INTO Employee USING dual ON ("id"=2097153) 
     WHEN MATCHED THEN UPDATE SET "last"="smith" , "name"="john" 
     WHEN NOT MATCHED THEN INSERT ("id","last","name") 
     VALUES (2097153,"smith", "john"); 
    EXIT; -- success? -> exit loop 
    EXCEPTION 
    WHEN NO_DATA_FOUND THEN -- the entry was concurrently deleted 
     NULL; -- exception? -> no op, i.e. continue looping 
    WHEN DUP_VAL_ON_INDEX THEN -- an entry was concurrently inserted 
     NULL; -- exception? -> no op, i.e. continue looping 
    END; 
END LOOP; 
END; 

нотабене В режиме транзакции SERIALIZABLE, который я не рекомендую btw, вместо этого вы можете использовать исключения ORA-08177: can't serialize access for this transaction.

5

Я использую первый образец кода в течение многих лет. Обратите внимание на notfound, а не на счет.

UPDATE tablename SET val1 = in_val1, val2 = in_val2 
    WHERE val3 = in_val3; 
IF (sql%notfound) THEN 
    INSERT INTO tablename 
     VALUES (in_val1, in_val2, in_val3); 
END IF; 

Код ниже, возможно, это новый и улучшенный код

MERGE INTO tablename USING dual ON (val3 = in_val3) 
WHEN MATCHED THEN UPDATE SET val1 = in_val1, val2 = in_val2 
WHEN NOT MATCHED THEN INSERT 
    VALUES (in_val1, in_val2, in_val3) 

В первом примере обновление делает индекса поиска. Он должен, чтобы обновить правую строку. Oracle открывает неявный курсор, и мы используем его для обертывания соответствующей вставки, чтобы мы знали, что вставка произойдет только тогда, когда ключ не существует. Но вставка - независимая команда, и она должна выполнить второй поиск. Я не знаю внутренней работы команды слияния, но поскольку команда представляет собой единое целое, Oracle могла бы выполнить правильную вставку или обновление с помощью единого индекса.

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

0

Копировать & пасты пример для upserting одной таблицы в другую, с MERGE:

CREATE GLOBAL TEMPORARY TABLE t1 
    (id VARCHAR2(5) , 
    value VARCHAR2(5), 
    value2 VARCHAR2(5) 
    ) 
    ON COMMIT DELETE ROWS; 

CREATE GLOBAL TEMPORARY TABLE t2 
    (id VARCHAR2(5) , 
    value VARCHAR2(5), 
    value2 VARCHAR2(5)) 
    ON COMMIT DELETE ROWS; 
ALTER TABLE t2 ADD CONSTRAINT PK_LKP_MIGRATION_INFO PRIMARY KEY (id); 

insert into t1 values ('a','1','1'); 
insert into t1 values ('b','4','5'); 
insert into t2 values ('b','2','2'); 
insert into t2 values ('c','3','3'); 


merge into t2 
using t1 
on (t1.id = t2.id) 
when matched then 
    update set t2.value = t1.value, 
    t2.value2 = t1.value2 
when not matched then 
    insert (t2.id, t2.value, t2.value2) 
    values(t1.id, t1.value, t1.value2); 

select * from t2 

Результат:

  1. б 4 5
  2. C 3 3
Смежные вопросы