【问题标题】:Is there an easy way to group interpolated start/finish dates in SQL?有没有一种简单的方法可以在 SQL 中对插值的开始/结束日期进行分组?
【发布时间】:2017-01-18 16:04:49
【问题描述】:

所以基本上,假设我们有一个具有开始和结束日期的项目列表。像这样的:

| Name   | Start     | Finish   |
---------------------------------
| Item 1 | Jan 1     | Jan 4    |
| Item 2 | Jan 3     | Jan 5    |
| Item 3 | Jan 4     | Jan 7    |

| Item 4 | Jan 10    | Jan 14   |

| Item 5 | Jan 15    | Jan 17   |
| Item 6 | Jan 17    | Jan 20   |

| Item 7 | Jan 25    | Jan 27   |
| Item 8 | Jan 26    | Jan 26   |
| Item 9 | Jan 27    | Jan 30   |

如果项目的开始日期介于组其他成员的最低开始日期和最高完成日期之间,我基本上需要获取组中项目的分组日期,如上所示。 1 月 1 日至 7 日、1 月 10 日至 14 日、1 月 15 日至 20 日和 1 月 25 日至 30 日。在 SQL 中是否有比简单地强制执行此操作更简单的方法?

谢谢!

【问题讨论】:

  • 有没有一种简单的方法来分组...你尝试过哪些方法?显示代码!
  • 暴力破解。只需遍历行并逐个添加它们
  • LargeCrimsonFish 的问题并不完全清楚,但数据似乎可以拯救你。您似乎想要将在无活动期间处于活动状态(在开始和结束之间)的所有项目组合在一起。对吗?
  • 对,完全正确。我一直在运行嵌套的while循环,但这似乎效率不高,所以我想知道是否有更有效的方法来做到这一点。感谢您的回复!

标签: sql


【解决方案1】:

您所追求的通常称为 会话化,例如,在点击流分析学科中:我们将同一用户在同一网站上的点击分组在一起,点击之间的不活动时间少于 30 分钟, 分析此类会话中的行为。

让我给你一个快速的答案 - 但它只适用于 Vertica,使用 CONDITIONAL_TRUE_EVENT 分析函数:

https://my.vertica.com/docs/8.0.x/HTML/index.htm#Authoring/SQLReferenceManual/Functions/TimeSeries/CONDITIONAL_TRUE_EVENTAnalytic.htm

WITH
-- input data
foo(name,start,finish) AS (
          SELECT 'Item 1',DATE '2017-01-01',DATE '2017-01-04'
UNION ALL SELECT 'Item 2',DATE '2017-01-03',DATE '2017-01-05'
UNION ALL SELECT 'Item 3',DATE '2017-01-04',DATE '2017-01-07'

UNION ALL SELECT 'Item 4',DATE '2017-01-10',DATE '2017-01-14'

UNION ALL SELECT 'Item 5',DATE '2017-01-15',DATE '2017-01-17'
UNION ALL SELECT 'Item 6',DATE '2017-01-17',DATE '2017-01-20'

UNION ALL SELECT 'Item 7',DATE '2017-01-25',DATE '2017-01-27'
UNION ALL SELECT 'Item 8',DATE '2017-01-26',DATE '2017-01-26'
UNION ALL SELECT 'Item 9',DATE '2017-01-27',DATE '2017-01-30'
)

SELECT
  CONDITIONAL_TRUE_EVENT(start::TIMESTAMP > LAG(finish::TIMESTAMP))
OVER(PARTITION BY 1 ORDER BY start) AS grp_id
, *
FROM foo;

结果是递增 1,从 0 开始,每次 PARTITION BY 值更改(此处为常量)时,每次括号之间的表达式计算为 TRUE 时,都重置为 0:

grp_id|name  |start     |finish
     0|Item 1|2017-01-01|2017-01-04
     0|Item 2|2017-01-03|2017-01-05
     0|Item 3|2017-01-04|2017-01-07
     1|Item 4|2017-01-10|2017-01-14
     2|Item 5|2017-01-15|2017-01-17
     2|Item 6|2017-01-17|2017-01-20
     3|Item 7|2017-01-25|2017-01-27
     3|Item 8|2017-01-26|2017-01-26
     4|Item 9|2017-01-27|2017-01-30

现在。您的数据库平台是否支持通用分析函数,或者不支持,例如 MySQL?根据您的回答,我将根据 OLAP 函数或相关子选择来重写它。

但无论哪种方式,这都需要更多时间......

玩得开心-

理智的马可

【讨论】:

  • 这正是我要找的!非常感谢!不幸的是,我使用的是标准的 MS 2008 SQL Sever Management Studio,所以我不确定您的示例是否还有效,但知道该技术被称为(会话化)可以大大简化它。非常感谢您的帮助!
【解决方案2】:

显然,CONDITIONAL_TRUE_EVENT 是最优雅的解决方案,但由于 SQL 本身不支持此功能,因此可以这样完成(首选 CTE 而不是子查询)。

;WITH DatesWithLag AS (
    SELECT 
      StartDate 
    , StopDate 
    , LAG(StopDate) OVER (ORDER BY StartDate) AS PrevStop 
    , DATEDIFF( 
         day 
      , LAG(StopDate) OVER (ORDER BY StartDate) 
      , StartDate 
      ) AS DayDiff
    FROM Dates
), 
Cond1 AS (
    SELECT 
      StartDate 
    , StopDate 
    , DayDiff 
    , (CASE WHEN DayDiff > 0 THEN 1 END) Change
    FROM DatesWithLag
),
Cond2 AS (
    SELECT 
      StartDate 
    , StopDate 
    , COUNT(Change) OVER ( 
        ORDER BY StartDate  
        ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW 
      ) Grp     --  
    FROM Cond1
)
SELECT Cond1.* 
     , Cond2.Grp 
FROM Cond1
    JOIN Cond2 ON Cond2.StartDate = Cond1.StartDate
ORDER BY Cond1.StartDate
GO

另一种选择是将CONDITIONAL_TRUE_EVENT 实现为CLR function.

【讨论】:

  • 该死的,我真希望我能将两个答案标记为已接受 :(。感谢您的帮助,今天真的教会了我很多东西。在阅读此答案之前,我最终做了一些 while 循环。
  • 我的赞美,阿列克谢。我承诺的基于 OLAP 的解决方案还将包含两个额外的 Global Table 表达式,它们的内容非常相似——我会将您的 Cond1 中的所有列放入 Cond2,无论如何只能从 Cond1 中选择,以避免加入。但是,总的来说,你帮我省了一些功课......
猜你喜欢
  • 2017-02-17
  • 2015-08-27
  • 1970-01-01
  • 2022-01-23
  • 2013-03-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多