Количество SQL последовательных дней

Это данные базы данных SQL:

UserTable

UserName    | UserDate      | UserCode
-------------------------------------------
user1       | 08-31-2014    | 232
user1       | 09-01-2014    | 232
user1       | 09-02-2014    | 0
user1       | 09-03-2014    | 121
user1       | 09-08-2014    | 122
user1       | 09-09-2014    | 0
user1       | 09-10-2014    | 144
user1       | 09-11-2014    | 166
user2       | 09-01-2014    | 177
user2       | 09-04-2014    | 188
user2       | 09-05-2014    | 199
user2       | 09-06-2014    | 0
user2       | 09-07-2014    | 155

Следует считать только последовательные дни (как Результат), если [UserCode] не равен нулю. UserDate - с 01.09.2014 по 11.09.2014. Показывать результат, только если Результат равен 2 или больше.

Я хочу, чтобы мой sql-запрос возвращал:

UserName    | StartDate     | EndDate       | Result
----------------------------------------------------------
user1       | 09-01-2014    | 09-03-2014    | 2
user1       | 09-08-2014    | 09-11-2014    | 3
user2       | 09-04-2014    | 09-07-2014    | 3

Возможно ли это, используя только SQL-запрос?


person majukivi    schedule 30.09.2014    source источник
comment
Какую версию mssql вы используете?   -  person Arion    schedule 30.09.2014


Ответы (2)


Это проблема Пробелов и островов . Самый простой способ решить эту проблему - использовать ROW_NUMBER() для определения пробелов в последовательности:

SELECT  UserName,
        UserDate,
        UserCode,
        GroupingSet = DATEADD(DAY, 
                            -ROW_NUMBER() OVER(PARTITION BY UserName 
                                                        ORDER BY UserDate), 
                            UserDate)
FROM    UserTable;

Это дает:

UserName    | UserDate      | UserCode   | GroupingSet
------------+---------------+------------+-------------
user1       | 09-01-2014    | 1          | 08-31-2014    
user1       | 09-02-2014    | 0          | 08-31-2014    
user1       | 09-03-2014    | 1          | 08-31-2014    
user1       | 09-08-2014    | 1          | 09-04-2014    
user1       | 09-09-2014    | 0          | 09-04-2014    
user1       | 09-10-2014    | 1          | 09-04-2014    
user1       | 09-11-2014    | 1          | 09-04-2014    
user2       | 09-01-2014    | 1          | 08-31-2014    
user2       | 09-04-2014    | 1          | 09-02-2014    
user2       | 09-05-2014    | 1          | 09-02-2014    
user2       | 09-06-2014    | 0          | 09-02-2014    
user2       | 09-07-2014    | 1          | 09-02-2014    

Как видите, это дает постоянное значение в GroupingSet для последовательных строк. Затем вы можете сгруппировать по этому столбцу, чтобы получить желаемое:

WITH CTE AS
(   SELECT  UserName,
            UserDate,
            UserCode,
            GroupingSet = DATEADD(DAY, 
                                -ROW_NUMBER() OVER(PARTITION BY UserName 
                                                            ORDER BY UserDate), 
                                UserDate)
    FROM    UserTable
)
SELECT  UserName,
        StartDate = MIN(UserDate),
        EndDate = MAX(UserDate),
        Result = COUNT(NULLIF(UserCode, 0))
FROM    CTE
GROUP BY UserName, GroupingSet
HAVING COUNT(NULLIF(UserCode, 0)) > 1
ORDER BY UserName, StartDate;

Пример на скрипте SQL

person GarethD    schedule 30.09.2014
comment
Спасибо, но я отредактировал свой вопрос. Виноват! Я не мог использовать сумму, потому что UserCode не всегда равен 1 или 0. - person majukivi; 30.09.2014
comment
Хорошо, я только что изменил его с SUM на COUNT(NULLIF(UserCode, 0)), поэтому, когда UserCode равно 0, функция NULLIF превратит его в NULL, и он не будет включен в COUNT - person GarethD; 30.09.2014
comment
Это такое блестящее решение ???????? - person Chinonso Chukwuogor; 20.04.2019

Пожалуйста, попробуй:

;with T1 as(
    select 
        *, 
        ROW_NUMBER() over (  order by UserName, UserDate) ID 
    from tbl
)
,T as (
    SELECT *, 1 CNT FROM T1 where ID=1
    union all
    SELECT b.*, (case when T.UserDate+1=b.UserDate and 
                           T.UserName=b.UserName then t.CNT 
                        else T.CNT+1 end)
    from T1 b INNER JOIN T on b.ID=T.ID+1
)
select distinct UserName, MIN(UserDate), max(UserDate)
,sum(case UserCode when 0 then 0 else 1 end) From T group by UserName, CNT
having COUNT(*)>1

Демонстрация скрипта SQL

person TechDo    schedule 30.09.2014
comment
Спасибо, но я отредактировал свой вопрос. Виноват! Я не мог использовать сумму, потому что UserCode не всегда равен 1 или 0. - person majukivi; 30.09.2014