【问题标题】:How can I find missing date range in sql server 2008?如何在 sql server 2008 中找到缺失的日期范围?
【发布时间】:2014-06-26 19:21:31
【问题描述】:

我的数据如下所示。

如何从 ss 表中找到缺失的日期范围。

我想查找 se_startdate 和 se_enddate 之间缺失的(ss 日期范围)日期范围。

例如上面的例子。

缺少的日期范围是

2014-07-01 to 2014-07-06
2014-07-18 to 2014-07-30.

【问题讨论】:

  • 我尝试过使用视图和使用 while 循环,但无法获得完整的逻辑。
  • SS_StartDate 是否可以早于 se_startdate 或 SS_EndDate 是否晚于 se_enddate?换句话说 - 间隔 (se_startdate, se_enddate) 是否总是包含间隔 (SS_StartDate, SS_EndDate) ?
  • 是的 se_startdate 和 se_enddate 始终包括 ss_startdate 和 ss_enddate。不能在 se_startdate 和 se_enddate 之外

标签: c# sql sql-server sql-server-2008


【解决方案1】:

可能有一种更简单的方法来执行此操作,但通常在尝试查找丢失的数字/日期时,您需要创建这些数字/日期,然后 LEFT JOIN 到现有数据以查找丢失的内容。您可以使用递归 cte 创建相关日期:

WITH cal AS (SELECT CAST('2014-07-01' AS DATE) dt
              UNION  ALL
              SELECT DATEADD(DAY,1,dt)
              FROM cal
              WHERE dt < '2014-07-30')
SELECT *
FROM cal

然后,您 LEFT JOIN 到您的餐桌上获取缺失日期列表:

WITH cal AS (SELECT CAST('2014-07-01' AS DATE) dt
              UNION  ALL
              SELECT DATEADD(DAY,1,dt)
              FROM cal
              WHERE dt < '2014-07-30')
SELECT DISTINCT cal.dt 
FROM  cal
LEFT JOIN YourTable a
   ON cal.dt BETWEEN CAST(SS_StartDate AS DATE) AND CAST(SS_EndDate AS DATE)
WHERE a.SS_StartDate IS NULL

然后你需要找出连续的行是否属于同一范围,或者它们之间是否有间隙,使用DATEDIFF()ROW_NUMBER()

WITH cal AS (SELECT CAST('2014-07-01' AS DATE) dt
              UNION  ALL
              SELECT DATEADD(DAY,1,dt)
              FROM cal
              WHERE dt < '2014-07-30')
    ,dt_list AS (SELECT DISTINCT cal.dt 
                  FROM  cal
                  LEFT JOIN YourTable a
                    ON cal.dt BETWEEN CAST(SS_StartDate AS DATE) AND CAST(SS_EndDate AS DATE)
                  WHERE a.SS_StartDate IS NULL)        
SELECT dt
      ,DATEDIFF(D, ROW_NUMBER() OVER(ORDER BY dt), dt) AS dt_range
FROM dt_list

然后使用MIN()MAX() 获取范围:

WITH cal AS (SELECT CAST('2014-07-01' AS DATE) dt
              UNION  ALL
              SELECT DATEADD(DAY,1,dt)
              FROM cal
              WHERE dt < '2014-07-30')
    ,dt_list AS (SELECT DISTINCT cal.dt 
                  FROM  cal
                  LEFT JOIN YourTable a
                    ON cal.dt BETWEEN CAST(SS_StartDate AS DATE) AND CAST(SS_EndDate AS DATE)
                  WHERE a.SS_StartDate IS NULL)        
    ,dt_range AS (SELECT dt
                         ,DATEDIFF(D, ROW_NUMBER() OVER(ORDER BY dt), dt) AS dt_range
                  FROM dt_list)
SELECT  MIN(dt) AS BeginRange
       ,MAX(dt) AS EndRange
FROM dt_range
GROUP BY dt_range;
--OPTION (MAXRECURSION 0)

演示:SQL Fiddle

注意:如果您检查的范围超过 100 天,您需要指定 MAXRECURSION,0 表示没有限制。

注意2:如果您的SE 日期旨在驱动完整的日期范围,则将cal cte 从固定日期更改为分别使用MIN()MAX() 的查询。

【讨论】:

  • 对于 generate a sequential series 使用递归 cte 可能是最糟糕的方法(除了显式循环)。同样在 SQL Server NOT EXISTS will out perform LEFT JOIN/IS NULL 中。但是,加 1 仍然符合逻辑。
  • @GarethD 好笔记,我在大多数情况下使用NOT EXISTS,但在向他人解释如何做事时却不那么频繁。序列系列链接中的stacked cte方法非常聪明,我通常使用FROM master..spt_values v1,master..spt_values v2 ...,但我想我会切换。
【解决方案2】:

缺少的范围必须从 se_StartDate 或 ss_EndDate+1 开始。同样,它必须在 se_EndDate 或 ss_StartDate-1 结束。置换候选范围并丢弃重叠。

这种方法的优点是时间精度可以很容易地调整到小时、分钟或秒,而不需要枚举每个时钟滴答声。

SQL Fiddle Demo

SELECT DISTINCT
  range_start, range_end, se_StartDate, se_EndDate
FROM MyTable t1
CROSS APPLY (
  SELECT se_StartDate range_start
  UNION ALL
  SELECT DATEADD(day,1,SS_EndDate)
) rs
CROSS APPLY (
  SELECT se_EndDate range_end
  UNION ALL
  SELECT DATEADD(day,-1,SS_StartDate)
  FROM MyTable
  WHERE
    se_StartDate = t1.se_StartDate AND
    se_EndDate   = t1.se_EndDate AND
    SS_StartDate > range_start
) re
WHERE NOT EXISTS (
  SELECT 1
  FROM MyTable
  WHERE
    range_start < SS_EndDate AND
    range_end > SS_StartDate
)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-10-05
    • 1970-01-01
    • 1970-01-01
    • 2019-09-21
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多