【问题标题】:How do I create a multiple-table check constraint?如何创建多表检查约束?
【发布时间】:2010-04-06 20:31:23
【问题描述】:

请想象一下这个小型数据库...

图表

移除了死掉的 ImageShack 链接 - 志愿者数据库图表

表格

Volunteer     Event         Shift         EventVolunteer
=========     =====         =====         ==============
Id            Id            Id            EventId
Name          Name          EventId       VolunteerId
Email         Location      VolunteerId
Phone         Day           Description
Comment       Description   Start
                            End

关联

志愿者可以报名参加多项活动。
活动可能由多名志愿者担任。

一个事件可能有多个班次。
一个班次只属于一个事件。

一个班次只能由一名志愿者担任。
一名志愿者可以工作多个班次。

检查约束

  1. 我可以创建一个检查约束来 强制不配备班次 没有报名的志愿者 那个转变的事件?

  2. 我可以创建一个检查约束来 强制执行两个重叠的班次 从未配备过相同的人员 志愿者?

【问题讨论】:

  • 图表不见了(来自 ImageShack)。可以更新吗?

标签: sql-server check-constraints


【解决方案1】:

实施数据完整性的最佳位置是数据库。请放心,如果您允许,某些开发人员会有意或无意地找到一种方法将不一致的内容偷偷带入数据库!

这是一个带有检查约束的示例:

CREATE FUNCTION dbo.SignupMismatches()
RETURNS int
AS BEGIN RETURN (
    SELECT count(*)
    FROM Shift s
    LEFT JOIN EventVolunteer ev
    ON ev.EventId = s.EventId
    AND ev.VolunteerId = s.VolunteerId
    WHERE ev.Id is null
) END
go
ALTER TABLE Shift ADD CONSTRAINT chkSignup CHECK (dbo.SignupMismatches() = 0);
go
CREATE FUNCTION dbo.OverlapMismatches()
RETURNS int
AS BEGIN RETURN (
    SELECT count(*)
    FROM Shift a
    JOIN Shift b
    ON a.id <> b.id
    AND a.Start < b.[End]
    AND a.[End] > b.Start
    AND a.VolunteerId = b.VolunteerId
) END
go
ALTER TABLE Shift ADD CONSTRAINT chkOverlap CHECK (dbo.OverlapMismatches() = 0);

以下是新数据完整性检查的一些测试:

insert into Volunteer (name) values ('Dubya')
insert into Event (name) values ('Build Wall Around Texas')

-- Dubya tries to build a wall, but Fails because he's not signed up
insert into Shift (VolunteerID, EventID, Description, Start, [End]) 
    values (1, 1, 'Dunbya Builds Wall', '2010-01-01', '2010-01-02')

-- Properly signed up?  Good
insert into EventVolunteer (VolunteerID, EventID) 
    values (1, 1)
insert into Shift (VolunteerID, EventID, Description, Start, [End]) 
    values (1, 1, 'Dunbya Builds Wall', '2010-01-01', '2010-01-03')

-- Fails, you can't start the 2nd wall before you finished the 1st
insert into Shift (VolunteerID, EventID, Description, Start, [End]) 
    values (1, 1, 'Dunbya Builds Second Wall', '2010-01-02', '2010-01-03')

以下是表定义:

set nocount on
if OBJECT_ID('Shift') is not null
    drop table Shift
if OBJECT_ID('EventVolunteer') is not null
    drop table EventVolunteer
if OBJECT_ID('Volunteer') is not null
    drop table Volunteer
if OBJECT_ID('Event') is not null
    drop table Event
if OBJECT_ID('SignupMismatches') is not null
    drop function SignupMismatches
if OBJECT_ID('OverlapMismatches') is not null
    drop function OverlapMismatches

create table Volunteer (
    id int identity primary key
,   name varchar(50)
)
create table Event (
    Id int identity primary key
,   name varchar(50)
)
create table Shift (
    Id int identity primary key
,   VolunteerId int foreign key references Volunteer(id)
,   EventId int foreign key references Event(id)
,   Description varchar(250)
,   Start datetime
,   [End] datetime
)
create table EventVolunteer (
    Id int identity primary key
,   VolunteerId int foreign key references Volunteer(id)
,   EventId int foreign key references Event(id)
,   Location varchar(250)
,   [Day] datetime
,   Description varchar(250)
)

【讨论】:

  • 当从 EventVolunteer 表中删除一行时,我是否还需要一个约束? ALTER TABLE EventVolunteer ADD CONSTRAINT chkSignup2 CHECK (dbo.SignupMismatches() = 0); 或者,不需要吗?
  • @Zack:这很容易测试;我的猜测是,如果您允许删除,则第二个约束是必要的。作为最佳实践,完全禁止删除:最好使用位标志“已过时”。另请查看 Josh 的评论,它也会处理删除操作。
  • 请注意,我们在检查约束中使用 UDF 时遇到了一些麻烦。读者可能想在走这条路线之前查看How are my SQL Server constraints being bypassed?
  • 当心使用函数来强制执行这样的检查约束! t-sql 并不总是在更新语句之后检查函数,从而允许绕过约束。
【解决方案2】:

问题 1 很简单。只需让您的 Shift 表直接引用 EventVolunteer 表就可以了

【讨论】:

    【解决方案3】:

    我要做的是在 EventVolunteer 表上有一个自动递增的 Identity 列,对 EventId、VolunteerId 对有一个唯一约束。使用 EventVolunteerId(身份)作为 Shift 表的外键。这会相当简单地强制执行您想要的约束,同时稍微规范化您的数据。

    我知道这不是您一般问题的答案,但我认为这是解决您特定问题的最佳解决方案。

    编辑:

    我应该已经完整阅读了这个问题。该解决方案将防止一名志愿者在同一事件中进行两班倒,即使它们不重叠。也许将轮班开始和结束时间移至 EventVolunteer 并对该表的时间进行检查约束就足够了,尽管您在 Shift 表之外有轮班数据,这对我来说听起来并不直观。

    【讨论】:

      【解决方案4】:

      有一种方法可以通过使用触发器来实现,但我不建议这样做。我建议不要将您的业务逻辑放在数据库级别。 db 不需要知道谁在什么时候为某个班次配备人员。该逻辑应该放在您的业务层中。我建议使用存储库构建模式。 Scott Gutherie 在他的 mvc 1.0 书中有一个非常好的章节描述了这一点(链接如下)。

      http://weblogs.asp.net/scottgu/archive/2009/03/10/free-asp-net-mvc-ebook-tutorial.aspx

      【讨论】:

      • 嗯。我认为这是一个数据完整性问题,而不是业务逻辑。
      猜你喜欢
      • 2018-08-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-11-16
      • 1970-01-01
      • 2021-01-02
      • 2018-08-01
      • 2011-01-28
      相关资源
      最近更新 更多