Chèn máy chủ SQL nếu không tồn tại


243

Tôi muốn chèn dữ liệu vào bảng của mình, nhưng chỉ chèn dữ liệu chưa tồn tại trong cơ sở dữ liệu của tôi.

Đây là mã của tôi:

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

Và lỗi là:

Msg 156, Cấp 15, Trạng thái 1, Thủ tục EmailsRecebidosInsert, Dòng 11
Cú pháp không chính xác gần từ khóa 'WHERE'.


10
Bạn không nên chỉ dựa vào kiểm tra này để đảm bảo không có bản sao, nó không an toàn và bạn sẽ nhận được bản sao khi điều kiện cuộc đua được đáp ứng. Nếu bạn thực sự cần dữ liệu duy nhất, hãy thêm một ràng buộc duy nhất vào bảng và sau đó bắt lỗi vi phạm ràng buộc duy nhất. Xem câu trả lời này
GarethD

1
Bạn có thể sử dụng truy vấn MERGE hoặc Nếu không tồn tại (chọn câu lệnh) bắt đầu chèn giá trị END
Abdul Hannan Ijaz

Nó phụ thuộc vào kịch bản nếu bạn nên chuyển tiếp hay không trong kiểm tra này. Nếu bạn đang phát triển một kịch bản triển khai ghi dữ liệu vào bảng "tĩnh", thì đây không phải là vấn đề.
AxelWass

bạn có thể sử dụng "nếu không tồn tại (chọn * từ ..." như stackoverflow.com/a/43763687/2736742
A. Morel

2
@GarethD: bạn có nghĩa là "không an toàn chủ đề"? Nó có thể không thanh lịch nhưng nó có vẻ đúng với tôi. Một inserttuyên bố luôn luôn là một giao dịch duy nhất. Không phải là nếu SQL Server đánh giá truy vấn con trước và sau đó tại một số điểm sau đó và không giữ khóa, tiếp tục thực hiện thao tác chèn.
Ed Avis

Câu trả lời:


322

thay vì dưới mã

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

thay thế bằng

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

Đã cập nhật: (cảm ơn @Marc Durdin đã chỉ)

Lưu ý rằng dưới tải cao, điều này đôi khi vẫn thất bại, bởi vì kết nối thứ hai có thể vượt qua bài kiểm tra NẾU KHÔNG EXISTS trước khi kết nối đầu tiên thực hiện INSERT, tức là điều kiện cuộc đua. Xem stackoverflow.com/a/3791506/1836776 để biết câu trả lời hay về lý do tại sao ngay cả việc gói trong một giao dịch cũng không giải quyết được điều này.


20
Lưu ý rằng dưới tải cao, điều này đôi khi vẫn thất bại, bởi vì kết nối thứ hai có thể vượt qua bài kiểm tra NẾU KHÔNG EXISTS trước khi kết nối đầu tiên thực hiện INSERT, tức là điều kiện cuộc đua. Xem Xem stackoverflow.com/a/3791506/1836776 để biết câu trả lời hay về lý do tại sao ngay cả việc gói trong một giao dịch cũng không giải quyết được điều này.
Marc Durdin

11
CHỌN 1 TỪ EmailsRecebidos WHERE De = @_DE VÀ Assunto = @_ASSUNTO VÀ Dữ liệu = @_DATA Để sử dụng 1 thay vì * sẽ hiệu quả hơn
Reno

1
Đặt một khóa viết xung quanh toàn bộ và sau đó bạn sẽ không có bất kỳ cơ hội trùng lặp.
Kevin Finkenbinder

10
@jazzcat select *trong trường hợp này không có gì khác biệt vì nó được sử dụng trong một EXISTSmệnh đề. SQL Server sẽ luôn tối ưu hóa nó và đã được thực hiện từ lâu. Vì tôi rất già nên tôi thường viết những truy vấn này EXISTS (SELECT 1 FROM...)nhưng nó không còn cần thiết nữa.
Lâu đài

16
Tại sao loại câu hỏi đơn giản này tạo ra nhiều nghi ngờ hơn là sự chắc chắn?
drowa

77

Đối với những người tìm kiếm cách nhanh nhất , gần đây tôi đã tìm thấy các điểm chuẩn này , trong đó rõ ràng là sử dụng "CHỌN CHỌN ... NGOẠI CHỌN ..." hóa ra là nhanh nhất cho 50 triệu hồ sơ trở lên.

Dưới đây là một số mã mẫu từ bài viết (khối mã thứ 3 là nhanh nhất):

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
Tôi thích NGOẠI TRỪ
Bryan

1
Lần đầu tiên tôi đã sử dụng NGOẠI TRỪ. Đơn giản và thanh lịch.
jhowe

Nhưng EXCEPT có thể không hiệu quả cho các hoạt động hàng loạt.
Aasish Kr. Sharma

NGOẠI TRỪ không hiệu quả.
Biswa

1
@Biswa: Không theo những điểm chuẩn đó. Mã có sẵn từ trang web. Hãy chạy nó trên hệ thống của bạn để xem kết quả so sánh như thế nào.

25

Tôi sẽ sử dụng hợp nhất:

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

Tôi đi với điều này bởi vì người hâm mộ của nó
jokab

Tôi rất thích sử dụng hợp nhất ... nhưng nó không hoạt động cho các Bảng được tối ưu hóa bộ nhớ.
Don Sam

20

Hãy thử mã dưới đây

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

Các INSERTlệnh không có một WHEREkhoản - bạn sẽ phải viết nó như thế này:

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
Bạn cần xử lý lỗi cho quy trình này vì sẽ có trường hợp chèn sẽ xảy ra giữa kiểm tra và chèn.
Filip De Vos

@FilipDeVos: đúng - một khả năng, có thể không có khả năng lắm, nhưng vẫn là một khả năng. Điểm tốt.
marc_s

Điều gì nếu bạn bọc cả hai trong một giao dịch? Điều đó sẽ ngăn chặn khả năng? (Tôi không phải là chuyên gia về giao dịch, vì vậy xin vui lòng tha thứ nếu đây là một câu hỏi ngu ngốc.)
David

1
Xem stackoverflow.com/a/3791506/1836776 để biết câu trả lời hay về lý do tại sao một giao dịch không giải quyết được điều này, @David.
Marc Durdin

Trong câu lệnh IF: không cần sử dụng BEGIN & END nếu số dòng lệnh được yêu cầu chỉ là một dòng ngay cả khi bạn đã sử dụng nhiều hơn một dòng, vì vậy bạn có thể bỏ qua nó ở đây.
Wessam El Mahdy

11

Tôi đã làm điều tương tự với SQL Server 2012 và nó đã hoạt động

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
Tất nhiên là nó hoạt động, bạn đang sử dụng một bảng tạm thời (nghĩa là bạn không cần phải lo lắng về sự tương tranh khi sử dụng các bảng tạm thời).
drowa

6

Tùy thuộc vào phiên bản (2012?) Của SQL Server ngoài IF EXISTS, bạn cũng có thể sử dụng MERGE như vậy:

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 khác nhau, cùng một nguyên tắc. Chỉ chèn nếu mệnh đề ở nơi không tồn tại

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

Như được giải thích trong mã dưới đây: Thực hiện các truy vấn bên dưới và tự xác minh.

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;

Chèn một bản ghi:

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  |
+----+--------+-----------+------+

Bây giờ, hãy thử chèn lại cùng một bản ghi:

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  |
+----+--------+-----------+------+

Chèn một bản ghi khác:

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
Đây không phải là cho MySQL và câu hỏi là dành cho SQL Server?
Douglas Gaskell

Vâng, nó dành cho MySQL.
vadiraj jahagirdar

-2

Bạn có thể sử dụng GOlệnh. Điều đó sẽ khởi động lại việc thực thi các câu lệnh SQL sau khi xảy ra lỗi. Trong trường hợp của tôi, tôi có một vài câu lệnh INSERT, trong đó một số ít các bản ghi đó đã tồn tại trong cơ sở dữ liệu, tôi chỉ không biết những bản nào. Tôi thấy rằng sau khi xử lý một vài 100, việc thực thi chỉ dừng lại với một thông báo lỗi rằng nó không thể INSERTlà bản ghi đã tồn tại. Khá khó chịu, nhưng đặt một GOgiải pháp này. Nó có thể không phải là giải pháp nhanh nhất, nhưng tốc độ không phải là vấn đề của tôi.

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

GOlà một phân tách hàng loạt? Nó không hỗ trợ ngăn ngừa các bản ghi trùng lặp.
Dale K
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.