【问题标题】:Adding constraints that check a separate (linked) table for a value添加检查单独(链接)表的值的约束
【发布时间】:2021-04-25 18:37:56
【问题描述】:

我有两张桌子:

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 是否等于Approved(对于那个@987654331 @)?

【问题讨论】:

  • 这能回答你的问题吗? Sub queries in check constraint
  • @astentx 看起来可能。然而,看起来好像操作没有在他的表中设置外键,而我有 - 所以不确定我是否需要创建子查询或函数。我认为使用 CHECK 功能可能会起作用,但我不确定如何实现它。
  • 在表 BookID 上有一个主键 BookShipment 看起来不对。这意味着您只能运送一本书一次;永远。
  • @AaronWright 否,this is the limitation是用于 CHECK 约束的逻辑表达式,返回 TRUE 或 FALSE。与 CHECK 约束一起使用的logical_expression 不能引用另一个表,但可以为同一行引用同一表中的其他列。表达式不能引用别名数据类型。很少的描述
  • @Larnu 认为我在外面输入时犯了一个错误。主键应该是(BookID, ShipmentID)

标签: sql sql-server database-design


【解决方案1】:

如果你总是要检查一个状态,这可以通过 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小提琴here

【讨论】:

  • @astentx... 谢谢。我会试一试。但是,其中很多似乎超出了我的范围。这只是你花在练习 SQL 和数据库创建上的时间越长学到的东西吗?
  • @AaronWright 我认为最重要的部分是了解您的数据库可以提供的功能,它如何处理数据以及为什么会存在一些限制(拥有它们的功能原因是什么) .当然,实践有很大的影响,因为它创造了很多需要解决的案例。但我不知道一些正式的方法或待办事项清单来发明解决方法)
【解决方案2】:

如果您只有一件事情要检查,那么astentex's answer 会很好地帮助您。但如果您有任意约束,尤其是跨越多个表,则有一个更灵活的不同选项。

它基于一个涉及索引视图的技巧。这是我从spaghettidba 的一篇文章中得到的。

索引视图是持久化到磁盘的视图。我们通过在视图上创建聚集索引来创建它。它有很多限制,关键是我们不能使用left/right/full join,只允许使用inner。它还必须是模式绑定的(您不能更改基础列),并且必须引用具有两部分名称的表。

让我们假设与您的约束相反的情况是正确的:BookShipment 中有 行,其中相关的 Book 不是 Approved。我们怎么能在视图中看到这样的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);

现在我们将使用一个小技巧。如果我们想停止该视图中存在的任何行,我们需要强制每个插入的行相乘,以使其无法满足唯一约束。

让我们为此创建一个表:

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

并且在任何插入BookShipmentBook 不是Approved 时,唯一约束将失败。

SQL Server 将在插入和更新时维护此视图,因此如果将 Book 更改为具有 BookShipment 的非 Approved,则约束也将导致更新失败。

请注意,此索引不占用空间,因为其中从来没有任何行。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-01-04
    • 2015-01-14
    • 1970-01-01
    • 1970-01-01
    • 2016-11-08
    • 2014-05-11
    相关资源
    最近更新 更多