Microsoft SQL Server 2016,T-SQL:根据各个日期获取数据集的日期范围

Posted

技术标签:

【中文标题】Microsoft SQL Server 2016,T-SQL:根据各个日期获取数据集的日期范围【英文标题】:Microsoft SQL Server 2016, T-SQL : obtain date range for a dataset based on individual dates 【发布时间】:2021-12-23 09:15:20 【问题描述】:

我在 SQL Server 2016 中有一个有趣的情况。我正在使用 T-SQL 语言。

我有一个名为 (#dataset) 的数据集:

名为 ContinuousDates 的最后一列将始终具有连续的日期值,例如 2021 年 1 月 1 日至 2021 年 12 月 31 日。同一 ID 或名称不会有重复的日期,即一个人在某一天可以只有一行数据。 (在这个例子中,我只展示了一个人,ID = 1 和 Name = X。在我的实际数据中,我有多个人)。

请注意,NYC 城市出现在数据集中的较早位置,并且在最后 4 行中重复出现。

我需要根据日期范围获取以下数据集:

我尝试在数据集上使用简单的 MINIMUM 和 MAXIMUM,但我意识到有时我会得到错误输出,如下所示:

我尝试了一些使用 RANK() 和 DENSE_RANK() 函数的选项,但无法找到解决方案。有人可以为我提供帮助吗?

我这里附有代码:

CREATE TABLE #dataset

(

ID int,
Name varchar(20),
City varchar(20),
ContinuousDates date

)


INSERT INTO #dataset
VALUES(1,'X','NYC','1/1/2021')

INSERT INTO #dataset
VALUES(1,'X','NYC','1/2/2021')

INSERT INTO #dataset
VALUES(1,'X','NYC','1/3/2021')

INSERT INTO #dataset
VALUES(1,'X','SFO','1/4/2021')

INSERT INTO #dataset
VALUES(1,'X','SFO','1/5/2021')

INSERT INTO #dataset
VALUES(1,'X','PHY','1/6/2021')

INSERT INTO #dataset
VALUES(1,'X','PHY','1/7/2021')

INSERT INTO #dataset
VALUES(1,'X','PHY','1/8/2021')

INSERT INTO #dataset
VALUES(1,'X','NYC','1/9/2021')

INSERT INTO #dataset
VALUES(1,'X','NYC','1/10/2021')

INSERT INTO #dataset
VALUES(1,'X','NYC','1/11/2021')

INSERT INTO #dataset
VALUES(1,'X','NYC','1/12/2021')




SELECT *
FROM #dataset
ORDER BY ContinuousDates

我有一组新代码,为了更好的演示:

CREATE TABLE #dataset

(

ID int,
Name varchar(20),
City varchar(20),
ContinuousDates date

)


INSERT INTO #dataset
VALUES(1,'X','NYC','1/1/2021')

INSERT INTO #dataset
VALUES(1,'X','NYC','1/2/2021')

INSERT INTO #dataset
VALUES(1,'X','NYC','1/3/2021')

INSERT INTO #dataset
VALUES(1,'X','SFO','1/4/2021')

INSERT INTO #dataset
VALUES(1,'X','SFO','1/5/2021')

INSERT INTO #dataset
VALUES(1,'X','PHY','1/6/2021')

INSERT INTO #dataset
VALUES(1,'X','PHY','1/7/2021')

INSERT INTO #dataset
VALUES(1,'X','PHY','1/8/2021')

INSERT INTO #dataset
VALUES(1,'X','NYC','1/9/2021')

INSERT INTO #dataset
VALUES(1,'X','NYC','1/10/2021')

INSERT INTO #dataset
VALUES(1,'X','NYC','1/11/2021')

INSERT INTO #dataset
VALUES(1,'X','NYC','1/12/2021')

INSERT INTO #dataset
VALUES(2,'Y','MEL','1/13/2021')

INSERT INTO #dataset
VALUES(3,'Z','SYD','1/14/2021')

INSERT INTO #dataset
VALUES(3,'Z','SYD','1/15/2021')

INSERT INTO #dataset
VALUES(3,'Z','PER','1/16/2021')

INSERT INTO #dataset
VALUES(4,'A',NULL,'1/16/2021')

INSERT INTO #dataset
VALUES(4,'A', NULL,'1/17/2021')



SELECT *
FROM #dataset
ORDER BY ID, ContinuousDates

【问题讨论】:

【参考方案1】:

解决步骤:

编号部分的 ID 和名称按日期 (row_id) 排序 ID、姓名和城市按日期排序的编号部分 (p_row_id) 计算row_id - p_row_id

现在,您可以在一组唯一值中为每个时期获得组号。

您只需按此号码、ID、姓名和城市进行分组

ID Name City ContinuousDates p_row_id row_id row_id - p_row_id
1 X NYC 2021-01-01 1 1 0
1 X NYC 2021-01-02 2 2 0
1 X NYC 2021-01-03 3 3 0
1 X SFO 2021-01-04 1 4 3
1 X SFO 2021-01-05 2 5 3
1 X PHY 2021-01-06 1 6 5
1 X PHY 2021-01-07 2 7 5
1 X PHY 2021-01-08 3 8 5
1 X NYC 2021-01-09 4 9 5
1 X NYC 2021-01-10 5 10 5
1 X NYC 2021-01-11 6 11 5
1 X NYC 2021-01-12 7 12 5
select
     CD.ID
    ,CD.[Name]
    ,CD.City
    ,min(CD.ContinuousDates) as DateStart
    ,max(CD.ContinuousDates) as DateEnd
from
    (
        select *
            ,row_number() over(partition by CD.ID, CD.[Name], CD.City order by CD.ContinuousDates) as p_row_id
            ,row_number() over(partition by CD.ID, CD.[Name] order by CD.ContinuousDates) as row_id
        from #dataset CD
    ) CD
group by CD.row_id - CD.p_row_id
        ,CD.ID
        ,CD.[Name]
        ,CD.City
order by DateStart

多列模板:

select
     CD.GroupColumn1
    ,CD.GroupColumn2
    ..
    ,CD.Column1
    ,CD.Column2
    ,CD.Column3
    ,CD.Column4
    ..
    ,min(CD.ContinuousDates) as DateStart
    ,max(CD.ContinuousDates) as DateEnd
from
    (
        select *
            ,row_number() over(partition by
                 CD.GroupColumn1
                ,CD.GroupColumn2
                ..
                ,CD.Column1
                ,CD.Column2
                ,CD.Column3
                ,CD.Column4
                ..
                order by CD.ContinuousDates) as p_row_id
            ,row_number() over(partition by
                 CD.GroupColumn1
                ,CD.GroupColumn2
                ..
                order by CD.ContinuousDates) as row_id
        from #dataset CD
    ) CD
group by CD.row_id - CD.p_row_id
        ,CD.GroupColumn1
        ,CD.GroupColumn2
        ..
         CD.Column1
        ,CD.Column2
        ,CD.Column3
        ,CD.Column4
        ..
order by DateStart

【讨论】:

当我对许多员工使用代码时遇到问题,比如 X,Y,Z,...... 差异值 [CD.row_id - CD.p_row_id] 没有正确出现.我应该在 row_id 列中为 Partition 添加任何额外的列吗? (如果我一次尝试一名员工,您的上述解决方案效果很好) 对于多名员工(ID = 1,2,3,4..),这样对吗? row_number() over(按 CD.ID 顺序按 CD.ContinuousDates 分区)作为 row_id 我刚刚添加了可以用作多列模板的查询。任何额外的列都应该添加到“select”、“group by”和“partition by” 我在这部分添加了多列;假设我有一个员工 X、员工 Y、员工 Z,每个人都有一个唯一的 ID,上面代码中需要的小改动是 row_id 列:row_number() over(partition by CD.ID order by CD.ContinuousDates) as row_id。也就是说,名为 row_id 的列必须有一个按 ID 划分的分区; (目前它只有 CD.ContinuousDates 的 ORDER BY 子句)。我刚刚检查了这个 我明白了。那是我的错。您已经提到,对于相同的 ID 或名称,日期永远不会重复。我不知道我是怎么错过的。如果不是一对一的关系,您还需要将 Name 包含到 row_id 分区中。【参考方案2】:

这是一种孤岛问题

有许多不同的解决方案。这是一个简单的

使用LAG 标识每个岛的起始行 运行条件计数为我们提供了每个岛的 ID 然后只需按该 ID 分组(连同任何其他分区列)
WITH StartPoints AS (
    SELECT *,
      IsStart = CASE WHEN LAG(City, 1, '') OVER (PARTITION BY ID ORDER BY ContinuousDates)
                         <> City THEN 1 END
    FROM #dataset ds
),
Groups AS (
    SELECT *,
      GroupId = COUNT(IsStart) OVER (PARTITION BY ID ORDER BY ContinuousDates ROWS UNBOUNDED PRECEDING)
    FROM StartPoints
)
SELECT
  ID,
  Name,
  City = MIN(City),
  DateStart = MIN(ContinuousDates),
  DateEnd = MAX(ContinuousDates)
FROM Groups
GROUP BY
  ID,
  Name,
  GroupId;

db<>fiddle

【讨论】:

非常感谢您的回复;如果 'City' 类型的列超过 1 列怎么办?在我的原始数据中,我有 City、State、Manager、Department 等大量列。而且我有很多人,而不仅仅是一个叫 X 的人。我有 X,Y,Z,... 另外,有没有办法,我可以只使用 ad-hoc 查询来开发这个解决方案,而不使用 CTE? CTE 是临时查询,不确定您的意思。如果让您感觉更好,您可以将它们移动到派生表(子查询)中,但是您将拥有双重嵌套的表。如果您想按所有这些列进行分区,只需将它们全部添加到 PARTITION BYGROUP BY 子句中。所以你会做PARTITION BY ID, Name, City, State, Manager, Department ORDER BY ... 我是否还需要使用 LAG 测试其他列,例如 Country、Manager、Department,如下所示? LAG(City, 1, '') OVER (PARTITION BY ID ORDER BY ContinuousDates) City 如果我有三列:City、Department、Manager,您能否只修改 StartPoints CTE 中的 IsStart 列?如果你只是提供一个大纲,我会吸收它。 我想把那些AND改成OR

以上是关于Microsoft SQL Server 2016,T-SQL:根据各个日期获取数据集的日期范围的主要内容,如果未能解决你的问题,请参考以下文章

Microsoft SQL Server 2016 安装错误 - “等待数据库引擎恢复句柄失败”

Microsoft SQL Server 2016,T-SQL:根据各个日期获取数据集的日期范围

Microsoft: Get started with Dynamic Data Masking in SQL Server 2016 and Azure SQL

数据库管理:Microsoft SQL Server 2016

Ubuntu 下安装 SQL Server 2016初探

Microsoft SQL Server 2016 官方简体中文64位企业版下载(含激活序列号密钥)