【问题标题】:Aggregating groups of date ranges聚合日期范围组
【发布时间】:2014-06-12 09:47:57
【问题描述】:

假设一个简单的查询如:

从 SampleTable 中选择名称、角色、placeOfWork、startDate、endDate

显示员工的姓名,他们在工作场所从开始日期到结束日期所担任的角色。当工作分配为当前时,结束日期为空。

我有一个这样的查询的结果集,我会在其中返回这样的样本:

Jack      Cook       Jimmy's Burger Joint    01-01-2010     21-01-2010
Jack      Cook       Jimmy's Burger Joint    21-01-2010     31-03-2010
Jack      Cook       Jimmy's Burger Joint    31-03-2010     24-12-2010
Ronald    Marketing  McDonald's              01-01-2010     22-01-2010
Ronald    Marketing  McDonald's              22-01-2010     06-06-2010
Ronald    Marketing  McDonald's              06-06-2010     NULL
Jack      Cosmonaut  NASA                    01-01-2011     NULL
...

我想将工作分配汇总为“单个概念性的”,例如:

Jack      Cook       Jimmy's Burger Joint    01-01-2010     24-12-2010
Ronald    Marketing  McDonald's              01-01-2010     NULL
Jack      Cosmonaut  NASA                    01-01-2011     NULL
...

我想尽可能避免使用临时表,因为我需要从不同的地方运行查询。我无法使用内部联接或 group by。

【问题讨论】:

  • 您使用的是哪个 DBMS?
  • Sybase 但我希望这可以是 ANSI sql
  • 实际的解决方案会因 DBMS 而异,但我解决这些问题的一般方法是将您的范围扩展为行,即 01-01-2010 --> 05-01-2010 扩展为 5 行。然后你有一个集合,你可以应用gaps and islands 逻辑。可以通过加入数字表来扩展日期。如果下一个开始日期总是等于上一个结束日期,那么您可以使用可能更快的递归来解决这个问题。
  • 观察:Ronald 的记录有大约 4 个月的差距。在这种情况下,您显示的输出是否与您的单条记录概念一致?
  • 确实是例子的问题,我更新了

标签: sql


【解决方案1】:

我会用简单的逻辑来解决这个问题。当与之前的分配没有重叠时,分配开始。在这种情况下,我们可以为每个分配分配一个值,即过去分配的数量。这是最简单的lag() 和累积总和。这是一个没有这些的版本:

with stp as (
      select name, role, placeOfWork, startDate, endDate,
             (case when exists (select 1
                                from SampleTable st2
                                where st2.name = st.name and st2.role = st.role and
                                      st2.placeOfWork = st.placeOfWork and
                                      st2.endDate = st.StartDate
                               )
                   then 0
                   else 1
              end) as PeriodStart
      from SampleTable st 
     ),
     stpg as (
      select stp.*,
             (select sum(PeriodStart)
              from stp stp2
              where stp2.name = stp.name and stp2.role = stp.role and
                    stp2.placeOfWork = stp.placeOfWOrk and
                    stp2.StartDate <= stp.StartDate
             ) as grp
      from stp
select name, role, placeOfWork, min(StartDate) as StartDate, max(endDate) as endDate
from stpg
group by grp, name, role, placeOfWork;

【讨论】:

  • 我怀疑 st2 别名会在哪里声明,但不是
  • 不管怎样,将其转换为在 Sybase 上工作(关键字不存在),目前结果非常令人满意
【解决方案2】:

我的方法是首先将您的范围扩展为行(使用数字或日历表),例如此行:

 StartDate  |   Enddate 
------------+------------
 2010-01-01 | 2010-03-01

变成

    Date    
------------
 2010-01-01
 2010-01-02
 2010-01-03

由于很多日期函数是特定于 DBMS 的,我使用的是 SQL-Server 特定的语法,但这应该很容易适应 sybase(我根本不熟悉),这将扩展一个简单的表范围内所有日期的开始和结束日期:

SELECT  DATEADD(DAY, n.Number, t.StartDate) AS Date
FROM    T
        INNER JOIN Numbers n
            ON  DATEADD(DAY, n.Number, t.StartDate) <= t.EndDate

现在你有一个可以使用Gaps and Islands Logic 解决的集合。扩大范围后,您需要确定间隙和孤岛,为此我使用 sybase 和 SQL Server 都支持的 DENSE_RANK。这给出了下面的列GroupingSet。最后一步是根据您的岛屿进行聚合:

WITH Expanded AS
(   SELECT  Name,
            Job,
            Company,
            StartDate,
            DATEADD(DAY, n.Number, t.StartDate) AS Date,
            CASE WHEN EndDate IS NULL THEN 1 ELSE 0 END AS EndDateIsNull
    FROM    T
            INNER JOIN Numbers n
                ON  DATEADD(DAY, n.Number, t.StartDate) <= ISNULL(t.EndDate, t.StartDate)
), Grouped AS
(   SELECT  Name,
            Job,
            Company,
            Date,
            DATEADD(DAY, -DENSE_RANK() OVER(PARTITION BY Name, Job, Company ORDER BY Date), Date) AS GroupingSet,
            EndDateIsNull
    FROM    Expanded
)
SELECT  Name, 
        Job, 
        Company,
        MIN(Date) AS StartDate, 
        CASE WHEN MAX(EndDateIsNull) = 0 THEN MAX(Date) END AS EndDate
FROM    Grouped
GROUP BY Name, Job, Company, GroupingSet
ORDER BY Name, Job, StartDate;

Example on SQL Fiddle

【讨论】:

  • +1 表示努力、差距和孤岛逻辑:) 我稍微更改了源数据:sqlfiddle.com/#!3/d0527/1。仍然无法处理“在其他工作中工作”的情况
  • 抱歉,我最初发布了错误的 SQL Fiddle 链接,然后编辑了答案,因此您发布的链接中的查询实际上与我的答案中的查询不匹配。 Is this what you would expect?
  • 这个解决方案很好,但是,我正在处理大约 300K 的任务,时间跨度约为 30 年。作为用户构建临时表的用户,存在溢出该数据库可用的内存的风险。 +1 不过,会很想将这种方法用于较小的数据集
【解决方案3】:

这个怎么样:

SELECT name, role, placeOfWork,
       MIN (startDate),
       CASE WHEN COUNT(endDate) = COUNT(startDate) THEN MAX(endDate) ELSE NULL END
FROM SampleTable
GROUP BY name, role, placeOfWork;

【讨论】:

  • 如果上述作业中存在任何空白,则它们不会显示为单独的额外作业;如果山姆在 2010 年曾在 NASA 工作,然后在 2011 年在汉堡王工作,然后在 2012 年再次在 NASA 工作,这将表明他从 2010 年到 2012 年在 NASA 工作,在 2011 年做了两份工作。
猜你喜欢
  • 1970-01-01
  • 2018-09-02
  • 1970-01-01
  • 1970-01-01
  • 2014-12-16
  • 2014-01-29
  • 2020-12-13
  • 1970-01-01
  • 2023-03-19
相关资源
最近更新 更多