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 BY
和 GROUP 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