添加检查单独(链接)table 值的约束
Adding constraints that check a separate (linked) table for a value
我有两个 table:
Book(BookID, Title, Author, Decision)
BookShipment(BookID, ShipmentID)
CREATE TABLE BookShipment(
BookID CHAR(4),
ShipmentID(7)
CONSTRAINT pk_BookShipment PRIMARY KEY (BookID, ShipmentID),
CONSTRAINT fk_BookShipment_Book FOREIGN KEY (BookID) REFERENCES Book(BookID));
想法是,一本书在添加到装运之前需要经过“批准”。如果是“已拒绝”,则不会添加。
有没有办法向 BookShipment
添加额外的约束,当添加新的 BookID
时,将检查 Book
[= 下的 Decision
30=] 等于 Approved
(对于那个 BookID
)?
如果您总是只有一个状态要检查,这可以通过 FK 约束的小技巧来完成:
- 在
Books(BookId, Decision)
上创建虚拟唯一索引。
- 将计算列添加到
BookShipment
,值为 Approved
。
- 在FK约束中引用创建的唯一索引。
在 CHECK
约束中定义 UDF 应该是更灵活的方式。
create table book (
BookID int identity(1,1) primary key,
Title varchar(100),
Author varchar(100),
Decision varchar(100),
--Dummy constraint for FK
constraint u_book unique(bookid, decision)
);
CREATE TABLE BookShipment(
BookID int,
ShipmentID varchar(7),
--Dummy column for FK
approved as cast('Approved' as varchar(100)) persisted
CONSTRAINT pk_BookShipment PRIMARY KEY (BookID),
CONSTRAINT fk_BookShipment_Book_Approved
FOREIGN KEY (BookID, approved)
REFERENCES Book(BookID, decision)
);
insert into book (Title, Author, Decision)
select 'A', 'B', 'Approved' union all
select 'A', 'B', 'New'
;
--2 rows affected
insert into BookShipment values(1, 1);
--1 rows affected
insert into BookShipment values(2, 2);
/*
insert into BookShipment values(2, 2);
Msg 547 Level 16 State 0 Line 1
The INSERT statement conflicted with the FOREIGN KEY constraint "fk_BookShipment_Book_Approved". The conflict occurred in database "fiddle_ea408f09b06247a78b47ea9c353eda10", table "dbo.book".
Msg 3621 Level 0 State 0 Line 1
The statement has been terminated.
*/
db<>fiddle here
如果您只有一件事要检查,那么 会很适合您。但是,如果您有任意约束,尤其是跨越多个 tables,则有一个更灵活的不同选项。
它基于涉及 索引视图 的技巧。我从 spaghettidba.
的一篇文章中得到了这个
索引视图是持久保存到磁盘的视图。我们通过在视图上创建聚簇索引来创建它。它有很多限制,在我们的例子中至关重要的是我们不能使用 left/right/full join
,只允许使用 inner
。它还必须是架构绑定的(您不能更改基础列),并且必须引用具有两部分名称的 tables。
让我们假设您的约束条件的反面成立:BookShipment
中 有 行,其中相关的 Book
不是 [=18] =].我们怎么能在视图中看到这样的Books
:
CREATE /* OR ALTER */ VIEW dbo.vwNonApprovedBooks
WITH SCHEMABINDING
AS
SELECT b.BookId
FROM dbo.BookShipment AS bs
JOIN dbo.Book AS b ON b.BookID = bs.BookID
WHERE b.Decision <> 'Approved';
GO
我们可以通过创建聚集索引来对其进行索引,还不要这样做:
CREATE UNIQUE CLUSTERED INDEX CX_vwNonApprovedBooks ON dbo.vwNonApprovedBooks (BookId);
下面我们来拉个小把戏。如果我们想要停止此视图中存在的任何行,我们需要强制每个插入的行相乘,以使其不符合唯一约束。
让我们为此创建一个 table:
CREATE TABLE dbo.DummyTwoRows (x bit NOT NULL PRIMARY KEY);
GO
INSERT dbo.DummyTwoRows VALUES (0),(1);
现在我们可以像这样重新定义视图:
CREATE /* OR ALTER */ VIEW dbo.vwNonApprovedBooks
WITH SCHEMABINDING
AS
SELECT 1 AS DummyOne
FROM dbo.BookShipment AS bs
JOIN dbo.Book AS b ON b.BookID = bs.BookID
CROSS JOIN dbo.DummyTwoRows
WHERE b.Decision <> 'Approved';
GO
CREATE UNIQUE CLUSTERED INDEX CX_vwNonApprovedBooks ON dbo.vwNonApprovedBooks (DummyOne);
并且在 BookShipment
中插入 Book
而不是 Approved
时,唯一约束将失败。
SQL 服务器将在插入和更新时维护此视图,因此如果将 Book
更改为非 Approved
,它具有 BookShipment
,则约束将更新也失败了。
请注意,此索引不占用 space,因为其中从来没有任何行。
我有两个 table:
Book(BookID, Title, Author, Decision)
BookShipment(BookID, ShipmentID)
CREATE TABLE BookShipment(
BookID CHAR(4),
ShipmentID(7)
CONSTRAINT pk_BookShipment PRIMARY KEY (BookID, ShipmentID),
CONSTRAINT fk_BookShipment_Book FOREIGN KEY (BookID) REFERENCES Book(BookID));
想法是,一本书在添加到装运之前需要经过“批准”。如果是“已拒绝”,则不会添加。
有没有办法向 BookShipment
添加额外的约束,当添加新的 BookID
时,将检查 Book
[= 下的 Decision
30=] 等于 Approved
(对于那个 BookID
)?
如果您总是只有一个状态要检查,这可以通过 FK 约束的小技巧来完成:
- 在
Books(BookId, Decision)
上创建虚拟唯一索引。 - 将计算列添加到
BookShipment
,值为Approved
。 - 在FK约束中引用创建的唯一索引。
在 CHECK
约束中定义 UDF 应该是更灵活的方式。
create table book (
BookID int identity(1,1) primary key,
Title varchar(100),
Author varchar(100),
Decision varchar(100),
--Dummy constraint for FK
constraint u_book unique(bookid, decision)
);
CREATE TABLE BookShipment(
BookID int,
ShipmentID varchar(7),
--Dummy column for FK
approved as cast('Approved' as varchar(100)) persisted
CONSTRAINT pk_BookShipment PRIMARY KEY (BookID),
CONSTRAINT fk_BookShipment_Book_Approved
FOREIGN KEY (BookID, approved)
REFERENCES Book(BookID, decision)
);
insert into book (Title, Author, Decision)
select 'A', 'B', 'Approved' union all
select 'A', 'B', 'New'
;
--2 rows affected
insert into BookShipment values(1, 1);
--1 rows affected
insert into BookShipment values(2, 2);
/*
insert into BookShipment values(2, 2);
Msg 547 Level 16 State 0 Line 1
The INSERT statement conflicted with the FOREIGN KEY constraint "fk_BookShipment_Book_Approved". The conflict occurred in database "fiddle_ea408f09b06247a78b47ea9c353eda10", table "dbo.book".
Msg 3621 Level 0 State 0 Line 1
The statement has been terminated.
*/
db<>fiddle here
如果您只有一件事要检查,那么
它基于涉及 索引视图 的技巧。我从 spaghettidba.
的一篇文章中得到了这个索引视图是持久保存到磁盘的视图。我们通过在视图上创建聚簇索引来创建它。它有很多限制,在我们的例子中至关重要的是我们不能使用 left/right/full join
,只允许使用 inner
。它还必须是架构绑定的(您不能更改基础列),并且必须引用具有两部分名称的 tables。
让我们假设您的约束条件的反面成立:BookShipment
中 有 行,其中相关的 Book
不是 [=18] =].我们怎么能在视图中看到这样的Books
:
CREATE /* OR ALTER */ VIEW dbo.vwNonApprovedBooks
WITH SCHEMABINDING
AS
SELECT b.BookId
FROM dbo.BookShipment AS bs
JOIN dbo.Book AS b ON b.BookID = bs.BookID
WHERE b.Decision <> 'Approved';
GO
我们可以通过创建聚集索引来对其进行索引,还不要这样做:
CREATE UNIQUE CLUSTERED INDEX CX_vwNonApprovedBooks ON dbo.vwNonApprovedBooks (BookId);
下面我们来拉个小把戏。如果我们想要停止此视图中存在的任何行,我们需要强制每个插入的行相乘,以使其不符合唯一约束。
让我们为此创建一个 table:
CREATE TABLE dbo.DummyTwoRows (x bit NOT NULL PRIMARY KEY);
GO
INSERT dbo.DummyTwoRows VALUES (0),(1);
现在我们可以像这样重新定义视图:
CREATE /* OR ALTER */ VIEW dbo.vwNonApprovedBooks
WITH SCHEMABINDING
AS
SELECT 1 AS DummyOne
FROM dbo.BookShipment AS bs
JOIN dbo.Book AS b ON b.BookID = bs.BookID
CROSS JOIN dbo.DummyTwoRows
WHERE b.Decision <> 'Approved';
GO
CREATE UNIQUE CLUSTERED INDEX CX_vwNonApprovedBooks ON dbo.vwNonApprovedBooks (DummyOne);
并且在 BookShipment
中插入 Book
而不是 Approved
时,唯一约束将失败。
SQL 服务器将在插入和更新时维护此视图,因此如果将 Book
更改为非 Approved
,它具有 BookShipment
,则约束将更新也失败了。
请注意,此索引不占用 space,因为其中从来没有任何行。