【问题标题】:SQL Group Rows Based on Order and Key基于顺序和键的 SQL 组行
【发布时间】:2020-07-09 12:10:23
【问题描述】:

我有一张表,其中包含一组临时工,他们的合同可以续签,并且他们在重叠期间继续工作,或者该工人可能会在稍后的某个时间回来并再次为不同部门或同一个部门工作。我的目标是以这样一种方式合并数据行,即如果工作人员的时间重叠,那么实质上重叠的时间段可以转换为一行。如果没有,则应将它们表示为单独的行。

当前输出:

+---------------------+------------------+------------------+------------------+
|         Name        |    Department    |    Start Date    |     End Date     |
+---------------------+------------------+------------------+------------------+
|         Tom         |     Finance      |    2010-08-09    |   2010-09-09     |
|         Tom         |     Finance      |    2010-09-10    |   2010-10-10     |
|         Tom         |        HR        |    2010-11-01    |   2011-01-15     |
|         Tom         |     Finance      |    2011-02-01    |   2011-03-01     |
+---------------------+------------------+------------------+------------------+

期望的输出

+---------------------+------------------+------------------+------------------+
|         Name        |    Department    |    Start Date    |     End Date     |
+---------------------+------------------+------------------+------------------+
|         Tom         |     Finance      |    2010-08-09    |   2010-10-10     |
|         Tom         |        HR        |    2010-11-01    |   2011-01-15     |
|         Tom         |     Finance      |    2011-02-01    |   2011-03-01     |
+---------------------+------------------+------------------+------------------+

我尝试按姓名和部门分组,然后输出 MIN 和 MAX 日期。但是,这不适用于上述示例。

我也尝试对订单进行排名,但是当进行值不匹配时,此功能不会重置排名编号。

【问题讨论】:

  • 用您正在使用的数据库标记您的问题。

标签: sql group-by grouping


【解决方案1】:

这是一个间隙和孤岛问题的示例。假设表格中的时间范围没有间隙,最简单的方法可能是行号的差异:

select name, department, min(start_date), max(end_date)
from (select t.*,
             row_number() over (partition by name order by start_date) as seqnum,
             row_number() over (partition by name, department order by start_date) as seqnum_nd
      from t
     ) t
group by name, department, (seqnum - sequm_nd);

这是如何工作的有点难以解释。但是,如果您查看子查询的结果,您可能会看到行号的差异如何识别具有相同部门的相邻记录。

如果您有差距——并且你想考虑到这一点——那么你可以使用更精确的版本。在此方法中,使用lag() 获取上一个结束日期,并使用该日期创建一个标志来识别“岛屿”何时开始。剩下的只是聚合:

select name, department, min(start_date), max(end_date)
from (select t.*,
             sum(case when prev_end_date >= start_date - interval '1 day' then 1 else 0 end) over (partition by name order by start_date) as grp
      from (select t.*,
                   lag(end_date) over (partition by name, department order by start_date) as prev_end_date
            from t
           ) t
     ) t
group by name, department, grp;

请注意,这使用了众所周知的依赖于数据库的日期运算符。确切的语法取决于您使用的数据库,因此代码可能需要调整。

【讨论】:

  • 感谢您的建议!但是,我拥有的数据会有差距,并且预计会有差距,因为它们并不总是重叠。如果有小的差距,这个解决方案会起作用吗?
  • @user2582770 。 . .第一个解决方案将相邻行与同一部门合并,即使存在间隙。第二个在间隙处分裂。
  • 太棒了,这就像一种享受!我有一个 while 循环,我讨厌它。
  • 所以我在使用它时遇到的一个问题是,当需要对超过一百万条记录进行排序时,ROW_NUMBER 真的很慢。有什么提高性能的潜在方法吗?
  • @user2582770 。 . .此答案中没有row_number(),因此您的问题不清楚。如果您有性能问题,我建议您提出一个新问题,提供数据库标签和查询的解释计划。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-01-19
  • 1970-01-01
  • 2021-12-17
  • 1970-01-01
  • 1970-01-01
  • 2022-01-22
相关资源
最近更新 更多