Inserisci SQL Server se non esiste


243

Voglio inserire i dati nella mia tabella, ma inserisco solo i dati che non esistono già nel mio database.

Ecco il mio codice:

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

E l'errore è:

Messaggio 156, livello 15, stato 1, e-mail di proceduraRecebidosInsert, riga 11
Sintassi errata vicino alla parola chiave "WHERE".


10
Non devi fare affidamento su questo controllo da solo per assicurarti che non vi siano duplicati, non è sicuro per i thread e otterrai duplicati quando viene soddisfatta una condizione di competizione. Se hai davvero bisogno di dati univoci aggiungi un vincolo univoco alla tabella, quindi rileva l'errore di violazione del vincolo univoco. Vedi questa risposta
GarethD

1
È possibile utilizzare la query MERGE o Se non esiste (selezionare l'istruzione) iniziare a inserire i valori END
Abdul Hannan Ijaz

Dipende dallo scenario se è necessario inoltrare o meno questo controllo. Se si sta sviluppando uno script di distribuzione che scrive i dati in una tabella "statica", ad esempio, questo non è un problema.
AxelWass,

puoi usare "se non esiste (seleziona * da ..." come questo stackoverflow.com/a/43763687/2736742
A. Morel,

2
@GarethD: cosa intendi con "non thread-safe"? Potrebbe non essere elegante ma mi sembra corretto. Una singola insertistruzione è sempre una singola transazione. Non è come se SQL Server valuti prima la sottoquery e poi in un secondo momento, e senza tenere un blocco, continua a fare l'inserimento.
Ed Avis,

Risposte:


323

anziché sotto il codice

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

sostituirlo con

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

Aggiornato: (grazie a @Marc Durdin per il puntamento)

Si noti che a carico elevato, ciò a volte fallisce, perché una seconda connessione può superare il test IF NOT EXISTS prima che la prima connessione esegua INSERT, ovvero una condizione di competizione. Vedi stackoverflow.com/a/3791506/1836776 per una buona risposta sul perché anche il wrapping in una transazione non risolve questo problema.


21
Si noti che a carico elevato, ciò a volte fallisce, perché una seconda connessione può superare il test IF NOT EXISTS prima che la prima connessione esegua INSERT, ovvero una condizione di competizione. Vedere Stackoverflow.com/a/3791506/1836776 per una buona risposta sul perché anche il wrapping in una transazione non risolve questo problema.
Marc Durdin,

11
SELEZIONA 1 DA EmailsRecebidos DOVE De = @_DE E Assunto = @_ASSUNTO AND Dati = @_DATA Usare 1 invece di * sarebbe più efficiente
Reno,

1
Metti un lucchetto di scrittura intorno all'intera cosa e quindi non avrai alcuna possibilità di duplicati.
Kevin Finkenbinder,

10
@jazzcat select *in questo caso non fa alcuna differenza perché viene utilizzato in una EXISTSclausola. SQL Server lo ottimizzerà sempre e lo fa da anni. Dato che sono molto vecchio di solito scrivo queste domande come EXISTS (SELECT 1 FROM...)ma non sono più necessarie.
Loudenvier,

16
Perché questo tipo di semplice domanda genera più dubbi che certezze?
Drowa,

77

Per coloro che cercano il modo più veloce , di recente mi sono imbattuto in questi parametri di riferimento in cui apparentemente usando "INSERT SELECT ... EXCEPT SELECT ..." si è rivelato il più veloce per 50 milioni di record o più.

Ecco un po 'di codice di esempio dall'articolo (il terzo blocco di codice era il più veloce):

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
Mi piace EXCEPT SELECT
Bryan il

1
La prima volta che ho usato EXCEPT. Semplice ed elegante.
jhowe,

Ma EXCEPT potrebbe non essere efficiente per le operazioni in blocco.
Aasish Kr. Sharma,

EXCEPT non è così efficiente.
Biswa,

1
@Biswa: non secondo questi parametri. Il codice è disponibile dal sito. Sentiti libero di eseguirlo sul tuo sistema per vedere come confrontare i risultati.

25

Vorrei usare un'unione:

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

sto andando con questo perché il suo più elaborato
jokab

Mi piacerebbe usare Merge ... ma non funziona con le Tabelle ottimizzate per la memoria.
Don Sam,

20

Prova sotto il codice

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

Il INSERTcomando non ha una WHEREclausola: dovrai scriverlo in questo modo:

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
È necessario gestire gli errori per questa procedura perché ci saranno casi in cui si verificherà un inserimento tra il controllo e l'inserimento.
Filip De Vos

@FilipDeVos: vero - una possibilità, forse non molto probabile, ma comunque una possibilità. Buon punto.
marc_s,

Cosa succede se si avvolgono entrambi all'interno di una transazione? Ciò bloccherebbe la possibilità? (Non sono un esperto di transazioni, quindi ti prego di perdonare se questa è una domanda stupida.)
David

1
Vedi stackoverflow.com/a/3791506/1836776 per una buona risposta sul perché una transazione non risolve questo problema, @David.
Marc Durdin,

Nell'istruzione IF: non è necessario utilizzare BEGIN & END se il numero di righe di comando richieste è solo uno anche se hai utilizzato più di una riga, quindi puoi ometterlo qui.
Wessam El Mahdy,

11

Ho fatto la stessa cosa con SQL Server 2012 e ha funzionato

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
Ovviamente ha funzionato, stai usando una tabella temporanea (cioè non devi preoccuparti della concorrenza quando usi le tabelle temporanee).
Drowa,

6

A seconda della versione (2012?) Di SQL Server oltre a IF EXISTS puoi anche utilizzare MERGE in questo modo:

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 diverso, stesso principio. Inserisci solo se la clausola in cui non esiste non riesce

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

Come spiegato nel codice seguente: esegui le query seguenti e verifica te stesso.

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;

Inserisci un record:

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

Ora prova a inserire di nuovo lo stesso record:

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

Inserisci un altro record:

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
Non è questo per MySQL e la domanda è per SQL Server?
Douglas Gaskell,

Sì, è per MySQL.
vadiraj jahagirdar,

-3

Puoi usare il GOcomando. Ciò riavvierà l'esecuzione delle istruzioni SQL dopo un errore. Nel mio caso ho alcune istruzioni INSERT 1000, in cui una manciata di quei record esiste già nel database, ma non so quali. Ho scoperto che dopo aver elaborato alcuni 100, l'esecuzione si interrompe con un messaggio di errore che non è possibile INSERTpoiché il record esiste già. Abbastanza fastidioso, ma GOrisolto questo. Potrebbe non essere la soluzione più veloce, ma la velocità non era il mio problema.

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

GOè un separatore batch? Non aiuta a prevenire record duplicati.
Dale K
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.