日期范围重叠检查约束

时间:2012-08-20 10:06:49

标签: sql-server database sql-server-2005 database-design 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

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

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

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

BTW我认为DateStart包含在范围内,而DateEnd包括在范围内。

2 个答案:

答案 0 :(得分:29)

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

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


BTW,您的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]

注意:要允许范围“触摸”,请在起始表达式中使用<=,这会产生“&gt;”在最后的表达中。

答案 1 :(得分:-1)

我想在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

我不知道这个查询在性能方面有多好,我确信有很多方法可以优化它。