微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

SQL Server的唯一约束是“无声的”还是引发异常?

如何解决SQL Server的唯一约束是“无声的”还是引发异常?

我想防止在表中插入重复的值。首先,我添加代码来检查该值是否已经存在于数据库中,但是如果我可以在DDL级别上阻止它,那似乎是很多开销/浪费时间。

因此我找到了this并从中更改了我的一张桌子(作为示例):

CREATE TABLE [dbo].[ACTORS] 
(
    [Id]      INT IDENTITY (1,1) NOT NULL,[ActorId] CHAR(9)     NOT NULL,[Actor]   VARCHAR(50) NOT NULL,PRIMARY KEY CLUSTERED ([Id] ASC),);

对此:

CREATE TABLE [dbo].[ACTORS] 
(
    [Id]      INT IDENTITY (1,CONSTRAINT [CK_ACTORS_Column] 
        UNIQUE NONCLUSTERED ([ActorId] ASC)
);

我希望约束条件阻止第二个相同的ActorId而不发牢骚。 IOW,绕开它,不要告诉我,不要停止应用程序或引发异常。

这是(静地)工作的方式吗,还是会引发异常?

解决方法

让我们尝试一下

insert into actors (actorid,actor) values('foo','bar');
-- 1 row affected

insert into actors (actorid,'baz');
-- Msg 2627 Level 14 State 1 Line 1
-- Violation of UNIQUE KEY constraint 'CK_ACTORS_Column'. 
-- Cannot insert duplicate key in object 'dbo.ACTORS'. The duplicate key value is (foo      ).

唯一约束违反确实会引发错误。这样数据库就可以让您知道出了什么问题。

与许多其他数据库(MySQL,Postgres,SQLite ...)不同,SQL Server内置的(我所知道的)无选项可以忽略这种错误。一种解决方法是用insert和一个子查询重写not exists

insert into actors (actorid,actor)
select v.*
from (values ('foo','bar')) v(actorid,actor)
where not exists (select 1 from actor a where a.actorid = v.actorid)

另一种选择是merge语句:

merge into actors a
using (values ('foo',actor)
on v.actorid = a.actorid
when not matched then insert (actorid,actor)
values (v.actorid,v.actor)
,

@GMB在他的回答中写道:“ SQL Server内置了(我知道)没有选择来忽略此类错误”。

正如@a_horse_with_no_name在评论中指出的,此处有一个与索引相关的IGNORE_DUP_KEY选项。

您说:

我希望约束阻止第二个相同的ActorId 抱怨。 IOW,绕开它,不要告诉我, 不要停止应用程序或引发异常。

可以通过此选项实现。


首先,我应该指出,当您创建唯一约束时

CONSTRAINT [CK_ACTORS_Column] UNIQUE NONCLUSTERED ([ActorId] ASC)

引擎将创建一个唯一索引以强制执行约束。约束是逻辑概念,索引是概念的物理实现。

您可以通过仅创建索引(唯一索引)来达到相同的效果。创建索引时,您可以指定各种选项,包括IGNORE_DUP_KEY选项。

IGNORE_DUP_KEY = {开|关闭}

指定当 插入操作尝试将重复的键值插入唯一的 指数。 IGNORE_DUP_KEY选项仅适用于插入操作 在创建或重建索引之后。该选项在以下情况下无效 执行CREATE INDEX,ALTER INDEX或UPDATE。默认值为OFF。

打开:插入重复的键值时将出现警告消息 成为唯一索引。只有违反唯一性约束的行 将失败。

关闭:插入重复的键值时将出现错误消息 成为唯一索引。整个INSERT操作将回滚。

默认情况下,此选项为OFF,因此,尝试插入重复的键值将失败并显示错误。服务器将回滚INSERT操作并将此错误消息发送到您的应用程序,这将取决于您的应用程序如何处理它。如果您的应用程序不期望它,则可能会引发一些异常。

如果将此选项设置为ON,则您的应用程序将不再收到错误消息。它将收到警告消息,大多数应用程序通常会忽略该消息。因此,看起来服务器将静默地忽略重复值,而仅插入那些不重复的值。

安静地忽略问题很少是一种期望的行为,但是如果您真的知道自己在做什么,就可以做到。


这是一个简短的演示。

让我们从您的桌子开始

CREATE TABLE [dbo].[ACTORS] 
(
    [Id]      INT IDENTITY (1,1) NOT NULL,[ActorId] CHAR(9)     NOT NULL,[Actor]   VARCHAR(50) NOT NULL,PRIMARY KEY CLUSTERED ([Id] ASC),);

选项1.默认。 IGNORE_DUP_KEY = OFF

CREATE UNIQUE NONCLUSTERED INDEX [IX_ActorID] ON [dbo].[ACTORS]
(
    [ActorId] ASC
) WITH (IGNORE_DUP_KEY = OFF)

该表为空。让我们尝试插入一些值。

insert into actors (actorid,'bar');

-- (1 row affected)

让我们尝试插入重复的值:

insert into actors (actorid,'baz');

--Msg 2601,Level 14,State 1,Line 4
--Cannot insert duplicate key row in object 'dbo.ACTORS' with unique index 'IX_ActorID'. 
--The duplicate key value is (foo      ).
--The statement has been terminated.

让我们尝试在带有一些重复项的单个语句中插入多个值:

insert into actors (actorid,actor) values
('foo','baz'),('fo2',('fo3',('fo4',Line 16
--Cannot insert duplicate key row in object 'dbo.ACTORS' with unique index 'IX_ActorID'. 
--The duplicate key value is (foo      ).
--The statement has been terminated.

现在让我们看看表中的内容。

SELECT * FROM Actors;

+----+-----------+-------+
| Id |  ActorId  | Actor |
+----+-----------+-------+
|  1 | foo       | bar   |
+----+-----------+-------+

仅第一个INSERT语句成功,并且仅插入了一行。

现在,清理。

DROP INDEX [IX_ActorID] ON [dbo].[ACTORS];
TRUNCATE TABLE dbo.Actors;

选项2。IGNORE_DUP_KEY = ON

CREATE UNIQUE NONCLUSTERED INDEX [IX_ActorID] ON [dbo].[ACTORS]
(
    [ActorId] ASC
) WITH (IGNORE_DUP_KEY = ON)

该表为空。让我们尝试插入一些值。

insert into actors (actorid,'baz');

--Duplicate key was ignored.
--(0 rows affected)

如您所见,现在这不是错误消息。只是警告“重复键被忽略。”

让我们尝试在带有一些重复项的单个语句中插入多个值:

insert into actors (actorid,'baz1'),'baz2'),'baz3'),'baz4'),'baz5'),'baz6'),'baz7');

--Duplicate key was ignored.
--(3 rows affected)

在这里您可以看到7行中仅插入了3行。

现在让我们看看表中的内容。

SELECT * FROM Actors;

+----+-----------+-------+
| Id |  ActorId  | Actor |
+----+-----------+-------+
|  1 | foo       | bar   |
|  4 | fo2       | baz2  |
|  6 | fo3       | baz4  |
|  9 | fo4       | baz7  |
+----+-----------+-------+

您可以看到最后一个INSERT语句插入了非重复值。另外,请仔细查看ID列中的值。 IDENTITY值之间存在间隙,因为它们是为尝试插入的每一行生成的,并且其中某些行被唯一索引拒绝。


总体而言,此索引选项主要用于以下情况:需要在单个INSERT语句中批量插入很多行,但是您希望其中的 some 行可能违反唯一约束。您不希望整个大型INSERT语句失败,而只希望忽略很少的违规行。如果没有此选项,则必须尝试逐行,逐行地插入值,这可能比单个INSERT语句要慢得多。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。