SQL 服务器根据行之间的时间跨度对行进行分组
SQL Server Grouping rows based on time span between rows
假设我们有这个 table:
Date
Par1
Par2
2020-01-31 00:00:10
1
null
2020-01-31 00:00:15
2
null
2020-01-31 00:00:16
null
4
2020-01-31 00:00:30
3
null
目标是在同一行(使用SQL)上获取 Par1 和 Par2 的值,如果两行之间的时间小于 2 秒。在这种情况下,第 2 行和第 3 行仅相隔 1 秒,因此它们应该显示在同一行。所以想要的结果是这样的:
Date
Par1
Par2
2020-01-31 00:00:10
1
null
2020-01-31 00:00:15
2
4
2020-01-31 00:00:30
3
null
Par1 和 Par2 由不同的数据流填充,因此当 Par1 为 NOT null 时,Par2 始终为 null。
我过去曾按接近程度对数据进行分组,但必须经过几个中间步骤才能获得我想要的分组。使用步骤如下:
- 排序并为数据分配明确的行号。
- 根据与前面或后面记录的距离标记作为组开始或结束的行。
- 对于每个组的起始行,找到匹配的结束行并生成聚合该范围内数据的结果。
以下改编可能适合您的目的:
DECLARE @Data TABLE ([Date] DATETIME, Par1 INT, Par2 INT)
INSERT @Data
VALUES
('2020-01-31 00:00:10', 1, null),
('2020-01-31 00:00:15', 2, null),
('2020-01-31 00:00:16', null, 4),
('2020-01-31 00:00:30', 3, null)
;
WITH NUMBERED_DATA AS (
SELECT RowNo = ROW_NUMBER() OVER(ORDER BY [Date]), D.*
FROM @Data D
),
TAGGED_DATA AS (
SELECT B.IsFirst, B.IsLast, D.*
FROM NUMBERED_DATA D
CROSS APPLY(
SELECT
PriorDate = (SELECT D1.[Date] FROM NUMBERED_DATA D1 WHERE D1.RowNo = D.RowNo - 1),
NextDate = (SELECT D2.[Date] FROM NUMBERED_DATA D2 WHERE D2.RowNo = D.RowNo + 1)
) A
CROSS APPLY(
SELECT
IsFirst = CASE WHEN A.PriorDate IS NULL OR DATEDIFF(second, A.PriorDate, D.[date]) >= 2 THEN 1 ELSE 0 END,
IsLast = CASE WHEN A.NextDate IS NULL OR DATEDIFF(second, D.[date], A.NextDate) >= 2 THEN 1 ELSE 0 END
) B
),
COMBINED_DATA AS (
SELECT D1.Date, Par1First = D1.Par1, Par2Last = D2.Par2, Par1Min = MIN(D.Par1), Par2Max = MAX(D.Par2)
FROM TAGGED_DATA D1
CROSS APPLY (
SELECT TOP 1 D2.*
FROM TAGGED_DATA D2
WHERE D2.RowNo >= D1.RowNo AND D2.IsLast = 1
ORDER BY D2.RowNo ASC
) D2
JOIN TAGGED_DATA D ON D.RowNo BETWEEN D1.RowNo AND D2.RowNo
WHERE D1.IsFirst = 1
GROUP BY D1.Date, D1.Par1, D2.Par2
)
SELECT *
FROM COMBINED_DATA
ORDER BY Date
上面使用了通用 table 表达式 (CTE) 和 CROSS APPLY 结构的组合来逐步计算中间结果。要查看中间结果,请将最终 select 中的 COMBINED_DATA 更改为 TAGGED_DATA。
我不确定你是想要 First/last 还是 Min/Max Par1/Par2 值,所以上面计算了两者。如果只需要fist/last,可以去掉JOIN ... BETWEEN ...
和GROUP BY ...
行
上述的一个局限性是它对于大数据来说效率低下,因为它涉及 table 中间结果的扫描或散列连接。理想情况下,RowNo 应该被索引,但我认为这对于 CTE 来说是不可能的。 (参见 Adding an INDEX to a CTE)为了提高效率,需要调整上述逻辑以使用临时 table。
假设我们有这个 table:
Date | Par1 | Par2 |
---|---|---|
2020-01-31 00:00:10 | 1 | null |
2020-01-31 00:00:15 | 2 | null |
2020-01-31 00:00:16 | null | 4 |
2020-01-31 00:00:30 | 3 | null |
目标是在同一行(使用SQL)上获取 Par1 和 Par2 的值,如果两行之间的时间小于 2 秒。在这种情况下,第 2 行和第 3 行仅相隔 1 秒,因此它们应该显示在同一行。所以想要的结果是这样的:
Date | Par1 | Par2 |
---|---|---|
2020-01-31 00:00:10 | 1 | null |
2020-01-31 00:00:15 | 2 | 4 |
2020-01-31 00:00:30 | 3 | null |
Par1 和 Par2 由不同的数据流填充,因此当 Par1 为 NOT null 时,Par2 始终为 null。
我过去曾按接近程度对数据进行分组,但必须经过几个中间步骤才能获得我想要的分组。使用步骤如下:
- 排序并为数据分配明确的行号。
- 根据与前面或后面记录的距离标记作为组开始或结束的行。
- 对于每个组的起始行,找到匹配的结束行并生成聚合该范围内数据的结果。
以下改编可能适合您的目的:
DECLARE @Data TABLE ([Date] DATETIME, Par1 INT, Par2 INT)
INSERT @Data
VALUES
('2020-01-31 00:00:10', 1, null),
('2020-01-31 00:00:15', 2, null),
('2020-01-31 00:00:16', null, 4),
('2020-01-31 00:00:30', 3, null)
;
WITH NUMBERED_DATA AS (
SELECT RowNo = ROW_NUMBER() OVER(ORDER BY [Date]), D.*
FROM @Data D
),
TAGGED_DATA AS (
SELECT B.IsFirst, B.IsLast, D.*
FROM NUMBERED_DATA D
CROSS APPLY(
SELECT
PriorDate = (SELECT D1.[Date] FROM NUMBERED_DATA D1 WHERE D1.RowNo = D.RowNo - 1),
NextDate = (SELECT D2.[Date] FROM NUMBERED_DATA D2 WHERE D2.RowNo = D.RowNo + 1)
) A
CROSS APPLY(
SELECT
IsFirst = CASE WHEN A.PriorDate IS NULL OR DATEDIFF(second, A.PriorDate, D.[date]) >= 2 THEN 1 ELSE 0 END,
IsLast = CASE WHEN A.NextDate IS NULL OR DATEDIFF(second, D.[date], A.NextDate) >= 2 THEN 1 ELSE 0 END
) B
),
COMBINED_DATA AS (
SELECT D1.Date, Par1First = D1.Par1, Par2Last = D2.Par2, Par1Min = MIN(D.Par1), Par2Max = MAX(D.Par2)
FROM TAGGED_DATA D1
CROSS APPLY (
SELECT TOP 1 D2.*
FROM TAGGED_DATA D2
WHERE D2.RowNo >= D1.RowNo AND D2.IsLast = 1
ORDER BY D2.RowNo ASC
) D2
JOIN TAGGED_DATA D ON D.RowNo BETWEEN D1.RowNo AND D2.RowNo
WHERE D1.IsFirst = 1
GROUP BY D1.Date, D1.Par1, D2.Par2
)
SELECT *
FROM COMBINED_DATA
ORDER BY Date
上面使用了通用 table 表达式 (CTE) 和 CROSS APPLY 结构的组合来逐步计算中间结果。要查看中间结果,请将最终 select 中的 COMBINED_DATA 更改为 TAGGED_DATA。
我不确定你是想要 First/last 还是 Min/Max Par1/Par2 值,所以上面计算了两者。如果只需要fist/last,可以去掉JOIN ... BETWEEN ...
和GROUP BY ...
行
上述的一个局限性是它对于大数据来说效率低下,因为它涉及 table 中间结果的扫描或散列连接。理想情况下,RowNo 应该被索引,但我认为这对于 CTE 来说是不可能的。 (参见 Adding an INDEX to a CTE)为了提高效率,需要调整上述逻辑以使用临时 table。