【问题标题】:Date range overlapping check constraint日期范围重叠检查约束
【发布时间】:2012-08-20 10:06:49
【问题描述】:

我在 sql server 2005 中有一个包含 3 列的简单表:DateStart、DateEnd 和 Value。我尝试设置“表检查约束”以避免插入重叠记录。例如,如果在此类表中存在 DateStart = 2012-01-01(1 月 1 日)和 DateEnd 2012-01-15(1 月 15 日)的记录,则 Check 约束必须避免插入 DateStart=2012-01-10 的记录( no care DateEnd)、DateEnd=2012-01-10 (no care DateStart) 的记录或 DateStart 2011-12-10 和 DateEnd 2012-02-01 的记录。

我这样定义了一个UDF:

CREATE FUNCTION [dbo].[ufn_checkOverlappingDateRange]
(
    @DateStart AS DATETIME
    ,@DateEnd AS DATETIME
)
RETURNS BIT 
AS
BEGIN
  DECLARE @retval BIT
  /* date range at least one day */
  IF (DATEDIFF(day,@DateStart,@DateEnd) < 1)
    BEGIN
      SET @retval=0
    END
  ELSE
    BEGIN
      IF EXISTS
        (
          SELECT
              *
            FROM [dbo].[myTable]
            WHERE
            ((DateStart <= @DateStart) AND (DateEnd > @DateStart))
            OR
            ((@DateStart <= DateStart) AND (@DateEnd > DateStart))
        )
        BEGIN
          SET @retval=0
        END
    ELSE
      BEGIN
            SET @retval=1
          END
        END
  RETURN @retval
END

然后认为检查可能是这样的:

ALTER TABLE [dbo].[myTable]  WITH CHECK ADD  CONSTRAINT [CK_OverlappingDateRange] CHECK  ([dbo].[ufn_checkOverlappingDateRange]([DateStart],[DateEnd])<>(0))

但是当我插入第一条记录时,即使 [myTable] 为空 EXISTS 运算符也会返回 true。我在哪里?是否可以设置这样的约束?

顺便说一句,我认为 DateStart 包括在范围内,而 DateEnd 不包括在范围内。

【问题讨论】:

    标签: sql-server database sql-server-2005 database-design check-constraints


    【解决方案1】:

    在插入行之后执行 CHECK,因此范围与自身重叠。

    您需要修改您的 WHERE 以包含以下内容:@MyTableId &lt;&gt; MyTableId


    顺便说一句,您的 WHERE 表达式可以简化。

    范围在以下情况下重叠:

    • 一个范围的结束在另一个范围的开始之前
    • 一个范围的开始在另一个范围的结束之后。

    可以用 SQL 写成这样:

    WHERE @DateEnd < DateStart OR DateEnd < @DateStart
    

    取反以获得确实重叠的范围...

    WHERE NOT (@DateEnd < DateStart OR DateEnd < @DateStart)
    

    ...根据De Morgan's laws 与...相同...

    WHERE NOT (@DateEnd < DateStart) AND NOT (DateEnd < @DateStart)
    

    ...等同于:

    WHERE @DateEnd >= DateStart AND DateEnd >= @DateStart
    

    所以你的最终 WHERE 应该是:

    WHERE
        @MyTableId <> MyTableId
        AND @DateEnd >= DateStart
        AND DateEnd >= @DateStart
    

    [SQL Fiddle]

    注意:要允许“触摸”范围,请在起始表达式中使用 &lt;=,这将在最终表达式中生成 &gt;

    【讨论】:

    • 考虑避免间隔重叠 only 对于相同的 id(不是键,而是来自其他列的值)我的意思是,不是为了避免表级别的重叠,而只是为了某些条目属于同一个ID..有可能吗?
    • 这种方法在使用 READ COMMITTED SNAPSHOT 隔离时会不一致吗?由于每个事务在事务开始时都有自己的快照,这可能导致两个事务都看到它们以有效状态结束(即事务 B 不会看到事务 A 已经添加了会导致重叠的行)
    • @neo112 是的。并且非 SNAPSHOT 隔离也存在竞争条件。这可以通过锁定整个表来解决(这具有明显的可伸缩性后果),或者非常小心地锁定各个行(不是在原始表中,而是在预定义日期范围的特殊表中)。感谢您指出问题 - 我可能应该在我的回答中提到这些并发症......
    • @LeonardoLopez 据我所知,这不是记录在案的行为,因此您不应依赖它(当然,在可能的情况下) - 最好编写 CHECK 以不知道何时他们实际上是被评估的。话虽如此,这似乎是SQL Server中长期存在的行为,您可以直接在执行计划中看到它:Asset节点将始终位于Clustered Index InsertClustered Index Update等节点之上,这意味着稍后执行。
    • @LeonardoLopez 您也可以从调试器中看到它:在 CHECK 下的函数中中断,然后使用 SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED 从另一个会话并查询表 - 新数据将已经在那里。
    【解决方案2】:

    我只想在Branko Dimitrijevic 的答案中添加 DateEnd 为空的情况,因为我目前有这样的情况。

    当您保持打卡日志并且用户仍处于登录状态时,可能会发生这种情况。

    WHERE
        @MyTableId <> MyTableId
        AND @DateEnd >= DateStart
        AND DateEnd >= @DateStart
        OR @DateEnd >= DateStart
        AND DateEnd is null
        OR @DateStart >= DateStart
        AND DateEnd is null
    

    我不知道这个查询在性能方面有多好,我相信有办法优化它。

    【讨论】:

    • 如果没有任何括号,这将不会产生您可能认为的效果。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-02-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多