Sql 用于汇总具有版本历史记录的表中的行的查询
我正在寻找有关SQL查询的帮助。详情如下 数据库:Microsoft SQL Server 2016 数据表:Sql 用于汇总具有版本历史记录的表中的行的查询,sql,sql-server,tsql,Sql,Sql Server,Tsql,我正在寻找有关SQL查询的帮助。详情如下 数据库:Microsoft SQL Server 2016 数据表: 这是一个“版本历史”表,有3列:版本号、生效日期和结束日期 结束日期为1999年12月31日的版本号被视为“活动”版本号 用户可以“恢复”以前的版本并使其再次处于活动状态 版本号 效率 结束时 0 2021-04-13 18:03:26.483 2021-04-16 18:35:06.367 1. 2021-04-16 18:35:06.370 2021-04-19 20:45:3
- 这是一个“版本历史”表,有3列:版本号、生效日期和结束日期
- 结束日期为1999年12月31日的版本号被视为“活动”版本号
- 用户可以“恢复”以前的版本并使其再次处于活动状态
您可以使用连续的公共表表达式(cte)通过两个步骤实现这一点。首先,你需要一个连续的排名数字在你的数据。在此基础上,您可以执行递归cte,查看连续行的版本号(必须相隔一行)。这允许我们创建一个“批次”编号:如果版本号相同,则我们取上一个批次号,如果版本号不同,则将上一个批次号增加1。最后,我们需要一个简单的最小和最大日期分组的批号。结果如下所示:
declare @t1 TABLE (
[version_number] [int] NULL,
[eff_dt] [datetime] NOT NULL,
[end_dt] [datetime] NOT NULL
);
INSERT @t1 ([version_number], [eff_dt], [end_dt])
VALUES
(1, CAST(N'2021-05-20T17:14:55.870' AS DateTime), CAST(N'9999-12-31T00:00:00.000' AS DateTime)),
(5, CAST(N'2021-05-14T15:52:50.847' AS DateTime), CAST(N'2021-05-20T17:14:55.860' AS DateTime)),
(4, CAST(N'2021-05-20T17:14:55.863' AS DateTime), CAST(N'2021-05-20T17:14:55.867' AS DateTime)),
(4, CAST(N'2021-05-11T15:36:25.283' AS DateTime), CAST(N'2021-05-14T15:52:50.843' AS DateTime)),
(4, CAST(N'2021-05-06T16:52:45.253' AS DateTime), CAST(N'2021-05-11T15:36:25.283' AS DateTime)),
(4, CAST(N'2021-05-06T16:17:23.130' AS DateTime), CAST(N'2021-05-06T16:52:45.250' AS DateTime)),
(3, CAST(N'2021-05-06T16:13:04.000' AS DateTime), CAST(N'2021-05-06T16:17:23.127' AS DateTime)),
(2, CAST(N'2021-05-06T16:00:59.990' AS DateTime), CAST(N'2021-05-06T16:13:03.997' AS DateTime)),
(1, CAST(N'2021-04-19T20:45:38.997' AS DateTime), CAST(N'2021-05-06T16:00:59.990' AS DateTime)),
(1, CAST(N'2021-04-16T18:35:06.370' AS DateTime), CAST(N'2021-04-19T20:45:38.993' AS DateTime)),
(0, CAST(N'2021-04-13T18:03:26.483' AS DateTime), CAST(N'2021-04-16T18:35:06.367' AS DateTime));
with rowdata as
(
SELECT version_number, eff_dt, end_dt,
ROW_NUMBER() OVER(ORDER BY eff_dt) rn
FROM @t1
),
cte_recursive as
(
SELECT 1 as batchno, rn, version_number, eff_dt, end_dt
FROM rowdata
WHERE version_number = 0
UNION ALL
SELECT CASE WHEN rec.version_number = rd.version_number
THEN rec.batchno
ELSE rec.batchno + 1
END,
rd.rn, rd.version_number, rd.eff_dt, rd.end_dt
FROM cte_recursive rec
INNER JOIN rowdata rd on rec.rn = rd.rn - 1
)
SELECT
version_number, min(eff_dt) as eff_dt, max(end_dt) as end_dt
FROM cte_recursive
GROUP BY version_number, batchno
有几点需要注意。我更喜欢将表变量用于临时表(有一个小小的优点,它们不需要删除!)。其次,您可以插入多个由逗号分隔的值,如我所示(无需多次插入)
为了帮助您理解递归元素是如何工作的,我们从一个简单的选择开始,这是基本情况,在本例中选择version_number为0的位置。然后,我们通过连接到递归部分来构建它,其中rn(ROW_NUMBER()返回的值)比我们已有的值大一。我们只需要检查旧值和新行之间版本号的差异,以确定批次号是否需要增加
您可能会发现,一次运行一个查询有助于了解发生了什么(例如,只需运行包含行号()的子选择)
顺便说一句,您添加create语句真是太好了。我不知道为什么人们投了反对票。作为第一个问题,你比很多人都付出了更多的努力。我想原因是您自己没有尝试回答,但我同情您可能不知道从哪里开始。您可以通过两个步骤来实现这一点,使用连续的公共表表达式(cte)。首先,你需要一个连续的排名数字在你的数据。在此基础上,您可以执行递归cte,查看连续行的版本号(必须相隔一行)。这允许我们创建一个“批次”编号:如果版本号相同,则我们取上一个批次号,如果版本号不同,则将上一个批次号增加1。最后,我们需要一个简单的最小和最大日期分组的批号。结果如下所示:
declare @t1 TABLE (
[version_number] [int] NULL,
[eff_dt] [datetime] NOT NULL,
[end_dt] [datetime] NOT NULL
);
INSERT @t1 ([version_number], [eff_dt], [end_dt])
VALUES
(1, CAST(N'2021-05-20T17:14:55.870' AS DateTime), CAST(N'9999-12-31T00:00:00.000' AS DateTime)),
(5, CAST(N'2021-05-14T15:52:50.847' AS DateTime), CAST(N'2021-05-20T17:14:55.860' AS DateTime)),
(4, CAST(N'2021-05-20T17:14:55.863' AS DateTime), CAST(N'2021-05-20T17:14:55.867' AS DateTime)),
(4, CAST(N'2021-05-11T15:36:25.283' AS DateTime), CAST(N'2021-05-14T15:52:50.843' AS DateTime)),
(4, CAST(N'2021-05-06T16:52:45.253' AS DateTime), CAST(N'2021-05-11T15:36:25.283' AS DateTime)),
(4, CAST(N'2021-05-06T16:17:23.130' AS DateTime), CAST(N'2021-05-06T16:52:45.250' AS DateTime)),
(3, CAST(N'2021-05-06T16:13:04.000' AS DateTime), CAST(N'2021-05-06T16:17:23.127' AS DateTime)),
(2, CAST(N'2021-05-06T16:00:59.990' AS DateTime), CAST(N'2021-05-06T16:13:03.997' AS DateTime)),
(1, CAST(N'2021-04-19T20:45:38.997' AS DateTime), CAST(N'2021-05-06T16:00:59.990' AS DateTime)),
(1, CAST(N'2021-04-16T18:35:06.370' AS DateTime), CAST(N'2021-04-19T20:45:38.993' AS DateTime)),
(0, CAST(N'2021-04-13T18:03:26.483' AS DateTime), CAST(N'2021-04-16T18:35:06.367' AS DateTime));
with rowdata as
(
SELECT version_number, eff_dt, end_dt,
ROW_NUMBER() OVER(ORDER BY eff_dt) rn
FROM @t1
),
cte_recursive as
(
SELECT 1 as batchno, rn, version_number, eff_dt, end_dt
FROM rowdata
WHERE version_number = 0
UNION ALL
SELECT CASE WHEN rec.version_number = rd.version_number
THEN rec.batchno
ELSE rec.batchno + 1
END,
rd.rn, rd.version_number, rd.eff_dt, rd.end_dt
FROM cte_recursive rec
INNER JOIN rowdata rd on rec.rn = rd.rn - 1
)
SELECT
version_number, min(eff_dt) as eff_dt, max(end_dt) as end_dt
FROM cte_recursive
GROUP BY version_number, batchno
有几点需要注意。我更喜欢将表变量用于临时表(有一个小小的优点,它们不需要删除!)。其次,您可以插入多个由逗号分隔的值,如我所示(无需多次插入)
为了帮助您理解递归元素是如何工作的,我们从一个简单的选择开始,这是基本情况,在本例中选择version_number为0的位置。然后,我们通过连接到递归部分来构建它,其中rn(ROW_NUMBER()返回的值)比我们已有的值大一。我们只需要检查旧值和新行之间版本号的差异,以确定批次号是否需要增加
您可能会发现,一次运行一个查询有助于了解发生了什么(例如,只需运行包含行号()的子选择)
顺便说一句,您添加create语句真是太好了。我不知道为什么人们投了反对票。作为第一个问题,你比很多人都付出了更多的努力。我想原因是您自己没有尝试回答,但我很同情您可能不知道从哪里开始。使用
窗口函数可以轻松完成此操作。这是缺口和孤岛问题的一个变体
前提是识别版本号的连续值孤岛。第一个CTE使用lag
将当前行值与前一行值进行比较,并在值不同时标记下一组的开始。第二个CTE使用sum
作为窗口函数
生成组的运行总数。这为每组相似的版本号提供了自己的顺序值
然后,最终选择可以使用每个版本的最小和最大日期,通过版本号及其顺序组号对进行分组
还要注意的是,使用windows函数
并只点击源表一次,也比递归解决方案要有效得多
with ng as (
select *, case when Lag(version_number) over(order by end_dt) = version_number then 0 else 1 end as ng
from #t1
), grp as (
select *, Sum(ng) over(order by end_dt) as grp
from ng
)
select version_number, Min(eff_dt) eff_dt, Max(end_dt) end_dt
from grp
group by version_number, grp
order by eff_dt
使用窗口函数
可以轻松实现这一点。这是缺口和孤岛问题的一个变体
前提是识别版本号的连续值孤岛。第一个CTE使用lag
将当前行值与前一行值进行比较,并在值不同时标记下一组的开始。第二个CTE使用sum
作为窗口函数来生成