【问题标题】:Getting first and last values from contiguous ranges从连续范围中获取第一个和最后一个值
【发布时间】:2017-06-15 23:20:45
【问题描述】:

我想为每个id 获取连续的enter_dayleave_day 值的第一个enter_date 和最后一个leave_date。鉴于此示例数据:

+-----+------------+------------+-----------+-----------+
| id  | enter_date | leave_date | enter_day | leave_day |
+-----+------------+------------+-----------+-----------+
| 111 | 2016-07-29 | 2016-12-01 |         1 |        75 |
| 111 | 2016-12-02 | 2017-01-13 |        76 |        95 |
| 111 | 2017-01-17 | 2017-06-02 |        96 |       181 |
| 222 | 2016-07-29 | 2016-12-02 |         1 |        76 |
| 222 | 2017-01-30 | 2017-06-02 |       105 |       181 |
| 333 | 2016-08-01 | 2017-06-02 |         1 |       180 |
+-----+------------+------------+-----------+-----------+

我想要以下结果:

+-----+------------+------------+
| id  | enter_date | leave_date |
+-----+------------+------------+
| 111 | 2016-07-29 | 2017-06-02 |
| 222 | 2016-07-29 | 2016-12-02 |
| 222 | 2017-01-30 | 2017-06-02 |
| 333 | 2016-08-01 | 2017-06-02 |
+-----+------------+------------+

我想要一条 ID 为 111 的记录,因为任何 enter_day 和之前的 leave_day 之间没有间隔。

我想要 ID 222 的两条记录,因为存在间隔(第 75 天到第 104 天)。

编辑:到目前为止,我没有为 ID 111 提供正确的leave_date

with cte as (
    select a.id, a.enter_date, a.leave_date, b.enter_date next_ed, b.leave_date next_ld
    from #tbl a
    join #tbl b on b.id = a.id and b.enter_day = a.leave_day + 1
)
select id, min(enter_date) enter_date, max(leave_date) leave_date
from cte
group by id
union
select a.id, a.enter_date, a.leave_date
from #tbl a
left join #tbl b on b.id = a.id and b.enter_day = a.leave_day + 1
left join cte c on c.id = a.id and c.next_ed = a.enter_date and c.next_ld = a.leave_date
where b.id is null and c.id is null
order by 1,3

【问题讨论】:

  • 到目前为止您尝试过什么?这不是代码编写服务。
  • 搜索间隙和孤岛。这是此类查询的经典示例。
  • 我整个上午都在查看 gaps-and-islands 示例,我发现的所有示例都是针对单个列中的间隙或日期中的间隙(我不想要的) .

标签: sql sql-server sql-server-2008 gaps-and-islands


【解决方案1】:

以下是范围的间隙和岛屿示例。

我使用了一个临时计数表,但实际的数字/计数也可以解决问题。

如果您只运行内部查询,您将很快看到 6 行的样本数据如何爆炸成 514 行。然后是应用分组聚合得到最终结果的小问题。

示例

Declare @YourTable Table ([id] int,[enter_date] date,[leave_date] date,[enter_day] int,[leave_day] int)
Insert Into @YourTable Values
 (111,'2016-07-29','2016-12-01',1,75)
,(111,'2016-12-02','2017-01-13',76,95)
,(111,'2017-01-17','2017-06-02',96,181)
,(222,'2016-07-29','2016-12-02',1,76)
,(222,'2017-01-30','2017-06-02',105,181)
,(333,'2016-08-01','2017-06-02',1,180)

Select ID
      ,[enter_date] = min([enter_date])
      ,[leave_date] = max([leave_date])
 From (
        Select *
              ,Grp = N - Row_Number() over (Partition By ID Order by N)
         From @YourTable A
         Join (
                  Select Top (Select max([leave_day]-[enter_day])+1 From @YourTable)
                         N=-1+Row_Number() Over (Order By (Select Null))
                  From  master..spt_values n1,master..spt_values n2
              ) B on B.N between [enter_day] and [leave_day]
      ) A 
 Group By [ID],Grp
 Order By [ID],min([enter_date])

返回

ID  enter_date  leave_date
111 2016-07-29  2017-06-02
222 2016-07-29  2016-12-02
222 2017-01-30  2017-06-02
333 2016-08-01  2017-06-02

【讨论】:

  • 谢谢,约翰。这确实有效,但我的表有大约 36,000 行,您的解决方案在我取消查询之前运行了一分钟。我应该注意,我的“表格”实际上是另一个 CTE 的结果。我也尝试将这些结果放在临时表中,但解决方案仍然很慢。有什么建议吗?
  • @DakotaPaul 获取您的样本并将范围扩大到 30,000,并在我的笔记本电脑上在 12 秒内返回结果。不精彩,但也不可怕。一个调整是让连接更有效率,而不是 Select Top 1000 .... 使它成为 Select Top (Select max([leave_day]-[enter_day])+1 From @YourTable) ...
猜你喜欢
  • 1970-01-01
  • 2019-03-09
  • 1970-01-01
  • 1970-01-01
  • 2016-12-12
  • 1970-01-01
  • 1970-01-01
  • 2022-12-06
  • 2021-10-23
相关资源
最近更新 更多