【问题标题】:Oracle: how to INSERT if a row doesn't existOracle:如果行不存在如何插入
【发布时间】:2011-04-19 23:29:17
【问题描述】:

如果行不存在,在 PL/SQL (oracle) 中插入行的最简单方法是什么?

我想要类似的东西:

IF NOT EXISTS (SELECT * FROM table WHERE name = 'jonny') THEN
  INSERT INTO table VALUES ("jonny", null);
END IF;

但它不起作用。

注意:此表有 2 个字段,例如 nameage。但是只有name才是PK。

【问题讨论】:

  • 您是否期望 INSERT 语句通常是必需的(即该行通常不存在)?或者该行通常会存在?
  • @justin:该行通常不存在。
  • 酷。那么这里的三个选项中的任何一个都应该适合你。

标签: oracle plsql


【解决方案1】:

您应该使用合并: 例如:

MERGE INTO employees e
    USING (SELECT * FROM hr_records WHERE start_date > ADD_MONTHS(SYSDATE, -1)) h
    ON (e.id = h.emp_id)
  WHEN MATCHED THEN
    UPDATE SET e.address = h.address
  WHEN NOT MATCHED THEN
    INSERT (id, address)
    VALUES (h.emp_id, h.address);

MERGE INTO employees e
    USING hr_records h
    ON (e.id = h.emp_id)
  WHEN MATCHED THEN
    UPDATE SET e.address = h.address
  WHEN NOT MATCHED THEN
    INSERT (id, address)
    VALUES (h.emp_id, h.address);

https://oracle-base.com/articles/9i/merge-statement

【讨论】:

    【解决方案2】:

    CTE 并且只有 CTE :-)

    只是扔掉多余的东西。这里几乎是所有生活案例的完整而详细的表格。你可以使用任何简洁的形式。

    INSERT INTO reports r
      (r.id, r.name, r.key, r.param)
    

    --

      -- Invoke this script from "WITH" to the end (";")
      -- to debug and see prepared values.
      WITH
    
      -- Some new data to add.
      newData AS(
              SELECT 'Name 1' name, 'key_new_1' key FROM DUAL
        UNION SELECT 'Name 2' NAME, 'key_new_2' key FROM DUAL
        UNION SELECT 'Name 3' NAME, 'key_new_3' key FROM DUAL
        ),
      -- Any single row for copying with each new row from "newData",
      -- if you will of course.
      copyData AS(
          SELECT r.*
          FROM reports r
          WHERE r.key = 'key_existing'
            -- ! Prevent more than one row to return.
            AND FALSE -- do something here for than!
        ),
      -- Last used ID from the "reports" table (it depends on your case).
      -- (not going to work with concurrent transactions)
      maxId AS (SELECT MAX(id) AS id FROM reports),
    

    --

      -- Some construction of all data for insertion.
      SELECT maxId.id + ROWNUM, newData.name, newData.key, copyData.param
      FROM copyData
        -- matrix multiplication :)
        -- (or a recursion if you're imperative coder)
        CROSS JOIN newData
        CROSS JOIN maxId
    

    --

      -- Let's prevent re-insertion.
      WHERE NOT EXISTS (
          SELECT 1 FROM reports rs
          WHERE rs.name IN(
            SELECT name FROM newData
          ));
    

    我在类固醇上称之为“IF NOT EXISTS”。所以,这对我有帮助,而且我大多这样做。

    【讨论】:

    • SELECT MAX(id) AS id FROM reports 不适用于并发事务
    • 你能提供点什么吗?我只是不太了解所有的东西。
    【解决方案3】:

    对于要确保目标表中存在一行的情况(尤其是当您有两列作为主键时),我发现这些示例有点难以遵循,但主键可能根本不存在所以没有什么可以选择的。

    这对我有用:

    MERGE INTO table1 D
        USING (
            -- These are the row(s) you want to insert.
            SELECT 
            'val1' AS FIELD_A,
            'val2' AS FIELD_B
            FROM DUAL
    
        ) S ON (
            -- This is the criteria to find the above row(s) in the
            -- destination table.  S refers to the rows in the SELECT
            -- statement above, D refers to the destination table.
            D.FIELD_A = S.FIELD_A
            AND D.FIELD_B = S.FIELD_B
        )
    
        -- This is the INSERT statement to run for each row that
        -- doesn't exist in the destination table.
        WHEN NOT MATCHED THEN INSERT (
            FIELD_A,
            FIELD_B,
            FIELD_C
        ) VALUES (
            S.FIELD_A,
            S.FIELD_B,
            'val3'
        )
    

    重点是:

    • USING 块内的SELECT 语句必须始终返回行。如果此查询没有返回任何行,则不会插入或更新任何行。这里我从DUAL 中选择,所以总是只有一行。
    • ON 条件用于设置匹配行的条件。如果 ON 没有匹配项,则运行 INSERT 语句。
    • 如果您还想更好地控制更新,还可以添加WHEN MATCHED THEN UPDATE 子句。

    【讨论】:

      【解决方案4】:

      除了到目前为止给出的完美有效的答案外,还有ignore_row_on_dupkey_index 提示您可能想要使用:

      create table tq84_a (
        name varchar2 (20) primary key,
        age  number
      );
      
      insert /*+ ignore_row_on_dupkey_index(tq84_a(name)) */ into tq84_a values ('Johnny',   77);
      insert /*+ ignore_row_on_dupkey_index(tq84_a(name)) */ into tq84_a values ('Pete'  ,   28);
      insert /*+ ignore_row_on_dupkey_index(tq84_a(name)) */ into tq84_a values ('Sue'   ,   35);
      insert /*+ ignore_row_on_dupkey_index(tq84_a(name)) */ into tq84_a values ('Johnny', null);
      
      select * from tq84_a;
      

      提示在Tahiti上描述。

      【讨论】:

        【解决方案5】:

        您可以使用以下语法:

        INSERT INTO table_name ( name, age )
        select  'jonny', 18 from dual
        where not exists(select 1 from table_name where name = 'jonny');
        

        如果它打开一个弹出询问作为“输入替换变量”然后在上述查询之前使用它:

        set define off;
        INSERT INTO table_name ( name, age )
        select  'jonny', 18 from dual
        where not exists(select 1 from table_name where name = 'jonny');
        

        【讨论】:

        • 这与三年前发布的公认答案有何不同?
        【解决方案6】:
        INSERT INTO table
        SELECT 'jonny', NULL
          FROM dual -- Not Oracle? No need for dual, drop that line
         WHERE NOT EXISTS (SELECT NULL -- canonical way, but you can select
                                       -- anything as EXISTS only checks existence
                             FROM table
                            WHERE name = 'jonny'
                          )
        

        【讨论】:

        • @Jeff Walker: See this question
        • dual 是 Oracle 中的一列一行的虚拟表。这很糟糕(在 SQLite 中,您可以只选择不使用 from,在 Oracle 中,您必须在无处选择时使用 dual)。
        • -1 当多个会话尝试同时插入同一行时,这将不起作用 - 在他们提交之前,两者都不会看到另一个会话的数据,此时为时已晚。最好的解决方案是应用唯一约束。
        • 迟来的评论:@JeffreyKemp,有些用例不需要担心同时会话。
        • @JustinSkiles,这些用例是特殊情况,与这个问题无关。当设计人员决定不必“担心” DBMS 的一个基本特性(在本例中为并发)时,这很可能会在以后对他们的客户产生影响。
        【解决方案7】:

        使用@benoit 的部分答案,我将使用这个:

        DECLARE
            varTmp NUMBER:=0;
        BEGIN
            -- checks
            SELECT nvl((SELECT 1 FROM table WHERE name = 'john'), 0) INTO varTmp FROM dual;
        
            -- insert
            IF (varTmp = 1) THEN
                INSERT INTO table (john, null)
            END IF;
        
        END;
        

        抱歉,我没有使用任何完整的给定答案,但我需要IF 检查,因为我的代码比这个带有姓名和年龄字段的示例表复杂得多。我需要一个非常清晰的代码。好的,谢谢,我学到了很多!我会接受@benoit 的回答。

        【讨论】:

          【解决方案8】:

          假设您使用的是 10g,您还可以使用 MERGE 语句。这允许您插入不存在的行,如果存在则忽略该行。人们在想要执行“更新插入”时倾向于考虑 MERGE(如果行不存在则插入,如果行存在则更新),但 UPDATE 部分现在是可选的,因此也可以在这里使用。

          SQL> create table foo (
            2    name varchar2(10) primary key,
            3    age  number
            4  );
          
          Table created.
          
          SQL> ed
          Wrote file afiedt.buf
          
            1  merge into foo a
            2    using (select 'johnny' name, null age from dual) b
            3       on (a.name = b.name)
            4   when not matched then
            5    insert( name, age)
            6*    values( b.name, b.age)
          SQL> /
          
          1 row merged.
          
          SQL> /
          
          0 rows merged.
          
          SQL> select * from foo;
          
          NAME              AGE
          ---------- ----------
          johnny
          

          【讨论】:

            【解决方案9】:

            如果 name 是 PK,则只需插入并捕获错误。这样做而不是任何检查的原因是,即使有多个客户端同时插入,它也可以工作。如果您检查然后插入,则必须在此期间保持锁定,否则无论如何都会出现错误。

            这个代码是这样的

            BEGIN
              INSERT INTO table( name, age )
                VALUES( 'johnny', null );
            EXCEPTION
              WHEN dup_val_on_index
              THEN
                NULL; -- Intentionally ignore duplicates
            END;
            

            【讨论】:

            • 代码:BEGIN INSERT INTO table VALUES('jonny', null);EXCEPTION WHEN sqlcode != -1 THEN RAISE;END; / sqlcode = -1 当 ORA-00001
            • 尝试插入并捕获异常是否有意义取决于您希望插入成功的频率。如果 99% 的时间你插入了一个非重复的值并且它只会出错 1% 的时间,那么捕获并忽略异常是一个不错的选择。如果 99% 的时间该行已经存在,那么从性能角度来看,捕获异常可能会有问题。
            • 此外,merge 方法适用于插入中的多行...选择不适用的。 (我知道 OP 以单行为例,但对于这个(以及 Justin Cave 提出的性能问题)我认为合并是一个更好的解决方案。
            猜你喜欢
            • 2011-03-10
            • 2019-07-13
            • 2010-12-14
            • 1970-01-01
            • 2020-02-04
            • 1970-01-01
            • 1970-01-01
            • 2018-09-17
            • 1970-01-01
            相关资源
            最近更新 更多