Sql 用于汇总具有版本历史记录的表中的行的查询

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

我正在寻找有关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:38.993 1. 2021-04-19 20:45:38.997 2021-05-06 16:00:59.990 2. 2021-05-06 16:00:59.990 2021-05-06 16:13:03.997 3. 2021-05-06 16:13:04.000 2021-05-06 16:17:23.127 4. 2021-05-06 16:17:23.130 2021-05-06 16:52:45.250 4. 2021-05-06 16:52:45.253 2021-05-11 15:36:25.283 4. 2021-05-11 15:36:25.283 2021-05-14 15:52:50.843 5. 2021-05-14 15:52:50.847 2021-05-20 17:14:55.860 4. 2021-05-20 17:14:55.863 2021-05-20 17:14:55.867 1. 2021-05-20 17:14:55.870 9999-12-31 00:00:00.000
您可以使用连续的公共表表达式(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
作为
窗口函数来生成