Подсчитайте количество последовательных результатов в SQL

#sql #sql-server #tsql

Вопрос:

Я хочу узнать, сколько раз подряд происходит определенный результат:

      Date                         Car         Result
2021-10-07 16:33:44.270      |   Toyota    |   PASS
2021-10-07 18:35:08.073      |   Ford      |   PASS
2021-10-07 18:39:31.497      |   Jeep      |   FAIL
2021-10-07 21:30:46.150      |   Jeep      |   FAIL
2021-10-08 03:38:55.370      |   Toyota    |   FAIL
2021-10-08 06:43:07.597      |   Ford      |   PASS
2021-10-08 07:10:22.897      |   Toyota    |   FAIL
2021-10-08 07:39:56.810      |   Jeep      |   PASS
2021-10-08 10:45:39.260      |   Toyota    |   PASS
2021-10-08 11:29:45.123      |   Ford      |   FAIL
 

Я хочу увеличить количество последовательных сбоев при появлении результата СБОЯ и сбросить значение до 0 при достижении результата ПРОХОЖДЕНИЯ:

      Date                         Car         Failures
2021-10-07 16:33:44.270      |   Toyota    |   0
2021-10-07 18:35:08.073      |   Ford      |   0
2021-10-07 18:39:31.497      |   Jeep      |   1
2021-10-07 21:30:46.150      |   Jeep      |   2
2021-10-08 03:38:55.370      |   Toyota    |   1
2021-10-08 06:43:07.597      |   Ford      |   0
2021-10-08 07:10:22.897      |   Toyota    |   2
2021-10-08 07:39:56.810      |   Jeep      |   0
2021-10-08 10:45:39.260      |   Toyota    |   0
2021-10-08 11:29:45.123      |   Ford      |   1
 

Кто-нибудь может помочь мне найти правильный (T)SQL-запрос?

Ответ №1:

Это не очень красиво, но я считаю, что это удовлетворяет вашим условиям, а также дает желаемый набор результатов при запуске, используя только ваши примерные данные.

Я добавил несколько дополнительных крайних случаев, чтобы продемонстрировать, что он работает правильно.

 DECLARE @Table TABLE (
    Date datetime
    ,Car varchar(50)
    ,Result varchar(4)
);
INSERT INTO @Table VALUES
('2021-10-07 16:33:44.270','Toyota','PASS')
,('2021-10-07 18:35:08.073','Ford','PASS')
,('2021-10-07 18:39:31.497','Jeep','FAIL')
,('2021-10-07 21:30:46.150','Jeep','FAIL')
,('2021-10-08 03:38:55.370','Toyota','FAIL')
,('2021-10-08 06:43:07.597','Ford','PASS')
,('2021-10-08 07:10:22.897','Toyota','FAIL')
,('2021-10-08 07:39:56.810','Jeep','PASS')
,('2021-10-08 10:45:39.260','Toyota','PASS')
,('2021-10-08 11:29:45.123','Ford','FAIL')
,('2021-10-09 18:39:31.497','Jeep','FAIL')
,('2021-10-09 21:30:46.150','Jeep','FAIL')
,('2021-10-10 18:39:31.497','Jeep','PASS')
,('2021-10-10 21:30:46.150','Jeep','FAIL')
,('2021-10-10 10:45:39.260','Toyota','FAIL');

SELECT 
    pass_grouping.Date 
    ,pass_grouping.Car 
    /*now perform final sum of your counter for consecutive failures (AKA anything other than 'PASS')*/
    ,SUM(CASE WHEN pass_grouping.Result = 'PASS' THEN 0 ELSE 1 END) OVER(PARTITION BY pass_grouping.Car,pass_grouping.PassGrouperId ORDER BY pass_grouping.Date) AS Failures    
FROM 
    /*generate a PassGrouperId to indicate a group of rows in which where there was no 'PASS' ocurrence (which would be indicated by incremented the value of pass_counter.PassOcurrenceRunningTotal).*/
    /*The first row of each PassGrouperId group indicates where we need to reset the value of our final column 'Failures'. So we can partition outer query on that value.*/
    (SELECT 
        pass_counter.Date 
        ,pass_counter.Car 
        ,pass_counter.Result
        ,RANK() OVER(PARTITION BY pass_counter.Car ORDER BY pass_counter.PassOcurrenceRunningTotal) AS PassGrouperId
    FROM 
        /*create indicator that tracks 'PASS' Result ocurrence chronologically (using Date column) per each Car value*/ 
        (SELECT 
            tbl.Date 
            ,tbl.Car 
            ,tbl.Result
            ,SUM(CASE WHEN tbl.Result = 'PASS' THEN 1 ELSE 0 END) OVER(PARTITION BY Car ORDER BY Date) AS PassOcurrenceRunningTotal
        FROM 
            @Table AS tbl) AS pass_counter) AS pass_grouping 
ORDER BY 
    pass_grouping.Date;
 

Комментарии:

1. Неплохо. Это было направление, в котором я двигался после вашего комментария и переоценки моего ответа. Как вы упомянули здесь, найдите эту группу доступа на основе изменений, которые я полностью упустил из виду.

Ответ №2:

РЕДАКТИРОВАТЬ: Мой ответ здесь неверен, потому что, когда значения в результате переключаются обратно на ПЕРЕДАЧУ или СБОЙ (в соответствии с порядком в предложении ROW_NUMBER() OVER ()), нумерация продолжается с того места, где она была прервана, а не снова сбрасывается до 1.

Вы можете сделать это с помощью функции ROW_NUMBER() и некоторой CASE логики, например:

 -- Assuming the example data you posted is in a temp table named #TBL
WITH ResultData AS (
    SELECT *,
        ROW_NUMBER() OVER
        (
            -- Reset the count whenever the car or result changes
            PARTITION BY
                Car,
                Result
            -- Order first by car, and then by date so that the car won't change until we're done with it
            ORDER BY
                Car,
                Date
        )
        AS ResultCount
    FROM #TBL
)
SELECT Date, Car,
CASE WHEN Result = 'PASS' THEN 0 ELSE ResultCount END AS Failures
FROM ResultData
ORDER BY Date;
 

Это дает пример результата, который вы опубликовали. Если вы хотите создать временную таблицу и попробовать ее самостоятельно, добавьте эти инструкции перед приведенным выше запросом:

 CREATE TABLE #TBL
(
    Date datetime,
    Car nvarchar(50),
    Result nvarchar(4)
);

INSERT INTO #TBL VALUES
( '2021-10-07 16:33:44.270','Toyota','PASS' ),
( '2021-10-07 18:35:08.073','Ford','PASS' ),
( '2021-10-07 18:39:31.497','Jeep','FAIL' ),
( '2021-10-07 21:30:46.150','Jeep','FAIL' ),
( '2021-10-08 03:38:55.370','Toyota','FAIL' ),
( '2021-10-08 06:43:07.597','Ford','PASS' ),
( '2021-10-08 07:10:22.897','Toyota','FAIL' ),
( '2021-10-08 07:39:56.810','Jeep','PASS' ),
( '2021-10-08 10:45:39.260','Toyota','PASS' ),
( '2021-10-08 11:29:45.123','Ford','FAIL' );
 

(Убедитесь, что вы включили ; в конце INSERT инструкции, иначе она захлебнется в WITH том, что запускает запрос.)

Комментарии:

1. Это банальный способ получения запрошенного набора результатов, но я бы не сказал, что он удовлетворяет запрошенной логике OP. Например, добавление большего количества подобных данных ('2021-10-09 18:39:31.497','Jeep','FAIL'),('2021-10-09 21:30:46.150','Jeep','FAIL') приведет к значениям 3 и 4 (соответственно) в Failures столбце вместо 1 и 2. Ваш пример на самом деле не reset to 0 when a PASS result is achieved соответствует запросу. В вашем примере вы, по сути, просто скрываете этот факт с CASE помощью заявления

2. @trenton-ftw Я признаю, что он ведет себя не так, как я ожидал, и я не заметил, потому что просто получилось добиться желаемого результата, используя только данные примера операции.