【问题标题】:Microsoft SQL Server 2016, T-SQL : obtain date range for a dataset based on individual datesMicrosoft SQL Server 2016,T-SQL:根据各个日期获取数据集的日期范围
【发布时间】: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

【问题讨论】:

    标签: sql-server date tsql sql-server-2016 gaps-and-islands


    【解决方案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
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多