【问题标题】:SQL Server contraints for date ranges日期范围的 SQL Server 约束
【发布时间】:2017-03-02 18:04:04
【问题描述】:

我试图通过开始日期和结束日期来限制 SQL Server 数据库,这样我就不能重复预订资源(即没有重叠或重复的预订)。

假设我的资源被编号使得表格看起来像 ResourceId、StartDate、EndDate、状态

假设我有资源 #1。我想确保我不能为同一资源预订 2017 年 1 月 8 日至 2017 年 1 月 16 日和 2017 年 1 月 10 日至 2017 年 1 月 18 日的单独预订。

还有一些复杂情况,资源的 StartDate 可以与资源的 EndDate 相同。所以 1/8/1027 到 1/16/2017 和 1/16/2017 到 1/20/2017 是可以的(即一个人可以在同一天签入另一个人结帐)。

此外,“状态”字段指示资源的预订是“活动”还是“已取消”。所以我们可以忽略所有取消的预订。

在保存时,我们在代码(存储过程和 C#)中防止了这些重叠或重复预订,但我们希望通过添加数据库约束来增加额外的保护层。

这在 SQL Server 中可行吗?

提前致谢

【问题讨论】:

标签: sql-server date range constraints


【解决方案1】:

您可以使用 CHECK 约束来确保 startdate 在 EndDate 上或之前很容易:

CONSTRAINT [CK_Tablename_ValidDates] CHECK ([EndDate] >= [StartDate])

约束无助于防止日期范围重叠。您可以改为使用 TRIGGER 通过创建 FOR INSERT、UPDATE 触发器来强制执行此操作,该触发器在检测到重复时回滚事务:

CREATE TRIGGER [TR_Tablename_NoOverlappingDates] FOR INSERT, UPDATE AS
IF EXISTS(SELECT * from inserted INNER JOIN [MyTable] ON blah blah blah ...) BEGIN 
    ROLLBACK TRANSACTION;
    RAISERROR('hey, no overlapping date ranges here, buddy', 16, 1);
    RETURN;
END

另一种选择是创建一个索引视图来查找重复项,并对该视图设置一个唯一约束,如果存在超过 1 条记录,则会违反该约束。这通常是通过将 2 行笛卡尔连接到选择重复 id 的聚合视图的虚拟表来完成的——因此,具有重复的一条记录将在视图中返回具有相同假 id 值且具有唯一索引的两行。

我都做过,我更喜欢触发方法。

【讨论】:

    【解决方案2】:

    从这里的答案中汲取灵感:Date range overlapping check constraint

    首先,检查以确保不存在重叠:

    select *
    from dbo.Reservation as r
    where exists (
      select 1
      from dbo.Reservation i
      where i.PersonId = r.PersonId
        and i.ReservationId != r.ReservationId
        and isnull(i.EndDate,'20990101') > r.StartDate
        and isnull(r.EndDate,'20990101') > i.StartDate
      );
    go
    

    如果一切都清楚,然后创建你的函数。

    有几种不同的方法来编写函数,例如我们可以跳过StartDateEndDate 并使用仅基于ReservationId 的内容,如上面的查询,但我将以此为例:

    create function dbo.udf_chk_Overlapping_StartDate_EndDate (
        @ResourceId int
      , @StartDate date
      , @EndDate date
    ) returns bit as
    begin;
      declare @r bit = 1;
      if not exists (
        select 1
        from dbo.Reservation as r
        where r.ResourceId = @ResourceId
          and isnull(@EndDate ,'20991231') > r.StartDate
          and isnull(r.EndDate,'20991231') >  @StartDate
          and r.[Status] = 'Active'
        group by r.ResourceId
        having count(*)>1
      )
      set @r = 0;
      return @r;
    end;
    go
    

    然后添加你的约束:

    alter table dbo.Reservation 
      add constraint chk_Overlapping_StartDate_EndDate 
        check (dbo.udf_chk_Overlapping_StartDate_EndDate(ResourceId,StartDate,EndDate)=0);
    go
    

    最后:测试一下。

    【讨论】:

    • 如果我通过 INSERT INTO Reservation - SELECT * FROM 插入多个项目,这将不起作用
    • @StefanSteiger 它是如何失败的?它在这里工作:dbfiddle example
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-12-31
    • 1970-01-01
    • 2013-12-18
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多