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行
关键字“ WHERE”附近的语法错误。


10
您不应仅依靠此检查来确保没有重复项,它也不是线程安全的,并且在满足竞争条件时您将获得重复项。如果确实需要唯一数据,请向表中添加唯一约束,然后捕获唯一约束违反错误。看到这个答案
GarethD 2014年

1
您可以使用MERGE查询或如果不存在(选择语句)开始插入值END
Abdul Hannan Ijaz

是否要中继此检查取决于方案。例如,如果您正在开发将数据写入“静态”表的部署脚本,那么这不是问题。
AxelWass


2
@GarethD:“不安全线程”是什么意思?它可能并不优雅,但对我来说似乎是正确的。单个insert语句始终是单个事务。好像SQL Server先评估子查询,然后再在以后的某个点(不持有锁)继续执行插入操作。
艾德·阿维斯

Answers:


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的指点)

请注意,在高负载下,这有时仍会失败,因为第二个连接可以在第一个连接执行INSERT(即竞争条件)之前通过IF NOT EXISTS测试。有关为何什至在事务中包装也不能解决此问题的良好答案,请参见stackoverflow.com/a/3791506/1836776


20
请注意,在高负载下,这有时仍会失败,因为第二个连接可以在第一个连接执行INSERT(即竞争条件)之前通过IF NOT EXISTS测试。有关为何什至在事务中换行也不能解决此问题的良好答案,请参见stackoverflow.com/a/3791506/1836776
Marc Durdin 2014年

11
从EmailsRecebidos中选择1,其中De = @_DE和Assunto = @_ASSUNTO AND数据= @_DATA使用1代替*会更有效
Reno

1
在整个事情上放一个写锁,这样您就不会有重复的机会。
凯文·芬肯宾德

10
@jazzcat select *在这种情况下没有任何区别,因为它在EXISTS子句中使用。SQL Server将始终对其进行优化,并且已经进行了很长时间了。由于我很老,所以通常将这些查询写为,EXISTS (SELECT 1 FROM...)但现在不再需要。
Loudenvier

16
为什么这种简单的问题比确定性引起更多的怀疑?
drowa

77

对于那些寻求最快方法的人,我最近遇到了这些基准测试,显然使用“ INSERT SELECT ... EXCEPT SELECT ...”证明是对5000万条或以上记录最快的基准

这是文章中的一些示例代码(第三个代码块是最快的):

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
Bryan

1
我第一次使用EXCEPT。简洁大方。
jhowe

但是EXCEPT对于批量操作可能不是有效的。
Aasish Kr。Sharma

EXCEPT不是那么有效。
Biswa

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
您需要为该过程处理错误,因为在某些情况下,检查和插入之间会发生插入。
Filip De Vos 2014年

@FilipDeVos:是的-一种可能性,也许不太可能,但仍然是一种可能性。好点子。
marc_s 2014年

如果您将两者都包装在交易中怎么办?那会阻止这种可能性吗?(我不是交易专家,所以如果这是一个愚蠢的问题,请原谅。)
David David

1
有关为什么交易无法解决此问题的好答案,请参见stackoverflow.com/a/3791506/1836776 @David。
马克·杜丁

在IF语句中:即使您使用了多行,如果所需的命令行数仅为一,也无需使用BEGIN&END,因此您可以在此处省略它。
Wessam El Mahdy'3

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

除了IF EXISTS之外,还取决于您的SQL Server版本(2012?),您还可以像下面这样使用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

1

不同的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语句的执行。在我的情况下,我有几千条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 ...

GO是批处理分离器?它无助于防止重复记录。
Dale K
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.