SQL 服务器唯一约束 "Silent" 还是会引发异常?
Are SQL Server unique constraints "Silent" or do they raise an exception?
我想防止在我的表中插入重复值。起初,我添加了代码来检查该值是否已存在于数据库中,但如果我可以在 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, 1) NOT NULL,
[ActorId] CHAR(9) NOT NULL,
[Actor] VARCHAR(50) NOT NULL,
PRIMARY KEY CLUSTERED ([Id] ASC),
CONSTRAINT [CK_ACTORS_Column]
UNIQUE NONCLUSTERED ([ActorId] ASC)
);
我希望约束能够防止第二个相同的 ActorId
而不会抱怨 [g]。 IOW,绕过它,不要告诉我它,不要停止应用程序或抛出异常。
它是这样工作的(静默地),还是会抛出异常?
让我们试试看:
insert into actors (actorid,actor) values('foo', 'bar');
-- 1 row affected
insert into actors (actorid, actor) values('foo', '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 ).
违反唯一约束确实会引发错误。这就是数据库让您知道出现问题的方式。
SQL 与许多其他数据库(MySQL、Postgres、SQLite. ..).解决方法是用 not exists
和子查询重写 insert
:
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', 'bar')) v(actorid, actor)
on v.actorid = a.actorid
when not matched then insert (actorid, actor)
values (v.actorid, v.actor)
@GMB 在他的回答中写道“SQL 服务器 built-in 没有选项(据我所知)可以忽略此类错误”。
正如评论中 @a_horse_with_no_name 指出的那样,这里有一个与索引相关的 IGNORE_DUP_KEY
选项。
你说:
I want the constraint to prevent a second identical ActorId without
whin[g]ing about it. IOW, just bypass it, don't tell me about it,
don't stop the app or throw an exception.
可以通过这个选项实现。
首先,我应该指出,当您创建唯一约束时
CONSTRAINT [CK_ACTORS_Column] UNIQUE NONCLUSTERED ([ActorId] ASC)
引擎将创建一个唯一索引来强制执行约束。约束是逻辑概念,索引是概念的物理实现。
您可以通过只创建一个索引,一个唯一索引来达到同样的效果。创建索引时,您可以指定各种选项,包括 IGNORE_DUP_KEY
选项。
IGNORE_DUP_KEY = { ON | OFF }
Specifies the error response when an
insert operation attempts to insert duplicate key values into a unique
index. The IGNORE_DUP_KEY option applies only to insert operations
after the index is created or rebuilt. The option has no effect when
executing CREATE INDEX, ALTER INDEX, or UPDATE. The default is OFF.
ON A warning message will occur when duplicate key values are inserted
into a unique index. Only the rows violating the uniqueness constraint
will fail.
OFF An error message will occur when duplicate key values are inserted
into a unique index. The entire INSERT operation will be rolled back.
默认情况下,此选项为 OFF
,因此尝试插入重复的键值将失败并出现错误。服务器将回滚 INSERT 操作并将此错误消息发送给您的应用程序,这将取决于您的应用程序如何处理它。如果您的应用程序不期望它,它可能会抛出一些异常。
如果您将此选项设置为 ON
,您的应用程序将不再收到错误消息。它会收到一条警告消息,大多数应用程序通常会忽略该消息。因此,看起来服务器会默默地忽略重复值并仅插入那些不重复的值。
默默地忽略问题很少是一种理想的行为,但如果你真的知道自己在做什么,你就可以做到。
这是一个简短的演示。
让我们从您的 table
开始
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)
table 为空。让我们尝试插入一些值。
insert into actors (actorid, actor) values('foo', 'bar');
-- (1 row affected)
让我们尝试插入一个重复值:
insert into actors (actorid, actor) values('foo', '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', 'baz'),
('fo2', 'baz'),
('fo3', 'baz'),
('fo3', 'baz'),
('fo3', 'baz'),
('fo4', 'baz');
--Msg 2601, Level 14, State 1, 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.
让我们看看 table 现在有什么。
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)
table 为空。让我们尝试插入一些值。
insert into actors (actorid, actor) values('foo', 'bar');
-- (1 row affected)
让我们尝试插入一个重复值:
insert into actors (actorid, actor) values('foo', 'baz');
--Duplicate key was ignored.
--(0 rows affected)
如您所见,现在不是错误信息。这只是一个警告“重复键被忽略。”
让我们尝试在带有一些重复项的单个语句中插入多个值:
insert into actors (actorid, actor) values
('foo', 'baz1'),
('fo2', 'baz2'),
('fo2', 'baz3'),
('fo3', 'baz4'),
('fo3', 'baz5'),
('fo3', 'baz6'),
('fo4', 'baz7');
--Duplicate key was ignored.
--(3 rows affected)
在这里你可以看到在 7 行中只插入了 3 行。
让我们看看 table 现在有什么。
SELECT * FROM Actors;
+----+-----------+-------+
| Id | ActorId | Actor |
+----+-----------+-------+
| 1 | foo | bar |
| 4 | fo2 | baz2 |
| 6 | fo3 | baz4 |
| 9 | fo4 | baz7 |
+----+-----------+-------+
您可以看到最后一个 INSERT
语句插入了 non-duplicate 个值。另外,仔细查看 ID
列中的值。 IDENTITY
值有间隙,因为它们是为尝试插入的每一行生成的,并且其中一些行被唯一索引拒绝。
总体而言,此索引选项主要用于需要在单个 INSERT
语句中批量插入大量行的情况,但您希望 一些[=这些行的 101=] 可能违反唯一约束。您不希望整个大型 INSERT
语句失败,您只想忽略少数违规行。如果没有此选项,您将不得不尝试插入值 one-by-one、row-by-row,这可能比单个 INSERT
语句慢得多。
我想防止在我的表中插入重复值。起初,我添加了代码来检查该值是否已存在于数据库中,但如果我可以在 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, 1) NOT NULL,
[ActorId] CHAR(9) NOT NULL,
[Actor] VARCHAR(50) NOT NULL,
PRIMARY KEY CLUSTERED ([Id] ASC),
CONSTRAINT [CK_ACTORS_Column]
UNIQUE NONCLUSTERED ([ActorId] ASC)
);
我希望约束能够防止第二个相同的 ActorId
而不会抱怨 [g]。 IOW,绕过它,不要告诉我它,不要停止应用程序或抛出异常。
它是这样工作的(静默地),还是会抛出异常?
让我们试试看:
insert into actors (actorid,actor) values('foo', 'bar');
-- 1 row affected
insert into actors (actorid, actor) values('foo', '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 ).
违反唯一约束确实会引发错误。这就是数据库让您知道出现问题的方式。
SQL 与许多其他数据库(MySQL、Postgres、SQLite. ..).解决方法是用 not exists
和子查询重写 insert
:
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', 'bar')) v(actorid, actor)
on v.actorid = a.actorid
when not matched then insert (actorid, actor)
values (v.actorid, v.actor)
@GMB 在他的回答中写道“SQL 服务器 built-in 没有选项(据我所知)可以忽略此类错误”。
正如评论中 @a_horse_with_no_name 指出的那样,这里有一个与索引相关的 IGNORE_DUP_KEY
选项。
你说:
I want the constraint to prevent a second identical ActorId without whin[g]ing about it. IOW, just bypass it, don't tell me about it, don't stop the app or throw an exception.
可以通过这个选项实现。
首先,我应该指出,当您创建唯一约束时
CONSTRAINT [CK_ACTORS_Column] UNIQUE NONCLUSTERED ([ActorId] ASC)
引擎将创建一个唯一索引来强制执行约束。约束是逻辑概念,索引是概念的物理实现。
您可以通过只创建一个索引,一个唯一索引来达到同样的效果。创建索引时,您可以指定各种选项,包括 IGNORE_DUP_KEY
选项。
IGNORE_DUP_KEY = { ON | OFF }
Specifies the error response when an insert operation attempts to insert duplicate key values into a unique index. The IGNORE_DUP_KEY option applies only to insert operations after the index is created or rebuilt. The option has no effect when executing CREATE INDEX, ALTER INDEX, or UPDATE. The default is OFF.
ON A warning message will occur when duplicate key values are inserted into a unique index. Only the rows violating the uniqueness constraint will fail.
OFF An error message will occur when duplicate key values are inserted into a unique index. The entire INSERT operation will be rolled back.
默认情况下,此选项为 OFF
,因此尝试插入重复的键值将失败并出现错误。服务器将回滚 INSERT 操作并将此错误消息发送给您的应用程序,这将取决于您的应用程序如何处理它。如果您的应用程序不期望它,它可能会抛出一些异常。
如果您将此选项设置为 ON
,您的应用程序将不再收到错误消息。它会收到一条警告消息,大多数应用程序通常会忽略该消息。因此,看起来服务器会默默地忽略重复值并仅插入那些不重复的值。
默默地忽略问题很少是一种理想的行为,但如果你真的知道自己在做什么,你就可以做到。
这是一个简短的演示。
让我们从您的 table
开始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)
table 为空。让我们尝试插入一些值。
insert into actors (actorid, actor) values('foo', 'bar');
-- (1 row affected)
让我们尝试插入一个重复值:
insert into actors (actorid, actor) values('foo', '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', 'baz'),
('fo2', 'baz'),
('fo3', 'baz'),
('fo3', 'baz'),
('fo3', 'baz'),
('fo4', 'baz');
--Msg 2601, Level 14, State 1, 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.
让我们看看 table 现在有什么。
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)
table 为空。让我们尝试插入一些值。
insert into actors (actorid, actor) values('foo', 'bar');
-- (1 row affected)
让我们尝试插入一个重复值:
insert into actors (actorid, actor) values('foo', 'baz');
--Duplicate key was ignored.
--(0 rows affected)
如您所见,现在不是错误信息。这只是一个警告“重复键被忽略。”
让我们尝试在带有一些重复项的单个语句中插入多个值:
insert into actors (actorid, actor) values
('foo', 'baz1'),
('fo2', 'baz2'),
('fo2', 'baz3'),
('fo3', 'baz4'),
('fo3', 'baz5'),
('fo3', 'baz6'),
('fo4', 'baz7');
--Duplicate key was ignored.
--(3 rows affected)
在这里你可以看到在 7 行中只插入了 3 行。
让我们看看 table 现在有什么。
SELECT * FROM Actors;
+----+-----------+-------+
| Id | ActorId | Actor |
+----+-----------+-------+
| 1 | foo | bar |
| 4 | fo2 | baz2 |
| 6 | fo3 | baz4 |
| 9 | fo4 | baz7 |
+----+-----------+-------+
您可以看到最后一个 INSERT
语句插入了 non-duplicate 个值。另外,仔细查看 ID
列中的值。 IDENTITY
值有间隙,因为它们是为尝试插入的每一行生成的,并且其中一些行被唯一索引拒绝。
总体而言,此索引选项主要用于需要在单个 INSERT
语句中批量插入大量行的情况,但您希望 一些[=这些行的 101=] 可能违反唯一约束。您不希望整个大型 INSERT
语句失败,您只想忽略少数违规行。如果没有此选项,您将不得不尝试插入值 one-by-one、row-by-row,这可能比单个 INSERT
语句慢得多。