SQL Server einfügen, falls nicht vorhanden

243

Ich möchte Daten in meine Tabelle einfügen, aber nur Daten einfügen, die noch nicht in meiner Datenbank vorhanden sind.

Hier ist mein Code:

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

Und der Fehler ist:

Nachricht 156, Ebene 15, Status 1, Prozedur EmailsRecebidosInsert, Zeile 11
Falsche Syntax in der Nähe des Schlüsselworts 'WHERE'.

Francisco Carvalho
quelle
10
Sie sollten sich nicht nur auf diese Prüfung verlassen, um sicherzustellen, dass keine Duplikate vorhanden sind. Sie ist nicht threadsicher und Sie erhalten Duplikate, wenn eine Rennbedingung erfüllt ist. Wenn Sie wirklich eindeutige Daten benötigen, fügen Sie der Tabelle eine eindeutige Einschränkung hinzu, und fangen Sie dann den Fehler bei der Verletzung eindeutiger Einschränkungen ab. Siehe diese Antwort
GarethD
1
Sie können die MERGE-Abfrage verwenden oder, falls nicht vorhanden (select-Anweisung), mit dem Einfügen von Werten beginnen. END
Abdul Hannan Ijaz
Es hängt vom Szenario ab, ob Sie diese Prüfung weiterleiten sollen oder nicht. Wenn Sie beispielsweise ein Bereitstellungsskript entwickeln, das Daten in eine "statische" Tabelle schreibt, ist dies kein Problem.
AxelWass
Sie können "falls nicht vorhanden (wählen Sie * aus ...") wie folgt verwenden
A. Morel
2
@GarethD: Was meinst du mit "nicht threadsicher"? Es mag nicht elegant sein, aber es sieht für mich richtig aus. Eine einzelne insertAnweisung ist immer eine einzelne Transaktion. Es ist nicht so, dass der SQL Server die Unterabfrage zuerst auswertet und dann zu einem späteren Zeitpunkt und ohne eine Sperre zu halten, das Einfügen durchführt.
Ed Avis

Antworten:

322

statt unter Code

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

ersetzen mit

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

Aktualisiert: (danke an @Marc Durdin für den Hinweis)

Beachten Sie, dass dies unter hoher Last manchmal immer noch fehlschlägt, da eine zweite Verbindung den IF NOT EXISTS-Test bestehen kann, bevor die erste Verbindung das INSERT ausführt, dh eine Race-Bedingung. Unter stackoverflow.com/a/3791506/1836776 finden Sie eine gute Antwort darauf, warum selbst das Einschließen einer Transaktion dies nicht löst.

Imran Ali Khan
quelle
20
Beachten Sie, dass dies unter hoher Last manchmal immer noch fehlschlägt, da eine zweite Verbindung den IF NOT EXISTS-Test bestehen kann, bevor die erste Verbindung das INSERT ausführt, dh eine Race-Bedingung. Unter stackoverflow.com/a/3791506/1836776 finden Sie eine gute Antwort darauf, warum selbst das Einschließen einer Transaktion dies nicht löst.
Marc Durdin
11
SELECT 1 FROM EmailsRecebidos WHERE De = @_DE AND Assunto = @_ASSUNTO AND Data = @_DATA Die Verwendung von 1 anstelle von * wäre effizienter
Reno
1
Setzen Sie eine Schreibsperre um das Ganze und Sie haben keine Chance auf Duplikate.
Kevin Finkenbinder
10
@jazzcat macht select *in diesem Fall keinen Unterschied, da es in einer EXISTSKlausel verwendet wird. SQL Server wird es immer optimieren und tut es schon seit Ewigkeiten. Da ich sehr alt bin, schreibe ich diese Abfragen normalerweise als, EXISTS (SELECT 1 FROM...)aber es wird nicht mehr benötigt.
Loudenvier
16
Warum erzeugt diese einfache Frage mehr Zweifel als Gewissheit?
Drowa
77

Für diejenigen, die nach dem schnellsten Weg suchen , bin ich kürzlich auf diese Benchmarks gestoßen, bei denen sich die Verwendung von "INSERT SELECT ... EXCEPT SELECT ..." anscheinend als der schnellste für 50 Millionen Datensätze oder mehr herausstellte.

Hier ist ein Beispielcode aus dem Artikel (der 3. Codeblock war der schnellste):

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

quelle
6
Ich mag EXCEPT SELECT
Bryan
1
Zum ersten Mal habe ich EXCEPT verwendet. Einfach und elegant.
Jhowe
EXCEPT ist jedoch möglicherweise nicht effizient für Massenoperationen.
Aasish Kr. Sharma
EXCEPT ist nicht so effizient.
Biswa
1
@Biswa: Nicht nach diesen Benchmarks. Der Code ist auf der Website verfügbar. Fühlen Sie sich frei, es auf Ihrem System auszuführen, um zu sehen, wie die Ergebnisse verglichen werden.
25

Ich würde eine Zusammenführung verwenden:

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
Brett Schneider
quelle
Ich
Ich würde gerne Merge verwenden ... aber es funktioniert nicht für speicheroptimierte Tabellen.
Don Sam
20

Versuchen Sie es mit dem folgenden Code

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
SaravanaC
quelle
11

Der INSERTBefehl hat keine WHEREKlausel - Sie müssen ihn folgendermaßen schreiben:

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
marc_s
quelle
1
Sie müssen Fehler für dieses Verfahren behandeln, da es Fälle geben wird, in denen zwischen Prüfung und Einfügung eine Einfügung erfolgt.
Filip De Vos
@FilipDeVos: wahr - eine Möglichkeit, vielleicht nicht sehr wahrscheinlich, aber immer noch eine Möglichkeit. Guter Punkt.
marc_s
Was ist, wenn Sie beide in eine Transaktion einschließen? Würde das die Möglichkeit blockieren? (Ich bin kein Experte für Transaktionen, also bitte verzeihen Sie, wenn dies eine dumme Frage ist.)
David
1
Siehe stackoverflow.com/a/3791506/1836776 für eine gute Antwort auf , warum eine Transaktion nicht lösen wird, @ David.
Marc Durdin
In der IF-Anweisung: BEGIN & END muss nicht verwendet werden, wenn die Anzahl der erforderlichen Befehlszeilen nur eins beträgt, auch wenn Sie mehr als eine Zeile verwendet haben. Sie können sie hier also weglassen.
Wessam El Mahdy
11

Ich habe das gleiche mit SQL Server 2012 gemacht und es hat funktioniert

Insert into #table1 With (ROWLOCK) (Id, studentId, name)
SELECT '18769', '2', 'Alex'
WHERE not exists (select * from #table1 where Id = '18769' and studentId = '2')
Hovhannes Babayan
quelle
4
Natürlich hat es funktioniert, Sie verwenden eine temporäre Tabelle (dh Sie müssen sich keine Gedanken über die Parallelität machen, wenn Sie temporäre Tabellen verwenden).
Drowa
6

Abhängig von Ihrer Version (2012?) Von SQL Server können Sie neben IF EXISTS auch MERGE wie folgt verwenden :

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
Don
quelle
1

Unterschiedliches SQL, gleiches Prinzip. Nur einfügen, wenn die Klausel in, wo nicht vorhanden, fehlschlägt

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')
Malcolm Swaine
quelle
-1

Wie im folgenden Code erläutert: Führen Sie die folgenden Abfragen aus und überprüfen Sie sich.

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;

Datensatz einfügen:

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

Versuchen Sie nun erneut, denselben Datensatz einzufügen:

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

Fügen Sie einen anderen Datensatz ein:

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  |
+----+--------+-----------+------+
vadiraj jahagirdar
quelle
1
Ist das nicht für MySQL und die Frage ist für SQL Server?
Douglas Gaskell
Ja, es ist für MySQL.
Vadiraj Jahagirdar
-2

Sie könnten den GOBefehl verwenden. Dadurch wird die Ausführung von SQL-Anweisungen nach einem Fehler neu gestartet. In meinem Fall habe ich einige 1000 INSERT-Anweisungen, von denen eine Handvoll dieser Datensätze bereits in der Datenbank vorhanden sind. Ich weiß nur nicht, welche. Ich habe festgestellt, dass nach der Verarbeitung einiger 100 die Ausführung nur mit einer Fehlermeldung beendet wird, die nicht möglich ist, INSERTda der Datensatz bereits vorhanden ist. Ziemlich nervig, aber das zu GOlösen. Es ist vielleicht nicht die schnellste Lösung, aber Geschwindigkeit war nicht mein Problem.

GO
INSERT INTO mytable (C1,C2,C3) VALUES(1,2,3)
GO
INSERT INTO mytable (C1,C2,C3) VALUES(4,5,6)
 etc ...
mljm
quelle
GOist ein Batch-Separator? Es hilft nicht dabei, doppelte Datensätze zu verhindern.
Dale K