按名称分组的连续日期范围内的最小和最大日期

Posted

技术标签:

【中文标题】按名称分组的连续日期范围内的最小和最大日期【英文标题】:Minimum and maximum dates within continuous date range grouped by name 【发布时间】:2020-05-06 22:27:08 【问题描述】:

我有一个人的开始和结束日期的数据范围,我想只获得每个人的连续日期范围:

输入:

NAME | STARTDATE      | END DATE
--------------------------------------
MIKE | **2019-05-15** | 2019-05-16 
MIKE | 2019-05-17     | **2019-05-18**
MIKE | 2020-05-18     | 2020-05-19

预期输出如下:

MIKE | **2019-05-15** | **2019-05-18** 
MIKE | 2020-05-18     | 2020-05-19

所以基本上输出是人的每个连续时期的 MIN 和 MAX。

感谢任何帮助。

我尝试了以下查询:

With N AS (   SELECT Name, StartDate, EndDate
       , LastStop = MAX(EndDate) 
                    OVER (PARTITION BY Name ORDER BY StartDate, EndDate 
                          ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING)   FROM   Table  ), B AS (   SELECT Name, StartDate, EndDate
       , Block = SUM(CASE WHEN LastStop Is Null Then 1
                          WHEN LastStop < StartDate Then 1
                          ELSE 0
                    END)
                 OVER (PARTITION BY Name ORDER BY StartDate, LastStop)   FROM   N ) SELECT Name
     , MIN(StartDate) DateFrom
     , MAX(EndDate) DateTo FROM   B GROUP BY Name, Block ORDER BY Name, Block

但它没有考虑连续周期。它显示相同的输入。

【问题讨论】:

你试过什么?你在哪里卡住了? 试过这样的事情:***.com/questions/49025748/… 将您尝试过的内容添加到您的问题中。 但不考虑连续周期..它只会显示最小值和最大值.. 【参考方案1】:

这是一种孤岛问题。无需按天扩展数据!这似乎效率很低。

相反,确定“岛屿”。这是没有重叠的地方——在你的情况下lag() 就足够了。然后是累积和聚合:

select name, min(startdate), max(enddate)
from (select t.*,
             sum(case when prev_enddate >= dateadd(day, -1, startdate) then 0 else 1 end) over 
                 (partition by name order by startdate) as grp
      from (select t.*,
                   lag(enddate) over (partition by name order by startdate) as prev_enddate
            from t
           ) t
     ) t
group by name, grp;

Here 是一个 dbfiddle。

【讨论】:

非常感谢..它工作完美,是的。实际上在我的情况下,我正在处理具有数千条记录的大数据..所以我也在寻找一种有效的方法...... 实际上,如果我像这种情况下切换数据的顺序,脚本将不起作用:dbfiddle.uk/… @Basharal 。 . .你可能想问一个新问题。但是当我阅读小提琴时,应该合并这些行,因为它们重叠。如果还有其他原因,请将其包含在新问题中。 @Basharal 。 . .你有理由不接受这个答案吗?它不仅可以工作,而且不使用任何“特殊”外部表,因此无论组的大小如何,它都可以工作。而且它不会乘以行来定义组。 @Grodon ...我同意你的看法..但是因为在我的情况下数据没有排序..例如我之前发送的小提琴...所以其他答案对我有用数据在数据库中排序..它应该返回预期的结果...所以这就是为什么..感谢您的帮助。【参考方案2】:

这是一个使用临时计数表的示例

示例或dbFiddle

;with cte as (
Select A.[Name]
      ,B.D
      ,Grp  = datediff(day,'1900-01-01',D) - dense_rank() over (partition by [Name] Order by D)
 From  YourTable A
 Cross Apply ( 
                Select Top (DateDiff(DAY,StartDate,EndDate)+1) D=DateAdd(DAY,-1+Row_Number() Over (Order By (Select Null)),StartDate) 
                 From  master..spt_values n1,master..spt_values n2 
             ) B

)
Select [Name]
      ,StartDate= min(D)
      ,EndDate  = max(D)
 From  cte
 Group By [Name],Grp

退货

Name    StartDate   EndDate
MIKE    2019-05-15  2019-05-18
MIKE    2020-05-18  2020-05-19

为了帮助可视化,CTE 生成以下内容

【讨论】:

我尝试像这样更改最后一个数据行:,('MIKE','2019-05-18','2019-05-19')...如果结果应该是:迈克 2019-05-15 2019-05-19 @Basharal 我在您的 dbFiddle 中看到,您有重叠的记录/日期。将 row_number 更改为 dense_rank @Basharal 更新了 dbFiddle dbfiddle.uk/… @Basharal 用 dense_rank() 更新了我的答案 @Basharal 实际上任何适当大小的表都可以。我使用这些来生成临时数字/计数表。如果您有数字表甚至日历表,也可以使用它们。【参考方案3】:

这会给你同样的结果

    SELECT subquery.name,min(subquery.startdate),max(subquery.enddate1)
FROM (SELECT NAME,startdate,
      CASE WHEN EXISTS(SELECT yt1.startdate 
                       FROM t yt1 
                       WHERE yt1.startdate = DATEADD(day, 1, yt2.enddate) 
                       ) THEN null else yt2.enddate END as enddate1
      FROM t yt2) as subquery
GROUP by NAME, CAST(MONTH(subquery.startdate) AS VARCHAR(2)) + '-' + CAST(YEAR(subquery.startdate) AS VARCHAR(4))

对于CASE WHEN EXISTS,我参考了SQL CASE

按月和年分组,您可以看到这个GROUP BY MONTH AND YEAR

DB_FIDDLE

【讨论】:

@Basharal 仅供参考,我建议不要将此网站上的人称为“亲爱的”——很多人会觉得这很奇怪。

以上是关于按名称分组的连续日期范围内的最小和最大日期的主要内容,如果未能解决你的问题,请参考以下文章

大日期范围内的最小/最大日期值取决于值

用于删除重复(连续)记录的 SQL,但将最小日期存储在开始日期和最大日期作为结束日期

Oracle sql查询按日期对连续记录进行分组

Oracle根据连续性日期的重复数据取最大或最小值日期

SQLite 按选定日期范围内的所有日期分组,即使数据不存在

选择日期范围,根据关闭条件计算范围内的多条记录,按班次分组