Oracle: как сделать UPSERT (обновить или вставить в таблицу?) - PullRequest
262 голосов
/ 26 октября 2008

Операция 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, как лучше всего это сделать?

Ответы [ 12 ]

198 голосов
/ 26 октября 2008

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

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
96 голосов
/ 22 апреля 2010

Двойной пример, приведенный выше, в 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, и выполнение вставки, если оно было.

46 голосов
/ 27 октября 2008

Альтернатива 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;   
43 голосов
/ 28 октября 2008

Еще один вариант без проверки исключений:

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;
25 голосов
/ 23 января 2014
  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;
22 голосов
/ 01 апреля 2014

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

В качестве примера, вот как код 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; 

N.B. В режиме транзакции SERIALIZABLE, который я не рекомендую, кстати, вы можете столкнуться с ORA-08177: невозможно сериализовать доступ для этой транзакции исключения вместо этого.

19 голосов
/ 25 декабря 2012

Я хотел бы получить ответ Grommit, за исключением того, что для него требуются значения двойных значений Я нашел решение, где оно может появиться один раз: 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); 
8 голосов
/ 26 ноября 2011

Примечание относительно двух предлагаемых решений:

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

или

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

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

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

7 голосов
/ 12 января 2015

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

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 голосов
/ 09 октября 2017

Пример копирования и вставки для переноса одной таблицы в другую с помощью 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. b 4 5
  2. с 3 3
  3. a 1 1
...