вставить если не существует оракул - PullRequest
42 голосов
/ 09 ноября 2009

Мне нужно иметь возможность выполнить запрос Oracle, который идет на вставку ряда строк, но он также проверяет, существует ли первичный ключ, и если он существует, то пропускает эту вставку. Что-то вроде:

INSERT ALL
    IF NOT EXISTS( SELECT 1 WHERE fo.primary_key='bar' )
    (
        INSERT INTO 
            schema.myFoo fo ( primary_key, value1, value2 )
        VALUES
            ('bar','baz','bat')
    ),

    IF NOT EXISTS( SELECT 1 WHERE fo.primary_key='bar1' )
    (
        INSERT INTO 
            schema.myFoo fo ( primary_key, value1, value2 )
        VALUES
            ('bar1','baz1','bat1')
    )
SELECT * FROM schema.myFoo;

Возможно ли это вообще с Oracle?

Бонусные баллы, если вы можете сказать мне, как это сделать в PostgreSQL или MySQL.

Ответы [ 10 ]

36 голосов
/ 25 февраля 2011

Опаздывает на вечеринку, но ...

В Oracle 11.2.0.1 есть семантическая подсказка , которая может сделать это: IGNORE_ROW_ON_DUPKEY_INDEX

Пример:

insert /*+ IGNORE_ROW_ON_DUPKEY_INDEX(customer_orders,pk_customer_orders) */
  into customer_orders
       (order_id, customer, product)
values (    1234,     9876,  'K598')
     ;

ОБНОВЛЕНИЕ : Хотя эта подсказка работает (если вы правильно написали ее), существуют лучшие подходы , для которых не требуется Oracle 11R2:

Первый подход - прямой перевод вышеуказанной семантической подсказки:

begin
  insert into customer_orders
         (order_id, customer, product)
  values (    1234,     9876,  'K698')
  ;
  commit;
exception
  when DUP_VAL_ON_INDEX
  then ROLLBACK;
end;

Второй подход - лот быстрее, чем оба вышеупомянутых намека, когда много споров:

begin
    select count (*)
    into   l_is_matching_row
    from   customer_orders
    where  order_id = 1234
    ;

    if (l_is_matching_row = 0)
    then
      insert into customer_orders
             (order_id, customer, product)
      values (    1234,     9876,  'K698')
      ;
      commit;
    end if;
exception
  when DUP_VAL_ON_INDEX
  then ROLLBACK;
end;
24 голосов
/ 09 ноября 2009

Заявление называется MERGE. Посмотри, мне лень.

Остерегайтесь, однако, что MERGE не атомарен, что может вызвать следующий эффект (спасибо, Мариус):

SESS1:

create table t1 (pk int primary key, i int);
create table t11 (pk int primary key, i int);
insert into t1 values(1, 1);
insert into t11 values(2, 21);
insert into t11 values(3, 31);
commit;

SESS2: insert into t1 values(2, 2);

SESS1:

MERGE INTO t1 d
USING t11 s ON (d.pk = s.pk)
WHEN NOT MATCHED THEN INSERT (d.pk, d.i) VALUES (s.pk, s.i);

SESS2: commit;

SESS1: ORA-00001

17 голосов
/ 18 марта 2011

Вставляется, только если вставляемый элемент отсутствует.

Работает так же, как:

if not exists (...) insert ... 

в T-SQL

insert into destination (DESTINATIONABBREV) 
  select 'xyz' from dual 
  left outer join destination d on d.destinationabbrev = 'xyz' 
  where d.destinationid is null;

может не быть красивым, но это удобно :) 1009 *

10 голосов
/ 24 февраля 2016

Мы можем объединить ДВОЙНОЙ и НЕ СУЩЕСТВУЮЩИЙ для архивирования ваших требований:

INSERT INTO schema.myFoo ( 
    primary_key, value1, value2
) 
SELECT
    'bar', 'baz', 'bat' 
FROM DUAL
WHERE NOT EXISTS (
    SELECT 1 
    FROM schema.myFoo
    WHERE primary_key = 'bar'
);
10 голосов
/ 23 апреля 2013

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

MERGE INTO TABLE1 a
    USING DUAL
    ON (a.C1_pk= 6)
WHEN NOT MATCHED THEN
    INSERT(C1_pk, C2,C3,C4)
    VALUES (6, 1,0,1);
5 голосов
/ 09 ноября 2009

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

Вставить все данные во временную таблицу, скажем, T с той же структурой, что и myFoo

Тогда

insert myFoo
  select *
     from t
       where t.primary_key not in ( select primary_key from myFoo) 

Это должно работать и на других базах данных - я сделал это на Sybase

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

4 голосов
/ 13 июня 2012
DECLARE
   tmp NUMBER(3,1);
BEGIN
  SELECT COUNT(content_id) INTO tmp FROM contents WHERE (condition);
  if tmp != 0 then
    INSERT INTO contents VALUES (...);
  else
    INSERT INTO contents VALUES (...);
  end if;
END;

Я использовал код выше. Это долго, но, просто и сработало для меня. Похоже на код Майкла.

0 голосов
/ 25 ноября 2015

INSERT INTO schema.myFoo ( primary_key        , value1          , value2         )
                         SELECT 'bar1' AS primary_key ,'baz1' AS value1 ,'bat1' AS value2 FROM DUAL WHERE (SELECT 1 AS value FROM schema.myFoo WHERE LOWER(primary_key) ='bar1' AND ROWNUM=1) is null;
0 голосов
/ 28 октября 2013

Если ваша таблица «независима» от других (я имею в виду, что она не вызовет каскадное удаление или не установит нулевые отношения внешних ключей), хорошим трюком может быть сначала УДАЛИТЬ строку, а затем ВСТАВИТЬ ее снова , Это может пойти так:

УДАЛИТЬ ИЗ MyTable, ГДЕ prop1 = 'aaa'; // при условии, что он выберет не более одной строки!

INSERT INTO MyTable (prop1, ...) VALUES ('aaa', ...);

Если вы удаляете что-то, что не существует, ничего не произойдет.

0 голосов
/ 10 ноября 2009

Это ответ на комментарий, размещенный erikkallen:

Вам не нужна временная таблица. если ты только несколько строк, (ВЫБРАТЬ 1 ИЗ СОЮЗ ВЫБРАТЬ 2 ИЗ двойного) будет делать. Почему бы ваш пример дать ORA-0001? Не слиться, взять обновить блокировку на ключ индекса, а не продолжайте, пока Sess1 не совершено или отменено? - Эриккален

Ну, попробуйте сами и скажите мне, получаете ли вы ту же ошибку или нет:

SESS1:

create table t1 (pk int primary key, i int);
create table t11 (pk int primary key, i int);
insert into t1 values(1, 1);
insert into t11 values(2, 21);
insert into t11 values(3, 31);
commit;

SESS2: insert into t1 values(2, 2);

SESS1:

MERGE INTO t1 d
USING t11 s ON (d.pk = s.pk)
WHEN NOT MATCHED THEN INSERT (d.pk, d.i) VALUES (s.pk, s.i);

SESS2: commit;

SESS1: ORA-00001

...