2010-10-01 49 views
38

在PL/SQL(oracle)中插入行如果不存在,最簡單的方法是什麼?Oracle:如果行不存在,如何插入

我想是這樣的:

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

但它不工作。

注:此表中有2場,也就是說,年齡。但只有名稱是PK。

+0

您是否期望INSERT語句通常是必需的(即該行通常不會存在)?或者該行通常會存在? – 2010-10-01 17:39:24

+0

@justin:行一般不會存在。 – Topera 2010-10-01 17:50:51

+0

很酷。那麼這裏的三個選項中的任何一個都應該適合你。 – 2010-10-01 18:01:11

回答

59
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' 
       ) 
+1

這段代碼中的「dual」是什麼?我肯定錯過了什麼。 – 2010-10-01 17:38:26

+0

@Jeff Walker:[看到這個問題](http://stackoverflow.com/questions/3732422/select-from-nothing/3732466#3732466) – 2010-10-01 17:40:15

+9

dual是Oracle中的一個虛擬表,有一列和一行。這是不好的(在SQLite中,你可以直接選擇,而在Oracle中,你必須從無處選擇時使用雙重)。 – Benoit 2010-10-01 17:56:28

14

如果name是一個PK,那麼只需插入並捕獲錯誤。這樣做而不是任何檢查的原因是,即使多個客戶端同時插入,它也可以工作。如果您檢查並插入,則必須在此期間持有鎖定,否則將會出現錯誤。

該代碼,這會是這樣的

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

代碼:'開始插入表值('jonny',null);例外時sqlcode!= -1然後升起;結束; /' sqlcode = -1當ORA-00001 – Benoit 2010-10-01 17:18:54

+3

嘗試插入和捕獲異常是否合理取決於您期望INSERT成功的頻率。如果99%的時間你插入一個非重複的值,並且它只會錯誤1%的時間,捕捉和忽略異常是一個不錯的選擇。如果行已經存在的時間爲99%,從性能角度來看,捕獲異常可能會產生問題。 – 2010-10-01 17:42:40

+0

另外,合併方法可以在插入中使用多行...選擇哪一個不這樣做。 (我知道OP以單行爲例,但爲此(以及Justin Cave選址的性能問題),我認爲合併是一個更好的解決方案。 – Adam 2014-04-22 12:24:43

27

假設你是在10g中,您還可以使用MERGE語句。這使您可以在行不存在的情況下插入行,並在行存在時忽略該行。當他們想要做一個「upsert」時,人們傾向於想到MERGE(INSERT如果該行不存在,則更新該行是否存在),但UPDATE部分現在是可選的,因此它也可以在這裏使用。 @benoit答案

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

使用的部分,我會用這樣的:

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的答案。

1

你可以使用這個語法:

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

這與三年前公佈的已接受答案有什麼不同? – Mat 2013-08-09 11:22:25

3

除到目前爲止給出的完美和有效的答案,還有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

我發現這些例子有點棘手,需要確保目標表中存在一行(特別是當您有兩列作爲主鍵時),但主鍵可能不存在所有這一切都沒有選擇。

這是對我工作:

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子句。
0

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」 類固醇。所以,這有助於我和我主要這樣做。

+1

'SELECT MAX(id)AS id FROM FROM reports'不能用於併發事務 – 2016-09-26 13:58:41

+0

你能提供一些東西嗎?我只是不知道所有的東西, – it3xl 2016-09-26 14:02:16

+0

,當然我知道序列) – it3xl 2016-09-26 14:12:06