在 SQL Server 中按连续日期分组

Posted

技术标签:

【中文标题】在 SQL Server 中按连续日期分组【英文标题】:Grouping by consecutive dates in SQL Server 【发布时间】:2015-09-02 07:20:39 【问题描述】:

我有以下列的表格:

[name_of_pos] varchar,
[date_from] datetime,
[date_to] datetime

以下是我的示例数据:

name_of_pos  date_from                 date_to
----------------------------------------------------------------
Asystent     2015-08-26 08:57:49.000   2015-09-04 08:57:49.000
Biuro        2015-09-01 08:53:32.000   2015-09-01 08:53:32.000
Biuro        2015-09-02 09:00:41.000   2015-09-02 09:00:41.000
Biuro        2015-09-03 11:46:03.000   2015-09-03 11:46:03.000
Biuro        2015-09-10 09:02:11.000   2015-09-15 09:02:11.000
Koordynator  2015-09-01 09:04:06.000   2015-09-01 09:04:06.000
Projektant   2015-08-31 08:59:46.000   2015-09-01 08:59:46.000
Projektant   2015-09-02 08:00:54.000   2015-09-02 08:00:54.000
Projektant   2015-09-14 12:34:50.000   2015-09-14 12:34:50.000

我要返回的是每个name_of_pos 的日期范围(date_from 的最小值到 date_to 的最大值),但仅限于日期值连续的情况(时间部分不重要,可以忽略结果)。

期望的输出是:

  name_of_pos   date_from   date_to
  ------------------------------------
  Asystent      2015-08-26  2015-09-04 
  Biuro         2015-09-01  2015-09-03 
  Biuro         2015-09-10  2015-09-15 
  Koordynator   2015-09-01  2015-09-01 
  Projektant    2015-08-31  2015-09-02 
  Projektant    2015-09-14  2015-09-14 

我尝试了使用类似于此问题的解决方案:

How do I group on continuous ranges

但运气不好,因为我有两个日期时间列。

【问题讨论】:

您确定您提供的源数据正确吗?如果是这样,你能解释一下你是如何得到Biuro 2015-09-01 08:53:32.000 2015-09-03 11:46:03.000 间隔的吗?此记录的源数据中似乎没有连续的时间间隔。 日期部分必须是连续的,时间部分不重要(可以忽略)所以 biuro 的三个记录(其中 date_from 是 2015-09-01,2015-09-02 和 2015-09- 03) 应作为 name_of_pos='Biuro' 的一行返回,其值为 date_from='2015-09-01' 和 date_to='2015-09-03' 【参考方案1】:

这是一个使用cte 迭代行(在它们被排序之后)并在分组前检查连续天数的解决方案:

-- dummy table
CREATE TABLE #TableA
    (
      [name_of_pos] VARCHAR(11) ,
      [date_from] DATETIME ,
      [date_to] DATETIME
    );

-- insert dummy data
INSERT  INTO #TableA
        ( [name_of_pos], [date_from], [date_to] )
VALUES  ( 'Asystent', '2015-08-26 08:57:49', '2015-09-04 08:57:49' ),
        ( 'Biuro', '2015-09-01 08:53:32', '2015-09-01 08:53:32' ),
        ( 'Biuro', '2015-09-02 09:00:41', '2015-09-02 09:00:41' ),
        ( 'Biuro', '2015-09-03 11:46:03', '2015-09-03 11:46:03' ),
        ( 'Biuro', '2015-09-10 09:02:11', '2015-09-15 09:02:11' ),
        ( 'Koordynator', '2015-09-01 09:04:06', '2015-09-01 09:04:06' ),
        ( 'Projektant', '2015-08-31 08:59:46', '2015-09-01 08:59:46' ),
        ( 'Projektant', '2015-09-02 08:00:54', '2015-09-02 08:00:54' ),
        ( 'Projektant', '2015-09-14 12:34:50', '2015-09-14 12:34:50' );

-- new temp table used to add row numbers for data order
SELECT  name_of_pos, CAST(date_from AS DATE) date_from, CAST(date_to AS DATE) date_to,
        ROW_NUMBER() OVER ( ORDER BY name_of_pos, date_from ) rn
INTO    #temp
FROM    #TableA

-- GroupingColumn in cte used to identify and group consecutive dates
;WITH    cte
          AS ( SELECT   name_of_pos ,
                        date_from ,
                        date_to ,
                        1 AS GroupingColumn ,
                        rn
               FROM     #temp
               WHERE    rn = 1
               UNION ALL
               SELECT   t2.name_of_pos ,
                        t2.date_from ,
                        t2.date_to ,
                        CASE WHEN t2.date_from = DATEADD(day, 1, cte.date_to) 
                                  AND cte.name_of_pos = t2.name_of_pos
                             THEN cte.GroupingColumn
                             ELSE cte.GroupingColumn + 1
                        END AS GroupingColumn ,
                        t2.rn
               FROM     #temp t2
                        INNER JOIN cte ON t2.rn = cte.rn + 1
             )
    SELECT  name_of_pos, MIN(date_from) AS date_from, MAX(date_to) AS date_to
    FROM    cte
    GROUP BY name_of_pos, GroupingColumn

DROP TABLE #temp
DROP TABLE #TableA

产生你想要的输出:

name_of_pos date_from   date_to
Asystent    2015-08-26  2015-09-04
Biuro       2015-09-01  2015-09-03
Biuro       2015-09-10  2015-09-15
Koordynator 2015-09-01  2015-09-01
Projektant  2015-08-31  2015-09-02
Projektant  2015-09-14  2015-09-14

【讨论】:

效果很好。谢谢!我还有一个问题 - 是否可以使用此查询创建视图? @Bart 是的,你应该可以做到like this 我收到一个递归错误(来自 CTE 不断加入自身),因为我有一个更大的数据集。你有什么建议吗?【参考方案2】:

您可以为此使用cte,但根据我的经验,最快的方法是循环使用更新:

declare @temp table
(
  name_of_pos varchar(128),
  date_from datetime,
  date_to datetime
)

insert into @temp (
    name_of_pos, date_from, date_to
) 
values
    ('Asystent', '2015-08-26 08:57:49', '2015-09-04 08:57:49'),
    ('Biuro', '2015-09-01 08:53:32', '2015-09-01 08:53:32'),
    ('Biuro', '2015-09-02 09:00:41', '2015-09-02 09:00:41'),
    ('Biuro', '2015-09-03 11:46:03', '2015-09-03 11:46:03'),
    ('Biuro', '2015-09-10 09:02:11', '2015-09-15 09:02:11'),
    ('Koordynator', '2015-09-01 09:04:06', '2015-09-01 09:04:06'),
    ('Projektant', '2015-08-31 08:59:46', '2015-09-01 08:59:46'),
    ('Projektant', '2015-09-02 08:00:54', '2015-09-02 08:00:54'),
    ('Projektant', '2015-09-14 12:34:50', '2015-09-14 12:34:50')

----------------------------------------------------------------------------------------------------
declare @temp_new table (
  name_of_pos varchar(128),
  date_from date,
  date_to date
)

insert into @temp_new (
    name_of_pos, date_from, date_to
)
select
    name_of_pos, date_from, date_to
from @temp

while @@rowcount > 0
begin
    update t1 set
        date_to = t2.date_to
    from @temp_new as t1
        inner join @temp_new as t2 on
            t2.name_of_pos = t1.name_of_pos and
            dateadd(dd, 1, t1.date_to) = t2.date_from
end

select name_of_pos, min(date_from), date_to
from @temp_new
group by name_of_pos, date_to
order by name_of_pos, date_to

【讨论】:

【参考方案3】:

这是一个差距和孤岛问题。这是调整的official 方法来完成它,这将被检查为解决方案:

;with 

cte as (
    SELECT *,
          dateadd( day,
           - (ROW_NUMBER() OVER (
               partition by name_of_pos
               ORDER BY t.date_from
              ) +                           -- here starts tuned part --
              isnull( 
               sum( datediff(day, date_from, date_to ) ) OVER (
                partition by name_of_pos
                ORDER BY t.date_from
                ROWS BETWEEN UNBOUNDED PRECEDING and 1 PRECEDING
              ) ,0)                         -- here ends tuned part --
           ),
           date_from
          ) as Grp
    FROM t
)  

SELECT name_of_pos
    ,min(date_from) AS date_from
    ,max(date_to) AS date_to
FROM cte
GROUP BY name_of_pos, Grp
ORDER BY name_of_pos, date_from

这里是tested on sqlfiddle(有一些不同的样本数据)。

【讨论】:

(请注意,对于您的结构,您必须将日期时间转换为日期) 唯一的缺点是它不能应用于重叠的时期,但在连续时期的情况下,这应该会很好。我会检查大量行,然后会回来 您可能还想提一下,这仅适用于 SQL Server 2012+【参考方案4】:

试试这个:

SELECT name_of_pos, date_from,date_to
FROM table
ORDER BY
name_of_pos asc, date_from desc;

【讨论】:

它返回 Biuro 的四条记录,而不是两条 - 一条用于 2015-09-01 至 2015-09-03,第二条用于 2015-09-10 至 2015-09-15 是的,以上内容对您没有帮助。考虑另一个查询

以上是关于在 SQL Server 中按连续日期分组的主要内容,如果未能解决你的问题,请参考以下文章

在 SQL/Impala 中按特定的分钟数对日期时间进行分组

SQL查询中按日期、天间隔分组

在 Django 中按日期分组

如何使用 SQL Server 在此查询中按天对结果进行分组?

如何在oracle sql developer中按年和月分组获取当前财政年度的数据?

如何在 Jooq 中按月和年将毫秒翻译成日期和分组?