Masukkan SQL Server jika tidak ada


243

Saya ingin memasukkan data ke tabel saya, tetapi hanya memasukkan data yang belum ada di database saya.

Ini kode saya:

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

Dan kesalahannya adalah:

Msg 156, Level 15, Status 1, Prosedur EmailsRecebidosInsert, Baris 11
Sintaksis salah di dekat kata kunci 'WHERE'.


10
Anda seharusnya tidak mengandalkan pemeriksaan ini sendirian untuk memastikan tidak ada duplikat, itu tidak aman thread dan Anda akan mendapatkan duplikat ketika kondisi balapan terpenuhi. Jika Anda benar-benar membutuhkan data unik, tambahkan batasan unik ke tabel, dan kemudian tangkap kesalahan pelanggaran batasan unik. Lihat jawaban ini
GarethD

1
Anda dapat menggunakan kueri MERGE atau Jika tidak ada (pilih pernyataan) mulai masukkan nilai END
Abdul Hannan Ijaz

Itu tergantung pada skenario apakah Anda harus menyampaikan atau tidak pada pemeriksaan ini. Misalnya, jika Anda mengembangkan skrip penyebaran yang menulis data ke tabel "statis", ini bukan masalah.
AxelWass

Anda dapat menggunakan "jika tidak ada (pilih * dari ..." seperti ini stackoverflow.com/a/43763687/2736742
A. Morel

2
@ GarethD: apa maksudmu "tidak aman"? Ini mungkin tidak elegan tetapi terlihat benar bagi saya. Satu insertpernyataan selalu merupakan satu transaksi. Ini bukan seolah-olah SQL Server mengevaluasi subquery pertama dan kemudian di beberapa titik kemudian, dan tanpa memegang kunci, melanjutkan untuk melakukan penyisipan.
Ed Avis

Jawaban:


323

bukannya Kode di bawah ini

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

ubah dengan

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

Diperbarui: (terima kasih kepada @Marc Durdin karena menunjuk)

Perhatikan bahwa di bawah beban tinggi, ini kadang-kadang masih gagal, karena koneksi kedua dapat lulus tes JIKA TIDAK ADA sebelum koneksi pertama mengeksekusi INSERT, yaitu kondisi balapan. Lihat stackoverflow.com/a/3791506/1836776 untuk jawaban yang bagus tentang mengapa bahkan membungkus transaksi tidak menyelesaikan masalah ini.


21
Perhatikan bahwa di bawah beban tinggi, ini kadang-kadang masih gagal, karena koneksi kedua dapat lulus tes JIKA TIDAK ADA sebelum koneksi pertama mengeksekusi INSERT, yaitu kondisi balapan. Lihat Lihat stackoverflow.com/a/3791506/1836776 untuk jawaban yang bagus tentang mengapa pembungkus transaksi sekalipun tidak menyelesaikan masalah ini.
Marc Durdin

11
PILIH 1 DARI Email EmailRecebidos DI MANA De = @_DE DAN Assunto = @_ASSUNTO DAN Data = @_DATA Untuk menggunakan 1 alih-alih * akan lebih efisien
Reno

1
Letakkan kunci tulis di sekitar semuanya dan kemudian Anda tidak akan memiliki kesempatan untuk duplikat.
Kevin Finkenbinder

10
@jazzcat select *dalam kasus ini tidak ada bedanya karena digunakan dalam EXISTSklausa. SQL Server akan selalu mengoptimalkannya dan telah melakukannya sejak lama. Karena saya sudah sangat tua, saya biasanya menulis pertanyaan ini EXISTS (SELECT 1 FROM...)tetapi tidak diperlukan lagi.
Loudenvier

16
Mengapa pertanyaan sederhana semacam ini menghasilkan lebih banyak keraguan daripada kepastian?
drowa

77

Bagi mereka yang mencari cara tercepat , saya baru - baru ini menemukan tolok ukur ini di mana tampaknya menggunakan "INSERT SELECT ... EXCEPT SELECT ..." ternyata menjadi yang tercepat untuk 50 juta rekaman atau lebih.

Berikut beberapa contoh kode dari artikel (blok kode ke-3 adalah yang tercepat):

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
Saya suka EXCEPT SELECT
Bryan

1
Pertama kali saya menggunakan KECUALI. Sederhana dan elegan.
jhowe

Tetapi KECUALI mungkin tidak efisien untuk operasi massal.
Aasish Kr. Sharma

KECUALI tidak seefisien itu.
Biswa

1
@Biswa: Tidak sesuai dengan tolok ukur itu. Kode tersedia dari situs. Jangan ragu untuk menjalankannya di sistem Anda untuk melihat bagaimana hasilnya dibandingkan.

25

Saya akan menggunakan gabungan:

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

Aku pergi dengan ini karena
pelawanya

Saya ingin menggunakan gabungan ... tetapi tidak berfungsi untuk Memory Optimized Tables.
Don Sam

20

Coba kode di bawah ini

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

The INSERTperintah tidak memiliki WHEREklausul - Anda harus menulis seperti ini:

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
Anda perlu menangani kesalahan untuk prosedur ini karena akan ada kasus di mana penyisipan akan terjadi antara pemeriksaan dan penyisipan.
Filip De Vos

@FilipDeVos: true - kemungkinan, mungkin tidak terlalu mungkin, tetapi masih ada kemungkinan. Poin bagus.
marc_s

Bagaimana jika Anda membungkus keduanya dalam suatu transaksi? Apakah itu menghalangi kemungkinan? (Saya bukan ahli dalam transaksi, jadi tolong maafkan jika ini adalah pertanyaan bodoh.)
David

1
Lihat stackoverflow.com/a/3791506/1836776 untuk jawaban yang baik tentang mengapa transaksi tidak menyelesaikan ini, @ David.
Marc Durdin

Dalam pernyataan IF: tidak perlu menggunakan BEGIN & END jika jumlah baris perintah yang diperlukan hanya satu walaupun Anda menggunakan lebih dari satu baris, jadi Anda bisa menghilangkannya di sini.
Wessam El Mahdy

11

Saya melakukan hal yang sama dengan SQL Server 2012 dan berhasil

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
Tentu saja itu berhasil, Anda menggunakan tabel sementara (yaitu Anda tidak perlu khawatir tentang konkurensi saat menggunakan tabel sementara).
drowa

6

Bergantung pada versi Anda (2012?) Dari SQL Server selain dari JIKA ADA Anda juga dapat menggunakan MERGE seperti:

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 berbeda, prinsip yang sama. Hanya masukkan jika klausa di mana tidak ada gagal

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

-2

Seperti dijelaskan dalam kode di bawah ini: Jalankan pertanyaan di bawah ini dan verifikasi diri Anda.

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;

Sisipkan catatan:

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

Sekarang, coba masukkan catatan yang sama lagi:

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

Masukkan catatan yang berbeda:

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
Bukankah ini untuk MySQL dan pertanyaannya adalah untuk SQL Server?
Douglas Gaskell

Ya untuk MySQL.
vadiraj jahagirdar

-3

Anda bisa menggunakan GOperintah. Itu akan memulai kembali pelaksanaan pernyataan SQL setelah kesalahan. Dalam kasus saya, saya memiliki beberapa pernyataan INSERT 1000, di mana beberapa catatan itu sudah ada dalam database, saya hanya tidak tahu yang mana. Saya menemukan bahwa setelah memproses beberapa 100, eksekusi hanya berhenti dengan pesan kesalahan yang tidak bisa INSERTkarena catatan sudah ada. Cukup menjengkelkan, tetapi menempatkan GOini diselesaikan. Ini mungkin bukan solusi tercepat, tetapi kecepatan bukanlah masalah saya.

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

GOApakah pemisah batch? Itu tidak membantu mencegah catatan duplikat.
Dale K
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.