Пометить последовательные ненулевые строки на отдельные разделы? - PullRequest
1 голос
/ 24 января 2020

Предположим, у нас есть эта простая схема и данные:

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

Мне нужно суммировать ненулевые значения Duration подряд и показать их в StartTime первого ряда в пакете. Т.е. мне нужно добраться до этого:

StartTime   Duration
10          5
26          25

Я просто не могу понять, как это сделать.

PS: В реальной таблице, конечно, гораздо больше строк.

Ответы [ 2 ]

1 голос
/ 24 января 2020

Ваш подход излишне сложен. Вам просто нужно присвоить 0 s группам, которые включают в себя точно следующее 1.

. Вы можете сделать это, посчитав количество «1» в каждой строке или после нее. Конечно, это также назначает группировку для строк без "0". Их можно отфильтровать, убедившись, что в каждой группе есть хотя бы 0:

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);

Здесь - это скрипта db <>.

Или альтернативный метод вообще не использует агрегацию. Он просто получает следующее начальное время «1» для каждой строки, а затем фильтрует до первой строки «0»:

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;

Это, вероятно, имеет лучшую производительность среди альтернатив.

1 голос
/ 24 января 2020

Это проблема пробелов и островков, требующая разбиения каждого раздела, где IsPassed постоянно в другую группу. Это можно сделать, рассчитав разницу между ROW_NUMBER() по всей таблице и разбивкой по IsPassed. Затем вы можете SUM значения Duration для каждой группы, где IsPassed = False, и взять 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

Демонстрация на dbfiddle

...