【问题标题】:Combine SQL records based on dates根据日期组合 SQL 记录
【发布时间】:2015-01-16 03:00:06
【问题描述】:

我有下表:

startDate   endDate
----------------------
01-01-2014  01-07-2014
01-08-2014  01-14-2014
01-15-2014  01-21-2014

01-28-2014  02-03-2014
02-04-2014  02-10-2014

我想将日期捆绑在一起以尽量减少记录数量。输出结果应如下所示:

startDate   endDate
----------------------
01-01-2014  01-21-2014
01-28-2014  02-10-2014

如果一个记录的 startDate 比另一个记录的 endDate 晚一天,则日期可以链接在一起。

不使用游标可以实现吗?

【问题讨论】:

  • 当日期范围连续时,开始日期是否总是比另一条记录的结束日期晚一天?或者日期范围可以重叠,您也想捆绑这些吗?
  • 我相信这可以通过窗口函数 + 递归 CTE 实现,但我对这些功能的练习还不够,无法让它发挥作用。
  • 是的,我相信使用窗口函数 + CTE 是可能的。也可以使用 while 循环,尽管这可以被认为类似于游标。请记住光标并不总是坏的。只有当存在“更好”的基于集合的解决方案时,它们才会变得糟糕
  • 天数不重叠,但可以存在重复记录。
  • 经过一些性能测试后,使用游标的替代方案似乎不像以前那么糟糕了。

标签: sql sql-server


【解决方案1】:

如 cmets 中所述,您需要 Recursive CTE 加上 Window Function 以聚合连续天数

;WITH cte
     AS (SELECT StartDate,
                EndDate
         FROM   yourtable
         UNION ALL
         SELECT a.StartDate,
                b.EndDate
         FROM   cte a
                JOIN yourtable b
                  ON Dateadd(DAY, 1, a.EndDate) = b.StartDate),
     cte1
     AS (SELECT StartDate,
                EndDate,
                Row_number()
                  OVER(
                    partition BY EndDate
                    ORDER BY StartDate ASC) AS rn
         FROM   cte)
SELECT StartDate,
       Max(EndDate) AS EndDate
FROM   cte1 a
WHERE  a.rn = 1
GROUP  BY StartDate
ORDER  BY EndDate 

SQLFIDDLE DEMO

【讨论】:

  • 这很好用,谢谢!性能很慢,但使用递归 JOIN 是可以预期的。
  • @Kirill - 是的,当数据量很大时,递归 cte 很慢
【解决方案2】:

注意:以下代码与 SQL Server 2012 兼容

使用下面的代码:

SELECT MIN(startdate), MAX(enddate) 
FROM
(
    SELECT *, SUM(diff) OVER(ORDER BY startdate) AS cat FROM
    (
        SELECT 
            startdate, 
            endDate, 
            CASE WHEN DATEDIFF("dd", LAG(enddate) OVER (ORDER BY startdate), startdate) > 1 THEN 1 ELSE 0 END AS diff 
        FROM <Your Table>
    ) t
) t
GROUP BY cat

发生了什么?

上面的代码使用窗口函数来检测上一个结束日期并检查它与当前开始日期之间的差异,如果有差异则放置 1,否则放置 0。结果表应如下所示:

startdate   enddate     diff
2014-01-01  2014-01-07  0
2014-01-08  2014-01-14  0
2014-01-15  2014-01-21  0
2014-01-28  2014-02-03  1
2014-02-04  2014-02-10  0
2014-02-11  2014-03-04  0
2014-03-14  2014-03-21  1
2014-04-01  2014-05-10  1

使用SUM函数作为窗口函数从当前行到第一行求和,结果为:

startdate   endDate   diff  cat
2014-01-01  2014-01-07  0   0
2014-01-08  2014-01-14  0   0
2014-01-15  2014-01-21  0   0
2014-01-28  2014-02-03  1   1
2014-02-04  2014-02-10  0   1
2014-02-11  2014-03-04  0   1
2014-03-14  2014-03-21  1   2
2014-04-01  2014-05-10  1   3

然后,您可以通过为每个 cat 获取 MIN(startdate)MAX(enddate) 来轻松聚合它们。

【讨论】:

  • 这是一个非常好的解决方案,快速且可靠。唯一的问题是 LAG 函数仅在 SQL 2012 和更新版本中支持,
【解决方案3】:

此解决方案假定连续的日期范围可能重叠

declare @t table (startDate date, endDate date)

insert into @t
values 
    ('01-01-2014', '01-07-2014'),
    ('01-08-2014', '01-14-2014'),
    ('01-15-2014', '01-21-2014'),
    ('01-28-2014', '02-03-2014'),
    ('02-04-2014', '02-10-2014')

;with cte 
as (select startdate, enddate from @t
    union all
    select cte.startdate, t.enddate
    from cte 
        inner join @t t
            on t.startdate between dateadd(dd,1,cte.startDate) and dateadd(dd,1,cte.enddate)
            and t.endDate > cte.endDate
    )
select min(startdate) startdate, enddate
from
    (select startDate, max(enddate) enddate 
    from cte 
    group by startdate) a
group by enddate

【讨论】:

    【解决方案4】:

    你也可以试试下面的方法

     ;WITH cte AS
     (
      SELECT ROW_NUMBER() OVER(ORDER BY t1.startDate) AS Id, t1.StartDate
      FROM dbo.test104 t1 LEFT JOIN dbo.test104 t2 ON DATEADD(DAY, -1, t1.startDate) = t2.endDate
      WHERE t2.endDate IS NULL
      ), cte2 AS
      ( 
       SELECT ROW_NUMBER() OVER(ORDER BY t1.EndDate) AS Id, t1.EndDate
       FROM dbo.test104 t1 LEFT JOIN dbo.test104 t2 ON DATEADD(DAY, 1, t1.EndDate) = t2.StartDate
       WHERE t2.startDate IS NULL
       )
       SELECT *
       FROM cte c1 JOIN cte2 c2 ON c1.Id = c2.Id
    

    SQLFiddle

    【讨论】:

      【解决方案5】:

      您可以在没有递归 CTE 的情况下执行此操作。您只需要识别开始每个序列的记录,然后使用聚合。

      with cte as (
            select t.*, (case when tprev.startdate is null then 1 else 0 end) as IsSeqStart
            from table t left join
                 table tprev
                 on t.startdate = dateadd(day, 1, tprev.enddate)
           )
      select min(startdate) as startdate, max(enddate) as enddate
      from (select cte.*, sum(isSeqStart) over (order by startdate) as grp
            from cte
           ) t
      group by grp
      

      【讨论】:

      • 对不起,这个不行,生成的表是这样的:startDate endDate' ------------------------`@987654323 @01-01-2014 02-10-2014
      • @Kirill 。 . .那应该是结束日期的最大值。我测试了代码并且它可以工作,但是——不幸的是——SQL Fiddle 这些天不支持 SQL Server 2012。
      猜你喜欢
      • 2013-06-23
      • 2015-12-06
      • 2021-01-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多