Вставка 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
Ви не повинні покладатися на цю перевірку самостійно, щоб уникнути дублікатів, вона не є безпечною для потоків, і ви отримаєте дублікати, коли буде дотримано умову гонки. Якщо вам справді потрібні унікальні дані, додайте до таблиці унікальне обмеження, а потім вкажіть унікальну помилку порушення обмежень. Дивіться цю відповідь
ГаретD

1
Ви можете використовувати запит MERGE або Якщо його немає (виберіть операцію), почніть вставляти значення END
Abdul Hannan Ijaz

Залежить від сценарію, чи слід ви переходити на цю перевірку чи ні. Якщо ви розробляєте сценарій розгортання, який записує дані, наприклад, у "статичну" таблицю, це не проблема.
AxelWass

ви можете використовувати "якщо його немає (виберіть * з ...", наприклад, цей stackoverflow.com/a/43763687/2736742
А. Морель

2
@GarethD: що ви маєте на увазі "не безпечно для потоків"? Це може бути не елегантно, але мені це здається правильним. Одноразова insertвиписка - це завжди одна операція. Це не так, якби SQL Server спочатку оцінює підзапит, а потім у якийсь пізній момент і, не тримаючи блокування, продовжує робити вставку.
Ед Авіс

Відповіді:


322

замість нижче коду

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, щоб отримати хорошу відповідь, чому навіть завершення транзакції не вирішує цього питання.


20
Зауважте, що при великому навантаженні це все одно іноді не вдасться, оскільки друге з'єднання може пройти тест IF NOT EXISTS до того, як перше з'єднання виконає INSERT, тобто стан перегонів. Див. Розділ stackoverflow.com/a/3791506/1836776, щоб отримати хорошу відповідь про те, чому навіть завершення транзакції не вирішує цього питання.
Марк Дурдін,

11
ВИБІР 1 З електронної поштиRecebidos, де De = @_DE І Assunto = @_ASSUNTO І дані = @_DATA Використовувати 1 замість * було б більш ефективно
Reno

1
Поставте блокування запису навколо всієї справи, і тоді у вас не буде жодного шансу на копії.
Кевін Фінкенбіндер

10
@jazzcat select *в цьому випадку не має ніякої різниці, оскільки він використовується в EXISTSпункті. SQL Server завжди оптимізує його та робить це протягом століть. Оскільки я дуже стара, я зазвичай пишу ці запити як, EXISTS (SELECT 1 FROM...)але вони більше не потрібні.
Луденв'є

16
Чому такий простий питання викликає більше сумнівів, ніж впевненість?
drowa

77

Для тих, хто шукає найшвидший спосіб , я нещодавно натрапив на ці орієнтири, де, мабуть, використання "INSERT SELECT ... EXCEPT SELECT ..." виявилося найшвидшим для 50 мільйонів записів або більше.

Ось приклад коду зі статті (третій блок коду був найшвидшим):

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
Мені подобається ВІДКРИТИЙ ВИБІР
Брайан

1
Перший раз я використав EXCEPT. Простий і елегантний.
jhowe

Але EXCEPT може бути неефективним для масових операцій.
Аасіш Кр. Шарма

ОКРЕМ не настільки ефективний.
Бісва

1
@Biswa: Не відповідно до цих орієнтирів. Код доступний на сайті. Не соромтеся запустити його у вашій системі, щоб побачити порівняння результатів.

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

я збираюся з цим, тому що його шанувальник
jokab

Я б хотів використовувати об'єднання ... але це не працює для оптимізованих пам'яті таблиць.
Дон Сем

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

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

1
Потрібно обробляти помилки для цієї процедури, оскільки трапляються випадки, коли між чеком та вставкою буде вставлення.
Філіп Де Вос

@FilipDeVos: true - можливість, можливо, не дуже вірогідна, але все-таки можливість. Гарна думка.
marc_s

Що робити, якщо обернути обоє в рамках транзакції? Чи заблокує це можливість? (Я не фахівець з транзакцій, тож пробачте, якщо це дурне питання.)
Девід

1
Дивіться stackoverflow.com/a/3791506/1836776, щоб отримати хорошу відповідь про те, чому транзакція не вирішує цю проблему, @David.
Марк Дурдін,

У викладі IF: немає необхідності використовувати BEGIN & END, якщо кількість необхідних командних рядків становить лише один, навіть якщо ви використовували більше одного рядка, тому ви можете опустити його тут.
Wessam El Mahdy

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
Звичайно, це спрацювало, ви використовуєте тимчасову таблицю (тобто вам не потрібно турбуватися про одночасність під час використання тимчасових таблиць).
drowa

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')

-1

Як пояснено в коді нижче: Виконайте нижче запити та перевірте себе.

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.
vadiraj jahagirdar

-2

Ви можете використовувати GOкоманду. Це відновить виконання SQL-операторів після помилки. У моєму випадку у мене є декілька 1000 тверджень INSERT, де в базі вже є кілька записів, я просто не знаю, які з них. Я виявив, що після обробки декількох 100, виконання просто зупиняється повідомленням про помилку, яке не може, 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 ...

GOце пакетний сепаратор? Це не допомагає запобігти повторювані записи.
Дейл К
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.