2009-11-09 3 views
38

Мне нужно иметь возможность запускать запрос Oracle, который идет на вставку ряда строк, но также проверяет, существует ли первичный ключ и если он это делает, тогда он пропускает эту вставку. Что-то вроде:Вставить, если не существует 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.

+0

Мне нравится использовать КТР - Common Table Expressions [Oracle: как INSERT, если строка не существует ] (http://stackoverflow.com/questions/3841441/oracle-how-to-insert-if-a-row-doesnt-exist/39704301#39704301) – it3xl

ответ

21

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

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

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

+1

Опять же, без блокировки стола (или основной записи), есть гонка. Этот метод всегда требует использования таблицы temp. Я бы не сказал, что это неправильно, но иногда может быть слишком много. –

+0

Нет, слияние должно быть атомарным. – erikkallen

+1

Да, сам MERGE является атомарным. Но ... Sess1: INSERT pk = 1 INTO myFoo; Sess2: MERGE INTO myFoo d ИСПОЛЬЗОВАНИЕ tmpTable s ON (d.pk = s.pk) ... Sess1: COMMIT; Sess2: ORA-00001; В случаях, когда количество вставленных строк низкое, на самом деле не имеет смысла использовать временную таблицу. Все имеет свою цену, а CREATE TABLE и MERGE не дешевы (посмотрите на необходимые защелки/замки и т. П.). –

5

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

Вставьте все данные в виде temportary таблицы говорят T с той же структурой, как MYFOO

Тогда

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

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

Не самое лучшее, если вставлять новые данные только в том случае, если вы скопировали все данные по проводу.

+0

Определенно кливер +1. Сначала мне нужно создать временную таблицу, но это действительно не страшная трудность. – cwallenpoole

+0

корректировка * умный *. – cwallenpoole

+0

Вставка этого способа является SLOW .... BULK COLLECT - это намного лучший вариант ... google it :) Для этого требуется некоторый pl-sql, но он бесконечно быстрее, чем слепая вставка из select. – Matt

0

Это ответ на комментарий разместил erikkallen:

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

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

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

+0

У меня нет доступа к Oracle, поэтому я не могу попробовать, но я верю вам. Я действительно думаю, что это следует считать ошибкой. – erikkallen

+2

Это правильное поведение в соответствии с уровнем изоляции транзакционной записи, а не ошибкой. поведение MERGE полностью согласуется с поведением обновления, которое не влияет на строки, за которыми следует попытка вставки. –

+0

@ Давид: Я понимаю, что эти вещи эквивалентны, но мне интересно, сколько людей это знают. Я уверен, что нет, и я действительно ожидал, что он будет работать без проблем.Если я хочу семантику INSERT, которая не вставляет строки, то UPDATE, тогда я пишу INSERT, а затем UPDATE. – erikkallen

35

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

С оракула 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') 
    ; 

UPDATE: Хотя этот намек работает (если оно пишется правильно), есть better approaches, которые не требуют 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; 
+1

Мне нравится второй подход, потому что ясно и легко понять, что он пытается сделать. –

+0

Ищете альтернативу тому, как я решал эту ситуацию, я наткнулся на ваш ответ, который подтверждает, что мой подход был правильным. Я реализовал второй подход, и это ясно и быстро! +1 – 2015-02-10 07:48:41

+0

Совет для тех, кто хочет получить доступ к объектам, созданным через статический SQL (не PL/SQL, который является динамическим SQL). Например, если вы хотите получить NEXTVAL из последовательности, которую вы создали за пределами PL/SQL «BEGIN END»; блок. «DECLARE seq_value NUMBER;» и затем «SELECT employees_seq.NEXTVAL INTO seq_value FROM dual;» https://docs.oracle.com/cd/B19306_01/appdev.102/b14261/sqloperations.htm#i7112 –

16

Это только вставляет, если элемент, который необходимо вставить, еще не присутствует.

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

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; 

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

+12

Или аналогичный, с существует: 'insert into destination select 'id', 'xyz' из dual, где не существует (выберите id из адресата, где id = 'id') ' – robinst

+1

Решение Robinst (комментарий) является лучшим на мой взгляд –

+0

nice robinst ... perfect! Большое спасибо – KingRider

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

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

8

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

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

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

УДАЛИТЬ ОТ MyTable WHERE prop1 = 'aaa'; // предполагая, что он выберет не более одной строки!

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

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

+0

есть ли способ проверить, существует ли какая-либо строка. –

0

 
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; 

8

Мы можем объединить DUAL и НЕ СУЩЕСТВУЕТ архивировать ваше требование:

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' 
); 
Смежные вопросы