Insertion de SQL Server s'il n'existe pas

243

Je souhaite insérer des données dans ma table, mais insérer uniquement des données qui n'existent pas déjà dans ma base de données.

Voici mon 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

Et l'erreur est:

Msg 156, niveau 15, état 1, procédure EmailsRecebidosInsert, ligne 11
Syntaxe incorrecte près du mot clé "WHERE".

Francisco Carvalho
la source
10
Vous ne devez pas vous fier uniquement à cette vérification pour garantir l'absence de doublons, elle n'est pas sécurisée pour les threads et vous obtiendrez des doublons lorsqu'une condition de concurrence critique est remplie. Si vous avez vraiment besoin de données uniques, ajoutez une contrainte unique à la table, puis interceptez l'erreur de violation de contrainte unique. Voir cette réponse
GarethD
1
Vous pouvez utiliser la requête MERGE ou, si elle n'existe pas (instruction select), commencer à insérer des valeurs END
Abdul Hannan Ijaz
Cela dépend du scénario si vous devez relayer ou non ce contrôle. Si vous développez un script de déploiement qui écrit des données dans une table "statique" par exemple, ce n'est pas un problème.
AxelWass
vous pouvez utiliser "s'il n'existe pas (sélectionnez * à partir de ..." comme ceci stackoverflow.com/a/43763687/2736742
A. Morel
2
@GarethD: que voulez-vous dire par "pas sûr pour les threads"? Ce n'est peut-être pas élégant, mais il me semble correct. Une seule insertinstruction est toujours une seule transaction. Ce n'est pas comme si SQL Server évalue d'abord la sous-requête, puis à un moment ultérieur, et sans tenir de verrou, continue l'insertion.
Ed Avis

Réponses:

324

au lieu de ci-dessous 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

remplacer par

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

Mise à jour: (merci à @Marc Durdin pour le pointage)

Notez que sous une charge élevée, cela échouera parfois, car une deuxième connexion peut passer le test IF NOT EXISTS avant que la première connexion n'exécute INSERT, c'est-à-dire une condition de concurrence critique. Voir stackoverflow.com/a/3791506/1836776 pour une bonne réponse sur la raison pour laquelle même l'habillage dans une transaction ne résout pas ce problème.

Imran Ali Khan
la source
21
Notez que sous une charge élevée, cela échouera parfois, car une deuxième connexion peut passer le test IF NOT EXISTS avant que la première connexion n'exécute INSERT, c'est-à-dire une condition de concurrence critique. Voir Voir stackoverflow.com/a/3791506/1836776 pour une bonne réponse sur la raison pour laquelle même l'habillage dans une transaction ne résout pas ce problème.
Marc Durdin
11
SELECT 1 FROM EmailsRecebidos WHERE De = @_DE AND Assunto = @_ASSUNTO AND Data = @_DATA Utiliser 1 au lieu de * serait plus efficace
Reno
1
Mettez un verrou d'écriture autour de tout cela et vous n'aurez aucune chance de doublons.
Kevin Finkenbinder
10
@jazzcat select *dans ce cas ne fait aucune différence car il est utilisé dans une EXISTSclause. SQL Server l'optimisera toujours et le fait depuis des lustres. Depuis que je suis très vieux, j'écris généralement ces requêtes comme EXISTS (SELECT 1 FROM...)mais ce n'est plus nécessaire.
Loudenvier
16
Pourquoi ce genre de question simple génère-t-il plus de doute que de certitude?
drowa
77

Pour ceux qui recherchent le moyen le plus rapide , j'ai récemment rencontré ces points de référence où apparemment l'utilisation de "INSERT SELECT ... EXCEPT SELECT ..." s'est avérée être la plus rapide pour 50 millions d'enregistrements ou plus.

Voici un exemple de code de l'article (le 3e bloc de code était le plus rapide):

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

la source
6
J'aime SAUF SELECT
Bryan
1
La première fois que j'utilise EXCEPT. Simple et élégant.
jhowe
Mais EXCEPT peut ne pas être efficace pour les opérations en vrac.
Aasish Kr. Sharma
SAUF n'est pas si efficace.
Biswa
1
@Biswa: Pas selon ces repères. Le code est disponible sur le site. N'hésitez pas à l'exécuter sur votre système pour voir comment les résultats se comparent.
25

J'utiliserais une fusion:

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
la source
je vais avec cela parce que son
amateur
J'adorerais utiliser la fusion ... mais cela ne fonctionne pas pour les tables optimisées en mémoire.
Don Sam
20

Essayez le code ci-dessous

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
la source
11

La INSERTcommande n'a pas de WHEREclause - vous devrez l'écrire comme ceci:

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
la source
1
Vous devez gérer les erreurs pour cette procédure car il y aura des cas où une insertion se produira entre la vérification et l'insertion.
Filip De Vos
@FilipDeVos: vrai - une possibilité, peut-être pas très probable, mais toujours une possibilité. Bon point.
marc_s
Et si vous encapsulez les deux dans une transaction? Est-ce que cela bloquerait la possibilité? (Je ne suis pas un expert des transactions, alors pardonnez s'il s'agit d'une question stupide.)
David
1
Voir stackoverflow.com/a/3791506/1836776 pour une bonne réponse sur pourquoi une transaction ne résout pas cela, @David.
Marc Durdin
Dans l'instruction IF: il n'est pas nécessaire d'utiliser BEGIN & END si le nombre de lignes de commande requises est juste un même si vous avez utilisé plusieurs lignes, vous pouvez donc l'omettre ici.
Wessam El Mahdy
11

J'ai fait la même chose avec SQL Server 2012 et cela a fonctionné

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
la source
4
Bien sûr, cela a fonctionné, vous utilisez une table temporaire (c'est-à-dire que vous n'avez pas à vous soucier de la concurrence lorsque vous utilisez des tables temporaires).
drowa
6

En fonction de votre version (2012?) De SQL Server en plus des IF EXISTS, vous pouvez également utiliser MERGE comme ceci:

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
la source
2

SQL différent, même principe. Insérer uniquement si la clause dans laquelle n'existe pas échoue

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
la source
-2

Comme expliqué dans le code ci-dessous: Exécutez les requêtes ci-dessous et vérifiez vous-même.

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;

Insérez un enregistrement:

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

Maintenant, essayez à nouveau d'insérer le même enregistrement:

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

Insérez un enregistrement différent:

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
la source
1
N'est-ce pas pour MySQL et la question est pour SQL Server?
Douglas Gaskell
Oui, c'est pour MySQL.
vadiraj jahagirdar
-3

Vous pouvez utiliser la GOcommande. Cela redémarrera l'exécution des instructions SQL après une erreur. Dans mon cas, j'ai quelques 1000 instructions INSERT, où une poignée de ces enregistrements existent déjà dans la base de données, je ne sais pas lesquels. J'ai trouvé qu'après avoir traité quelques 100, l'exécution s'arrête juste avec un message d'erreur qu'il ne peut pas INSERTcar l'enregistrement existe déjà. Assez ennuyeux, mais mettre GOcela résolu. Ce n'est peut-être pas la solution la plus rapide, mais la vitesse n'était pas mon problème.

GO
INSERT INTO mytable (C1,C2,C3) VALUES(1,2,3)
GO
INSERT INTO mytable (C1,C2,C3) VALUES(4,5,6)
 etc ...
mljm
la source
GO'est-ce qu'un séparateur de lots? Il n'aide pas à empêcher les enregistrements en double.
Dale K