【问题标题】:Insert if not exists Oracle如果不存在则插入 Oracle
【发布时间】:2025-11-27 17:05:01
【问题描述】:

我需要能够运行 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 中做到这一点,可以加分。

【问题讨论】:

标签: sql oracle oracle-sqldeveloper


【解决方案1】:

我们可以结合DUALNOT EXISTS来满足您的要求:

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

【讨论】:

    【解决方案2】:

    如果您不想从其他表中合并,而是要插入新数据...我想出了这个。有没有更好的方法来做到这一点?

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

    【讨论】:

      【解决方案3】:

      插入到 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) 为空;

      【讨论】:

        【解决方案4】:

        如果您的表与其他表“独立”(我的意思是,它不会触发级联删除或不会将任何外键关系设置为空),一个不错的技巧可能是首先删除该行然后再次插入它.它可以是这样的:

        从 MyTable 中删除 prop1 = 'aaa'; //假设它最多会选择一行!

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

        如果你要删除不存在的东西,什么都不会发生。

        【讨论】:

        • 有没有办法检查某行是否存在。
        【解决方案5】:
        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;
        

        我使用了上面的代码。它很长,但是很简单,对我有用。类似于 Micheal 的代码。

        【讨论】:

          【解决方案6】:

          聚会迟到了,但是……

          在 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')
               ;
          

          更新:虽然这个提示有效(如果你拼写正确的话),有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
          • 给那些想要访问通过静态 SQL(不是 PL/SQL,它是动态 SQL)创建的对象的提示。例如,如果您想从您在 PL/SQL “BEGIN END;”之外创建的序列中获取 NEXTVAL;堵塞。执行“DECLARE seq_value NUMBER;”然后“SELECT employees_seq.NEXTVAL INTO seq_value FROM dual;” docs.oracle.com/cd/B19306_01/appdev.102/b14261/…
          • 你说第二种方法更快。是否有任何数字可以支持您的主张?
          • @user3454439 加速取决于您的用例;如果你没有那么多的争论,那么你甚至可能不在乎。个人资料!
          【解决方案7】:

          仅当要插入的项目不存在时才插入。

          与以下内容相同:

          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;
          

          可能不漂亮,但很方便:)

          【讨论】:

          • 或类似,存在:insert into destination select 'id', 'xyz' from dual where not exists (select id from destination where id = 'id')
          • Robinst 解决方案(评论)在我看来是最好的
          • code if not exists (...) insert ... code Error(22,16): PLS-00204: function or pseudo-column 'EXISTS' may be used inside a仅 SQL 语句
          • 我认为 robinst 解决方案不是原子的,如果你需要的话
          【解决方案8】:

          该语句称为 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

          【讨论】:

          • 再一次,在不锁定表(或先锁定主记录)的情况下,有一场比赛。此方法始终需要使用临时表。我不会说这是错误的,但有时可能太多了。
          • 不,合并应该是原子的。
          • 是的,MERGE 本身是原子的。但是... Sess1: INSERT pk=1 INTO myFoo; Sess2: MERGE INTO myFoo d USING tmpTable s ON (d.pk = s.pk)... Sess1: COMMIT; Sess2:ORA-00001;对于插入的行数很少的情况,使用临时表确实没有意义。一切都有其价格,CREATE TABLE 和 MERGE 并不便宜(查看所需的闩锁/锁等)。
          • 您不需要临时表。如果您只有几行,(SELECT 1 FROM dual UNION SELECT 2 FROM dual)就可以了。为什么你的例子会给出 ORA-0001?在 Sess1 提交或回滚之前,merge 不会在索引键上获取更新锁并且不会继续吗?
          • 埃里克,请看下面的答案。没有足够的空间将其作为评论发布,也没有任何可用的格式。
          【解决方案9】:

          这是对 erikkallen 发表的评论的回答:

          您不需要临时表。如果你 只有几行,(SELECT 1 FROM dual UNION SELECT 2 FROM dual) 将 做。为什么你的例子会给 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

          【讨论】:

          • 我无法访问Oracle,所以我无法尝试,但我相信你。不过,我确实认为这应该被视为一个错误。
          • 这是在读取提交事务隔离级别下的正确行为,而不是错误。 MERGE 的行为与不影响任何行的更新行为完全一致,然后尝试插入。
          • @David:我意识到这些东西是等价的,但我想知道有多少人知道这一点。我当然没有,我真的希望它能毫无问题地工作。如果我想要一个不插入行的 INSERT 的语义,然后是一个 UPDATE,然后我写一个 INSERT,然后是一个 UPDATE。
          • 为什么是-1?它(显然)是正确的,它教会了我一些东西。 +1。
          • -1 因为它没有回答问题,尽管我同意这是有趣的信息。您可以将信息添加到您的答案中,或者制作您的社区 wiki,以便其他人可以。此外,它当然不应该被视为一个错误。
          【解决方案10】:

          如果代码在客户端,那么您需要多次访问服务器以消除这种情况。

          将所有数据插入到一个临时表中,比如 T,其结构与 myFoo 相同

          然后

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

          这也应该适用于其他数据库 - 我已经在 Sybase 上完成了此操作

          如果要插入的新数据很少,因为您已经通过网络复制了所有数据。

          【讨论】:

          • 绝对是砍刀+1。我必须先创建一个临时表,但这确实不是一个可怕的困难。
          • 以这种方式插入很慢...... BULK COLLECT 是一个更好的选择...... google it :) 它需要一些 pl-sql,但它比从选择中盲目插入要快得多。
          最近更新 更多