Sql 将连续的非零行标记为不同的分区?
假设我们有这个简单的模式和数据:Sql 将连续的非零行标记为不同的分区?,sql,sql-server,window-functions,Sql,Sql Server,Window Functions,假设我们有这个简单的模式和数据: DROP TABLE #builds CREATE TABLE #builds ( Id INT IDENTITY(1,1) NOT NULL, StartTime INT, IsPassed BIT ) INSERT INTO #builds (StartTime, IsPassed) VALUES (1, 1), (7, 1), (10, 0), (15, 1), (21, 1), (26, 0), (34, 0), (44, 0),
DROP TABLE #builds
CREATE TABLE #builds (
Id INT IDENTITY(1,1) NOT NULL,
StartTime INT,
IsPassed BIT
)
INSERT INTO #builds (StartTime, IsPassed) VALUES
(1, 1),
(7, 1),
(10, 0),
(15, 1),
(21, 1),
(26, 0),
(34, 0),
(44, 0),
(51, 1),
(60, 1)
SELECT StartTime, IsPassed, NextStartTime,
CASE IsPassed WHEN 1 THEN 0 ELSE NextStartTime - StartTime END Duration
FROM (
SELECT
LEAD(StartTime) OVER (ORDER BY StartTime) NextStartTime,
StartTime, IsPassed
FROM #builds
) x
ORDER BY StartTime
它生成以下结果集:
StartTime IsPassed NextStartTime Duration
1 1 7 0
7 1 10 0
10 0 15 5
15 1 21 0
21 1 26 0
26 0 34 8
34 0 44 10
44 0 51 7
51 1 60 0
60 1 NULL 0
我需要汇总非零的连续持续时间值,并在批处理中第一行的开始时间显示它们。也就是说,我需要做到这一点:
StartTime Duration
10 5
26 25
我就是不知道怎么做
PS:当然,真正的表包含更多的行。这是一个间隙和孤岛问题,需要将
IsPassed
为常量的每个部分划分为不同的组。这可以通过计算整个表上的ROW\u NUMBER()
与IsPassed
分区的差值来实现。然后,您可以对IsPassed=False
的每个组的Duration
值进行SUM
计算,并取MIN(StartTime)
得出该组第一行的StartTime
:
WITH CTE AS (
SELECT StartTime, IsPassed,
LEAD(StartTime) OVER (ORDER BY StartTime) AS NextStartTime
FROM #builds
),
CTE2 AS (
SELECT StartTime, IsPassed, NextStartTime,
CASE IsPassed WHEN 1 THEN 0 ELSE NextStartTime - StartTime END Duration,
ROW_NUMBER() OVER (ORDER BY StartTime) -
ROW_NUMBER() OVER (PARTITION BY IsPassed ORDER BY StartTime) AS grp
FROM CTE
)
SELECT MIN(StartTime) AS StartTime, SUM(Duration) AS Duration
FROM CTE2
WHERE IsPassed = 0
GROUP BY grp
ORDER BY MIN(StartTime)
输出:
StartTime Duration
10 5
26 25
您的方法过于复杂。您只需将
0
s分配给恰好包含以下1
的组
您可以通过计算每行上或每行后面的“1”的数量来实现这一点。当然,这也会为没有“0”的行分配一个分组。可通过确保每组中至少有on0
来过滤这些内容:
select min(StartTime), max(startTime) - min(startTime)
from (select b.*,
sum(case when IsPassed = 1 then 1 else 0 end) over (order by StartTime desc) as grp
from builds b
) b
group by grp
having min(convert(int, IsPassed)) = 0
order by min(StartTime);
他是一把小提琴
或者另一种方法根本不使用聚合。它只需为每一行获取下一个“1”开始时间,然后向下过滤到第一个“0”行:
这可能是替代品中性能最好的。这绝对是华丽的。我设法理解我的问题是一种缺口和孤岛(我在过去的几个小时里学会了这个词),但我没有理解理论,也没有弄清楚我该如何分组。“这太棒了!”马克我花了很长时间才弄明白。我在演示中留下了
SELECT*FROM CTE2
查询,以便您可以看到正在形成的组号。一旦你有了他们,得到结果就不难了。@mark我刚注意到我在第一次CTE中留下了一个不必要的滞后。我已经更新了答案和演示以删除它。所以他们的关键是对行进行分组,而我没有找到方法。我不明白在第一个变量中,sum(当IsPassed=1,然后1,否则0结束)是如何通过(order by StartTime desc)
工作的。没有分区,sum
到底做什么?我将很快研究第二种变体。您的回答突出了一个令人悲伤的事实——我不理解窗口函数是如何工作的。在第二个变体中,您再次使用min而不进行分区-它是如何工作的?@mark。如果没有按
划分的分区,则所有行都在一个分区中。我认为答案解释了它们是如何工作的——它们使用窗口函数为数据分配适当的分组。这比您的方法或其他答案中的方法简单得多(可能更快)。
select StartTime, next_1_starttime - StartTime
from (select b.*,
lag(IsPassed) over (order by StartTime) as prev_IsPassed,
min(case when IsPassed = 1 then StartTime end) over (order by StartTime desc) as next_1_starttime
from builds b
) b
where IsPassed = 0 and (prev_IsPassed = 1 or prev_IsPassed is null)
order by StartTime;