日期范围重叠检查约束

Ant*_*llo 21 database sql-server database-design sql-server-2005 check-constraints

我在sql server 2005中有一个简单的表,有3列:DateStart,DateEnd和Value.我试图设置"表检查约束"以避免插入重叠记录.例如,如果在此表中有DateStart = 2012-01-01(第一月1月)和DateEnd 2012-01-15(1月15日)的记录,则Check约束必须避免插入DateStart = 2012-01-10的记录(没有关心DateEnd),DateEnd = 2012-01-10(无关日期启动)的记录或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
Run Code Online (Sandbox Code Playgroud)

然后考虑检查可能是这样的:

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

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

顺便说一下,我认为DateStart包含在范围内,DateEnd从范围中排除.

Bra*_*vic 29

插入行正在执行CHECK ,因此范围与自身重叠.

您需要修改您的WHERE以包含以下内容:@MyTableId <> MyTableId.


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

在下列情况下,范围重叠:

  • 一个范围的结束是在另一个范围的开始之前
  • 或者一个范围的开始是在另一个范围结束之后.

哪个可以用SQL编写:

WHERE @DateEnd < DateStart OR DateEnd < @DateStart
Run Code Online (Sandbox Code Playgroud)

否定,为了得到该范围重叠...

WHERE NOT (@DateEnd < DateStart OR DateEnd < @DateStart)
Run Code Online (Sandbox Code Playgroud)

...根据De Morgan的法律,它与...相同

WHERE (NOT (@DateEnd < DateStart) AND NOT (DateEnd < @DateStart))
Run Code Online (Sandbox Code Playgroud)

......这与:

WHERE @DateEnd >= DateStart AND DateEnd >= @DateStart
Run Code Online (Sandbox Code Playgroud)

所以你最后的WHERE应该是:

WHERE
    @MyTableId <> MyTableId
    AND @DateEnd >= DateStart
    AND DateEnd >= @DateStart
Run Code Online (Sandbox Code Playgroud)

[SQL小提琴]

注意:要允许范围"触摸",请<=在起始表达式中使用,这将在最终表达式中生成">".

  • 这种方法在使用 READ COMMITTED SNAPSHOT 隔离时会不一致吗?由于每个事务在事务开始时都会获得自己的快照,这可能导致两个事务都看到它们以有效状态结束(即事务 B 不会看到事务 A 已经添加了会导致重叠的行) (2认同)
  • @neo112 是的。非快照隔离也存在竞争条件。这可以通过锁定整个表(这具有明显的可扩展性后果)或*非常*小心地锁定各个行(不是在原始表中,而是在预定义日期范围的特殊表中)来解决。感谢您指出问题 - 我可能应该在我的回答中提到这些复杂性...... (2认同)