Вставка SQL Server, если не существует

243

Я хочу вставить данные в мою таблицу, но вставить только те данные, которых еще нет в моей базе данных.

Вот мой код:

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

И ошибка:

Сообщение 156, уровень 15, состояние 1, процедура EmailsRecebidosInsert, строка 11
Неверный синтаксис рядом с ключевым словом «ГДЕ».

Франсиско Карвалью
источник
10
Вы не должны полагаться только на эту проверку, чтобы гарантировать отсутствие дубликатов, она не является поточно-ориентированной, и вы получите дубликаты при выполнении условия гонки. Если вам действительно нужны уникальные данные, добавьте уникальное ограничение в таблицу, а затем поймайте ошибку нарушения уникального ограничения. Смотрите этот ответ
GarethD
1
Вы можете использовать запрос MERGE или, если не существует (выбрать оператор), начать вставлять значения END
Абдул Ханнан Иджаз
Это зависит от сценария, если вы должны передать или нет на этой проверке. Если вы разрабатываете сценарий развертывания, который записывает данные, например, в «статическую» таблицу, это не проблема.
AxelWass
Вы можете использовать «если не существует (выберите * из ...», например вот так: stackoverflow.com/a/43763687/2736742
A. Morel
2
@GarethD: что ты имеешь в виду "не потокобезопасен"? Это может быть не элегантно, но мне кажется правильным. Один insertоператор - это всегда одна транзакция. Дело не в том, что SQL Server сначала оценивает подзапрос, а затем в какой-то более поздний момент и, не удерживая блокировку, продолжает выполнять вставку.
Эд Авис

Ответы:

324

вместо приведенного ниже кода

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

заменить

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 Durdin за указание)

Обратите внимание, что при высокой нагрузке это все равно иногда будет выходить из строя, поскольку второе соединение может пройти тест IF NOT EXISTS до того, как первое соединение выполнит INSERT, то есть условие гонки. См. Stackoverflow.com/a/3791506/1836776 для хорошего ответа о том, почему даже перенос в транзакцию не решает эту проблему.

Имран Али Хан
источник
21
Обратите внимание, что при высокой нагрузке это все равно иногда будет выходить из строя, поскольку второе соединение может пройти тест IF NOT EXISTS до того, как первое соединение выполнит INSERT, то есть условие гонки. См. См. Stackoverflow.com/a/3791506/1836776 для хорошего ответа о том, почему даже перенос в транзакцию не решает эту проблему.
Марк Дурдин
11
ВЫБЕРИТЕ 1 ИЗ EmailsRecebidos, ГДЕ De = @_DE AND Assunto = @_ASSUNTO AND Data = @_DATA Использование 1 вместо * было бы более эффективным
Reno
1
Поместите блокировку записи вокруг всего этого, и тогда у вас не будет никакой возможности дубликатов.
Кевин Финкенбиндер,
10
@jazzcat select *в этом случае не имеет значения, потому что он используется в EXISTSпредложении. SQL Server всегда будет оптимизировать это и делал это целую вечность. Поскольку я очень старый, я обычно пишу эти запросы как, EXISTS (SELECT 1 FROM...)но это больше не нужно.
Loudenvier
16
Почему этот простой вопрос порождает больше сомнений, чем уверенности?
дроу
77

Для тех, кто ищет самый быстрый способ , я недавно наткнулся на эти тесты, где использование INSERT SELECT ... EXCEPT SELECT ... оказалось, очевидно, самым быстрым для 50 миллионов записей или более.

Вот пример кода из статьи (3-й блок кода был самым быстрым):

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
Мне нравится EXCEPT SELECT
Брайан
1
В первый раз я использовал, КРОМЕ. Просто и элегантно.
jhowe
Но ИСКЛЮЧЕНИЕ может быть неэффективным для массовых операций.
Аасиш Кр. Шарма
ИСКЛЮЧЕНИЕ не так эффективно.
Бисва
1
@ Бисва: Не в соответствии с этими критериями. Код доступен с сайта. Не стесняйтесь запустить его в своей системе, чтобы увидеть, как результаты сравниваются.
25

Я бы использовал слияние:

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
Бретт Шнайдер
источник
я собираюсь с этим, потому что его любитель
Иокаб
Я хотел бы использовать слияние ... но оно не работает для таблиц, оптимизированных для памяти.
Дон Сэм
20

Попробуйте код ниже

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
источник
11

У INSERTкоманды нет WHEREпредложения - вам нужно написать это так:

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
источник
1
Вы должны обработать ошибки для этой процедуры, потому что будут случаи, когда вставка произойдет между проверкой и вставкой.
Филип Де Вос
@FilipDeVos: правда - возможность, возможно, не очень вероятная, но все же возможность. Хорошая точка зрения.
marc_s
Что, если вы заключите оба в транзакцию? Это заблокирует возможность? (Я не эксперт по сделкам, поэтому, пожалуйста, прости, если это глупый вопрос.)
Дэвид
1
См. Stackoverflow.com/a/3791506/1836776 для хорошего ответа о том, почему транзакция не решает эту проблему, @David.
Марк Дурдин
В операторе IF: нет необходимости использовать BEGIN & END, если количество требуемых командных строк равно одной, даже если вы использовали более одной строки, так что вы можете опустить это здесь.
Вессам Эль Махди
11

Я сделал то же самое с SQL Server 2012, и он работал

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
Конечно, это сработало, вы используете временную таблицу (т.е. вам не нужно беспокоиться о параллелизме при использовании временных таблиц).
дроу
6

В зависимости от вашей версии (2012?) SQL Server помимо IF EXISTS вы также можете использовать MERGE следующим образом:

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, тот же принцип. Вставлять только в том случае, если предложение в, где не существует, терпит неудачу

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

Как объяснено в приведенном ниже коде: выполните приведенные ниже запросы и проверьте себя.

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;

Вставить запись:

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

Теперь попробуйте снова вставить ту же запись:

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

Вставьте другую запись:

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
Разве это не для MySQL, а вопрос для SQL Server?
Дуглас Гаскелл
Да, это для MySQL.
Вадирадж Джахагирдар
-3

Вы можете использовать GOкоманду. Это возобновит выполнение операторов SQL после ошибки. В моем случае у меня есть несколько 1000 операторов INSERT, где несколько таких записей уже существует в базе данных, я просто не знаю, какие из них. Я обнаружил, что после обработки нескольких сотен выполнение просто останавливается с сообщением об ошибке, которое не может быть, INSERTпоскольку запись уже существует. Довольно раздражает, но выкладывать GOрешено это. Возможно, это не самое быстрое решение, но скорость не была моей проблемой.

GO
INSERT INTO mytable (C1,C2,C3) VALUES(1,2,3)
GO
INSERT INTO mytable (C1,C2,C3) VALUES(4,5,6)
 etc ...
mljm
источник
GOтакое пакетный разделитель? Это не помогает предотвратить дублирование записей.
Дейл К