Insertar SQL Server si no existe

243

Quiero insertar datos en mi tabla, pero insertar solo datos que no existen en mi base de datos.

Aquí está mi código:

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

Y el error es:

Msg 156, Nivel 15, Estado 1, Procedimiento EmailsRecebidosInsert, Línea 11
Sintaxis incorrecta cerca de la palabra clave 'WHERE'.

Francisco Carvalho
fuente
10
No debe confiar solo en esta verificación para asegurarse de que no haya duplicados, no es seguro para subprocesos y obtendrá duplicados cuando se cumpla una condición de carrera. Si realmente necesita datos únicos, agregue una restricción única a la tabla y luego detecte el error de violación de restricción única. Ver esta respuesta
GarethD
1
Puede usar la consulta MERGE o, si no existe (instrucción select), comience a insertar valores FIN
Abdul Hannan Ijaz
Depende del escenario si debe retransmitir o no esta verificación. Si está desarrollando una secuencia de comandos de implementación que escribe datos en una tabla "estática", por ejemplo, esto no es un problema.
AxelWass
puede usar "si no existe (seleccione * de ..." como este stackoverflow.com/a/43763687/2736742
A. Morel
2
@GarethD: ¿qué quieres decir con "no es seguro para subprocesos"? Puede que no sea elegante, pero me parece correcto. Una sola insertdeclaración es siempre una sola transacción. No es como si el SQL Server evalúa la subconsulta primero y luego en algún momento posterior, y sin mantener un bloqueo, continúa con la inserción.
Ed Avis

Respuestas:

323

en lugar de debajo del código

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

reemplazar 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

Actualizado: (gracias a @Marc Durdin por señalar)

Tenga en cuenta que bajo una carga alta, esto a veces fallará, porque una segunda conexión puede pasar la prueba SI NO EXISTE antes de que la primera conexión ejecute INSERT, es decir, una condición de carrera. Consulte stackoverflow.com/a/3791506/1836776 para obtener una buena respuesta sobre por qué incluso concluir una transacción no resuelve esto.

Imran Ali Khan
fuente
21
Tenga en cuenta que bajo una carga alta, esto a veces fallará, porque una segunda conexión puede pasar la prueba SI NO EXISTE antes de que la primera conexión ejecute INSERT, es decir, una condición de carrera. Consulte Ver stackoverflow.com/a/3791506/1836776 para obtener una buena respuesta sobre por qué incluso concluir una transacción no resuelve esto.
Marc Durdin
11
SELECCIONE 1 DE EmailsRecebidos DONDE De = @_DE Y Assunto = @_ASSUNTO Y Datos = @_DATA Usar 1 en lugar de * sería más eficiente
Reno
1
Ponga un bloqueo de escritura alrededor de todo y luego no tendrá ninguna posibilidad de duplicados.
Kevin Finkenbinder
10
@jazzcat select *en este caso no hace ninguna diferencia porque se está usando en una EXISTScláusula. SQL Server siempre lo optimizará y lo ha estado haciendo durante años. Como soy muy viejo, generalmente escribo estas consultas, EXISTS (SELECT 1 FROM...)pero ya no es necesario.
Loudenvier
16
¿Por qué este tipo de pregunta simple genera más dudas que certezas?
drowa
77

Para aquellos que buscan la forma más rápida , recientemente me encontré con estos puntos de referencia donde aparentemente usar "INSERTAR SELECCIONAR ... EXCEPTO SELECCIONAR ..." resultó ser el más rápido para 50 millones de registros o más.

Aquí hay un código de muestra del artículo (el tercer bloque de código fue el más rápido):

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

fuente
66
Me gusta EXCEPTO SELECCIONAR
Bryan
1
La primera vez que he usado EXCEPTO. Simple y elegante
jhowe
Pero EXCEPTO puede no ser eficiente para operaciones masivas.
Aasish Kr. Sharma
EXCEPTO no es tan eficiente.
Biswa
1
@Biswa: No de acuerdo con esos puntos de referencia. El código está disponible desde el sitio. Siéntase libre de ejecutarlo en su sistema para ver cómo se comparan los resultados.
25

Yo usaría una fusión:

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
fuente
voy con esto porque es más elegante
jokab
Me encantaría usar merge ... pero no funciona para las tablas optimizadas de memoria.
Don Sam
20

Prueba el siguiente código

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
fuente
11

El INSERTcomando no tiene una WHEREcláusula, tendrá que escribirlo así:

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
fuente
1
Debe manejar los errores para este procedimiento porque habrá casos en los que se realizará una inserción entre la verificación y la inserción.
Filip De Vos
@FilipDeVos: verdadero: una posibilidad, tal vez no muy probable, pero sigue siendo una posibilidad. Buen punto.
marc_s
¿Qué pasa si envuelve ambos dentro de una transacción? ¿Eso bloquearía la posibilidad? (No soy un experto en transacciones, así que perdona si esta es una pregunta estúpida).
David
1
Consulte stackoverflow.com/a/3791506/1836776 para obtener una buena respuesta sobre por qué una transacción no resuelve esto, @David.
Marc Durdin
En la declaración IF: no es necesario usar BEGIN & END si la cantidad de líneas de comando requeridas es solo una, incluso si usó más de una línea, por lo que puede omitirla aquí.
Wessam El Mahdy
11

Hice lo mismo con SQL Server 2012 y funcionó

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
fuente
44
Por supuesto que funcionó, está utilizando una tabla temporal (es decir, no necesita preocuparse por la concurrencia al usar tablas temporales).
drowa
6

Dependiendo de su versión (2012?) De SQL Server aparte de IF EXISTS, también puede usar MERGE de esta manera:

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
fuente
2

SQL diferente, mismo principio. Solo inserte si la cláusula en donde no existe falla

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
fuente
-2

Como se explica en el siguiente código: Ejecute las consultas a continuación y verifíquese.

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;

Insertar un registro:

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

Ahora, intente insertar el mismo registro nuevamente:

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

Insertar un registro diferente:

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
fuente
1
¿No es esto para MySQL y la pregunta es para SQL Server?
Douglas Gaskell
Sí, es para MySQL.
vadiraj jahagirdar
-3

Podrías usar el GOcomando. Eso reiniciará la ejecución de las instrucciones SQL después de un error. En mi caso, tengo unas 1000 instrucciones INSERT, donde ya existen algunos de esos registros en la base de datos, simplemente no sé cuáles. Descubrí que después de procesar unos 100, la ejecución simplemente se detiene con un mensaje de error que no puede INSERTporque el registro ya existe. Bastante molesto, pero poniendo unGO resolviendo esto. Puede que no sea la solución más rápida, pero la velocidad no fue mi problema.

GO
INSERT INTO mytable (C1,C2,C3) VALUES(1,2,3)
GO
INSERT INTO mytable (C1,C2,C3) VALUES(4,5,6)
 etc ...
mljm
fuente
GOes un separador de lotes? No ayuda a prevenir registros duplicados.
Dale K