แทรก SQL Server ถ้าไม่มีอยู่


243

ฉันต้องการแทรกข้อมูลลงในตารางของฉัน แต่แทรกเฉพาะข้อมูลที่ไม่มีอยู่ในฐานข้อมูลของฉัน

นี่คือรหัสของฉัน:

ALTER PROCEDURE [dbo].[EmailsRecebidosInsert]
  (@_DE nvarchar(50),
   @_ASSUNTO nvarchar(50),
   @_DATA nvarchar(30) )
AS
BEGIN
   INSERT INTO EmailsRecebidos (De, Assunto, Data)
   VALUES (@_DE, @_ASSUNTO, @_DATA)
   WHERE NOT EXISTS ( SELECT * FROM EmailsRecebidos 
                   WHERE De = @_DE
                   AND Assunto = @_ASSUNTO
                   AND Data = @_DATA);
END

และข้อผิดพลาดคือ:

ข่าวสารเกี่ยวกับ 156, ระดับ 15, สถานะ 1, อีเมลกระบวนงาน, รายงานบรรทัดที่ 11,
ไวยากรณ์ที่ไม่ถูกต้องใกล้กับคำหลัก 'WHERE'


10
คุณไม่ควรพึ่งพาการตรวจสอบนี้เพียงอย่างเดียวเพื่อให้แน่ใจว่าไม่มีการซ้ำซ้อนมันไม่ปลอดภัยสำหรับเธรดและคุณจะได้รับการทำซ้ำเมื่อพบสภาพการแข่งขัน หากคุณต้องการข้อมูลที่ไม่ซ้ำจริงๆให้เพิ่มข้อ จำกัด ที่ไม่ซ้ำกันลงในตารางจากนั้นตรวจพบข้อผิดพลาดการละเมิดข้อ จำกัด ที่ไม่ซ้ำกัน ดูคำตอบนี้
GarethD

1
คุณสามารถใช้การค้นหาแบบ MERGE หรือถ้าไม่มี (เลือกคำสั่ง) เริ่มใส่ค่า END
Abdul Hannan Ijaz

ขึ้นอยู่กับสถานการณ์หากคุณควรส่งต่อหรือไม่ใช้เช็ค หากคุณกำลังพัฒนาสคริปต์การปรับใช้ที่เขียนข้อมูลไปยังตาราง "สแตติก" ตัวอย่างเช่นนี้จะไม่มีปัญหา
AxelWass

คุณสามารถใช้ "ถ้าไม่มีอยู่ (เลือก * จาก ... " เช่นstackoverflow.com/a/43763687/2736742
A. Morel

2
@GarethD: คุณหมายถึงอะไร "ไม่ด้ายปลอดภัย"? มันอาจจะไม่หรูหรา แต่มันดูถูกต้องสำหรับฉัน insertคำสั่งเดียวจะเป็นรายการเดียวเสมอ ไม่ใช่ว่า SQL Server จะประเมินคิวรีย่อยก่อนจากนั้นในบางจุดในภายหลังและหากไม่มีการล็อกก็จะทำการแทรกต่อไป
Ed Avis

คำตอบ:


322

แทนรหัสด้านล่าง

BEGIN
   INSERT INTO EmailsRecebidos (De, Assunto, Data)
   VALUES (@_DE, @_ASSUNTO, @_DATA)
   WHERE NOT EXISTS ( SELECT * FROM EmailsRecebidos 
                   WHERE De = @_DE
                   AND Assunto = @_ASSUNTO
                   AND Data = @_DATA);
END

แทนที่ด้วย

BEGIN
   IF NOT EXISTS (SELECT * FROM EmailsRecebidos 
                   WHERE De = @_DE
                   AND Assunto = @_ASSUNTO
                   AND Data = @_DATA)
   BEGIN
       INSERT INTO EmailsRecebidos (De, Assunto, Data)
       VALUES (@_DE, @_ASSUNTO, @_DATA)
   END
END

อัปเดต: (ขอบคุณ @Marc Durdin สำหรับการชี้)

โปรดทราบว่าภายใต้การโหลดสูงบางครั้งสิ่งนี้จะยังคงล้มเหลวเนื่องจากการเชื่อมต่อที่สองสามารถผ่านการทดสอบ IF NOT EXISTS ก่อนการเชื่อมต่อครั้งแรกจะเรียกใช้ INSERT นั่นคือสภาพการแข่งขัน ดูstackoverflow.com/a/3791506/1836776สำหรับคำตอบที่ดีว่าทำไมการห่อธุรกรรมจึงไม่สามารถแก้ไขได้


20
โปรดทราบว่าภายใต้การโหลดสูงบางครั้งสิ่งนี้จะยังคงล้มเหลวเนื่องจากการเชื่อมต่อที่สองสามารถผ่านการทดสอบ IF NOT EXISTS ก่อนการเชื่อมต่อครั้งแรกจะเรียกใช้ INSERT นั่นคือสภาพการแข่งขัน ดูที่stackoverflow.com/a/3791506/1836776สำหรับคำตอบที่ดีว่าทำไมการห่อของในธุรกรรมจึงไม่สามารถแก้ปัญหานี้ได้
Marc Durdin

11
เลือก 1 จาก EmailsRecebidos WHERE De = @_DE AND Assunto = @_ASSUNTO และ Data = @_DATA หากต้องการใช้ 1 แทน * จะมีประสิทธิภาพมากขึ้น
Reno

1
ใส่ล็อกการเขียนไว้รอบตัวทั้งหมดแล้วคุณจะไม่มีโอกาสได้ทำซ้ำ
Kevin Finkenbinder

10
@ jazzcat select *ในกรณีนี้ไม่ได้ทำให้ความแตกต่างใด ๆ เพราะมันถูกใช้ในEXISTSประโยค SQL Server จะปรับให้เหมาะสมเสมอและทำมานานแล้ว เนื่องจากฉันแก่มากฉันมักจะเขียนข้อความค้นหาเหล่านี้เพราะEXISTS (SELECT 1 FROM...)มันไม่จำเป็น
Loudenvier

16
ทำไมคำถามง่าย ๆ แบบนี้ทำให้เกิดความสงสัยมากกว่าความมั่นใจ?
drowa

77

สำหรับผู้ที่กำลังมองหาวิธีที่เร็วที่สุดฉันเพิ่งพบกับมาตรฐานเหล่านี้ที่เห็นได้ชัดว่าใช้ "INSERT SELECT ... ยกเว้นที่เลือก ... " กลายเป็นเร็วที่สุดสำหรับ 50 ล้านบันทึกหรือมากกว่า

นี่คือตัวอย่างโค้ดบางส่วนจากบทความ (บล็อคโค้ดที่ 3 นั้นเร็วที่สุด):

INSERT INTO #table1 (Id, guidd, TimeAdded, ExtraData)
SELECT Id, guidd, TimeAdded, ExtraData
FROM #table2
WHERE NOT EXISTS (Select Id, guidd From #table1 WHERE #table1.id = #table2.id)
-----------------------------------
MERGE #table1 as [Target]
USING  (select Id, guidd, TimeAdded, ExtraData from #table2) as [Source]
(id, guidd, TimeAdded, ExtraData)
    on [Target].id =[Source].id
WHEN NOT MATCHED THEN
    INSERT (id, guidd, TimeAdded, ExtraData)
    VALUES ([Source].id, [Source].guidd, [Source].TimeAdded, [Source].ExtraData);
------------------------------
INSERT INTO #table1 (id, guidd, TimeAdded, ExtraData)
SELECT id, guidd, TimeAdded, ExtraData from #table2
EXCEPT
SELECT id, guidd, TimeAdded, ExtraData from #table1
------------------------------
INSERT INTO #table1 (id, guidd, TimeAdded, ExtraData)
SELECT #table2.id, #table2.guidd, #table2.TimeAdded, #table2.ExtraData
FROM #table2
LEFT JOIN #table1 on #table1.id = #table2.id
WHERE #table1.id is null

6
ฉันชอบยกเว้นที่เลือก
ไบรอัน

1
ครั้งแรกที่ฉันได้ใช้ยกเว้น เรียบง่ายและสง่างาม
jhowe

แต่ข้อยกเว้นอาจไม่มีประสิทธิภาพสำหรับการใช้งานจำนวนมาก
Aasish Kr. Sharma

ยกเว้นจะไม่มีประสิทธิภาพ
Biswa

1
@Biswa: ไม่เป็นไปตามมาตรฐานเหล่านั้น รหัสสามารถใช้ได้จากเว็บไซต์ อย่าลังเลที่จะเรียกใช้บนระบบของคุณเพื่อดูว่าผลลัพธ์เปรียบเทียบกันอย่างไร

25

ฉันจะใช้การผสาน:

create PROCEDURE [dbo].[EmailsRecebidosInsert]
  (@_DE nvarchar(50),
   @_ASSUNTO nvarchar(50),
   @_DATA nvarchar(30) )
AS
BEGIN
   with data as (select @_DE as de, @_ASSUNTO as assunto, @_DATA as data)
   merge EmailsRecebidos t
   using data s
      on s.de = t.de
     and s.assunte = t.assunto
     and s.data = t.data
    when not matched by target
    then insert (de, assunto, data) values (s.de, s.assunto, s.data);
END

ฉันจะไปกับสิ่งนี้เพราะนักเล่นของมัน
jokab

ฉันชอบที่จะใช้การผสาน ... แต่มันไม่ทำงานสำหรับ Memory Optimized Tables
ดอนแซม

20

ลองรหัสด้านล่าง

ALTER PROCEDURE [dbo].[EmailsRecebidosInsert]
  (@_DE nvarchar(50),
   @_ASSUNTO nvarchar(50),
   @_DATA nvarchar(30) )
AS
BEGIN
   INSERT INTO EmailsRecebidos (De, Assunto, Data)
   select @_DE, @_ASSUNTO, @_DATA
   EXCEPT
   SELECT De, Assunto, Data from EmailsRecebidos
END

11

INSERTคำสั่งไม่ได้มีWHEREข้อ - คุณจะต้องมีการเขียนเช่นนี้

ALTER PROCEDURE [dbo].[EmailsRecebidosInsert]
  (@_DE nvarchar(50),
   @_ASSUNTO nvarchar(50),
   @_DATA nvarchar(30) )
AS
BEGIN
   IF NOT EXISTS (SELECT * FROM EmailsRecebidos 
                   WHERE De = @_DE
                   AND Assunto = @_ASSUNTO
                   AND Data = @_DATA)
   BEGIN
       INSERT INTO EmailsRecebidos (De, Assunto, Data)
       VALUES (@_DE, @_ASSUNTO, @_DATA)
   END
END

1
คุณต้องจัดการกับข้อผิดพลาดสำหรับขั้นตอนนี้เนื่องจากจะมีบางกรณีที่การแทรกเกิดขึ้นระหว่างการตรวจสอบและการแทรก
Filip De Vos

@FilipDeVos: จริง - ความเป็นไปได้อาจจะไม่มาก แต่ก็ยังเป็นไปได้ จุดดี.
marc_s

ถ้าคุณห่อทั้งสองอย่างไว้ในการทำธุรกรรม? นั่นจะปิดกั้นความเป็นไปได้หรือไม่? (ฉันไม่มีความเชี่ยวชาญในการทำธุรกรรมดังนั้นโปรดยกโทษให้ถ้านี่เป็นคำถามที่โง่)
David

1
ดูstackoverflow.com/a/3791506/1836776สำหรับคำตอบที่ดีว่าทำไมการทำธุรกรรมไม่สามารถแก้ไขได้ @David
Marc Durdin

ในคำสั่ง IF: ไม่จำเป็นต้องใช้ BEGIN & END หากจำนวนบรรทัดคำสั่งที่ต้องการเป็นเพียงหนึ่งแม้ว่าคุณจะใช้มากกว่าหนึ่งบรรทัดดังนั้นคุณสามารถตัดออกได้ที่นี่
Wessam El Mahdy

11

ฉันทำสิ่งเดียวกันกับ SQL Server 2012 และทำงานได้

Insert into #table1 With (ROWLOCK) (Id, studentId, name)
SELECT '18769', '2', 'Alex'
WHERE not exists (select * from #table1 where Id = '18769' and studentId = '2')

4
แน่นอนมันทำงานได้คุณกำลังใช้ตารางชั่วคราว (เช่นคุณไม่จำเป็นต้องกังวลเกี่ยวกับการเกิดพร้อมกันเมื่อใช้ตารางชั่วคราว)
drowa

6

ขึ้นอยู่กับรุ่นของคุณ (2012?) ของ SQL Server นอกเหนือจาก IF EXISTS คุณยังสามารถใช้MERGEดังนี้:

ALTER PROCEDURE [dbo].[EmailsRecebidosInsert]
    ( @_DE nvarchar(50)
    , @_ASSUNTO nvarchar(50)
    , @_DATA nvarchar(30))
AS BEGIN
    MERGE [dbo].[EmailsRecebidos] [Target]
    USING (VALUES (@_DE, @_ASSUNTO, @_DATA)) [Source]([De], [Assunto], [Data])
         ON [Target].[De] = [Source].[De] AND [Target].[Assunto] = [Source].[Assunto] AND [Target].[Data] = [Source].[Data]
     WHEN NOT MATCHED THEN
        INSERT ([De], [Assunto], [Data])
        VALUES ([Source].[De], [Source].[Assunto], [Source].[Data]);
END

2

SQL ที่แตกต่างกัน, หลักการเดียวกัน แทรกเฉพาะในกรณีที่ไม่ได้อยู่ในข้อ

INSERT INTO FX_USDJPY
            (PriceDate, 
            PriceOpen, 
            PriceLow, 
            PriceHigh, 
            PriceClose, 
            TradingVolume, 
            TimeFrame)
    SELECT '2014-12-26 22:00',
           120.369000000000,
           118.864000000000,
           120.742000000000,
           120.494000000000,
           86513,
           'W'
    WHERE NOT EXISTS
        (SELECT 1
         FROM FX_USDJPY
         WHERE PriceDate = '2014-12-26 22:00'
           AND TimeFrame = 'W')

-1

ตามที่อธิบายในรหัสด้านล่าง:ดำเนินการแบบสอบถามด้านล่างและยืนยันด้วยตัวเอง

CREATE TABLE `table_name` (
  `id` int(11) NOT NULL auto_increment,
  `name` varchar(255) NOT NULL,
  `address` varchar(255) NOT NULL,
  `tele` varchar(255) NOT NULL,
  PRIMARY KEY  (`id`)
) ENGINE=InnoDB;

แทรกบันทึก:

INSERT INTO table_name (name, address, tele)
SELECT * FROM (SELECT 'Nazir', 'Kolkata', '033') AS tmp
WHERE NOT EXISTS (
    SELECT name FROM table_name WHERE name = 'Nazir'
) LIMIT 1;
Query OK, 1 row affected (0.00 sec)
Records: 1 Duplicates: 0 Warnings: 0

SELECT * FROM `table_name`;

+----+--------+-----------+------+
| id | name   | address   | tele |
+----+--------+-----------+------+
|  1 | Nazir  | Kolkata   | 033  |
+----+--------+-----------+------+

ตอนนี้พยายามแทรกระเบียนเดียวกันอีกครั้ง:

INSERT INTO table_name (name, address, tele)
SELECT * FROM (SELECT 'Nazir', 'Kolkata', '033') AS tmp
WHERE NOT EXISTS (
    SELECT name FROM table_name WHERE name = 'Nazir'
) LIMIT 1;

Query OK, 0 rows affected (0.00 sec)
Records: 0  Duplicates: 0  Warnings: 0

+----+--------+-----------+------+
| id | name   | address   | tele |
+----+--------+-----------+------+
|  1 | Nazir  | Kolkata   | 033  |
+----+--------+-----------+------+

แทรกบันทึกอื่น:

INSERT INTO table_name (name, address, tele)
SELECT * FROM (SELECT 'Santosh', 'Kestopur', '044') AS tmp
WHERE NOT EXISTS (
    SELECT name FROM table_name WHERE name = 'Santosh'
) LIMIT 1;

Query OK, 1 row affected (0.00 sec)
Records: 1 Duplicates: 0 Warnings: 0

SELECT * FROM `table_name`;

+----+--------+-----------+------+
| id | name   | address   | tele |
+----+--------+-----------+------+
|  1 | Nazir  | Kolkata   | 033  |
|  2 | Santosh| Kestopur  | 044  |
+----+--------+-----------+------+

1
ไม่ใช่สิ่งนี้สำหรับ MySQL และคำถามนี้สำหรับ SQL Server หรือไม่
Douglas Gaskell

ใช่สำหรับ MySQL
vadiraj jahagirdar

-2

คุณสามารถใช้GOคำสั่ง ที่จะรีสตาร์ทการประมวลผลคำสั่ง SQL หลังจากข้อผิดพลาด ในกรณีของฉันฉันมีคำสั่ง INSERT เพียงไม่กี่คำที่มีอยู่ไม่กี่รายการในฐานข้อมูลฉันไม่ทราบว่ามีรายการใดบ้าง ฉันพบว่าหลังจากประมวลผล 100 สองสามครั้งการดำเนินการจะหยุดลงพร้อมกับข้อความแสดงข้อผิดพลาดที่ไม่สามารถทำได้INSERTเนื่องจากมีการบันทึกอยู่แล้ว ค่อนข้างน่ารำคาญ แต่ก็GOแก้ไขได้ อาจไม่ใช่วิธีแก้ปัญหาที่เร็วที่สุด แต่ความเร็วไม่ใช่ปัญหาของฉัน

GO
INSERT INTO mytable (C1,C2,C3) VALUES(1,2,3)
GO
INSERT INTO mytable (C1,C2,C3) VALUES(4,5,6)
 etc ...

GOแยกแบทช์คืออะไร? มันไม่ได้ช่วยป้องกันการบันทึกที่ซ้ำกัน
Dale K
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.