Как определить группы последовательных дат в SQL?
Im пытается написать функцию, которая идентифицирует группы дат и измеряет размер группы.
Я делал это процедурно в Python до сих пор, но я хотел бы переместить его в SQL.
например, список
Bill 01/01/2011
Bill 02/01/2011
Bill 03/01/2011
Bill 05/01/2011
Bill 07/01/2011
должно быть выведено в новую таблицу как:
Bill 01/01/2011 3
Bill 02/01/2011 3
Bill 03/01/2011 3
Bill 05/01/2011 1
Bill 07/01/2011 1
В идеале это также должно учитывать выходные и праздничные дни - даты в моей таблице всегда будут Пн-Пт (я думаю, что могу это решить путем составления новой таблицы рабочих дней и нумерации их по порядку). Кто-то на работе предложил мне попробовать CTE. Я довольно новичок в этом, поэтому я был бы признателен за любое руководство, которое кто-либо может предоставить! Спасибо.
2 ответов
вы можете сделать это с помощью умного приложения оконных функций. Рассмотрим следующее:
select name, date, row_number() over (partition by name order by date)
from t
Это добавляет номер строки, который в вашем примере будет просто 1, 2, 3, 4, 5. Теперь возьмите разницу от даты, и у вас есть постоянное значение для группы.
select name, date,
dateadd(d, - row_number() over (partition by name order by date), date) as val
from t
наконец, вы хотите, чтобы количество групп в последовательности. Я бы также добавил идентификатор группы (например, чтобы различать последние два).
select name, date,
count(*) over (partition by name, val) as NumInSeq,
dense_rank() over (partition by name order by val) as SeqID
from (select name, date,
dateadd(d, - row_number() over (partition by name order by date), date) as val
from t
) t
Как-То, Я пропустил часть про будни и праздники. Это решение не решает эту проблему.
следующая учетная запись запроса выходные и праздничные дни. В запросе есть положение о включении праздников на лету, хотя с целью сделать запрос более ясным, я просто материализовал праздники в фактическую таблицу.
CREATE TABLE tx
(n varchar(4), d date);
INSERT INTO tx
(n, d)
VALUES
('Bill', '2006-12-29'), -- Friday
-- 2006-12-30 is Saturday
-- 2006-12-31 is Sunday
-- 2007-01-01 is New Year's Holiday
('Bill', '2007-01-02'), -- Tuesday
('Bill', '2007-01-03'), -- Wednesday
('Bill', '2007-01-04'), -- Thursday
('Bill', '2007-01-05'), -- Friday
-- 2007-01-06 is Saturday
-- 2007-01-07 is Sunday
('Bill', '2007-01-08'), -- Monday
('Bill', '2007-01-09'), -- Tuesday
('Bill', '2012-07-09'), -- Monday
('Bill', '2012-07-10'), -- Tuesday
('Bill', '2012-07-11'); -- Wednesday
create table holiday(d date);
insert into holiday(d) values
('2007-01-01');
/* query should return 7 consecutive good
attendance(from December 29 2006 to January 9 2007) */
/* and 3 consecutive attendance from July 7 2012 to July 11 2012. */
запрос:
with first_date as
(
-- get the monday of the earliest date
select dateadd( ww, datediff(ww,0,min(d)), 0 ) as first_date
from tx
)
,shifted as
(
select
tx.n, tx.d,
diff = datediff(day, fd.first_date, tx.d)
- (datediff(day, fd.first_date, tx.d)/7 * 2)
from tx
cross join first_date fd
union
select
xxx.n, h.d,
diff = datediff(day, fd.first_date, h.d)
- (datediff(day, fd.first_date, h.d)/7 * 2)
from holiday h
cross join first_date fd
cross join (select distinct n from tx) as xxx
)
,grouped as
(
select *, grp = diff - row_number() over(partition by n order by d)
from shifted
)
select
d, n, dense_rank() over (partition by n order by grp) as nth_streak
,count(*) over (partition by n, grp) as streak
from grouped
where d not in (select d from holiday) -- remove the holidays
выход:
| D | N | NTH_STREAK | STREAK |
-------------------------------------------
| 2006-12-29 | Bill | 1 | 7 |
| 2007-01-02 | Bill | 1 | 7 |
| 2007-01-03 | Bill | 1 | 7 |
| 2007-01-04 | Bill | 1 | 7 |
| 2007-01-05 | Bill | 1 | 7 |
| 2007-01-08 | Bill | 1 | 7 |
| 2007-01-09 | Bill | 1 | 7 |
| 2012-07-09 | Bill | 2 | 3 |
| 2012-07-10 | Bill | 2 | 3 |
| 2012-07-11 | Bill | 2 | 3 |
тест: http://www.sqlfiddle.com/#!3/815c5/1
основная логика запроса, чтобы сдвинуть все даты на два дня назад. Это сделано делим дату на 7 и умножаем ее на две, затем вычитаем из исходного числа. Например, если заданная дата приходится на 15 - ю, это будет вычислено как 15/7 * 2 == 4; затем вычесть 4 из исходного числа, 15-4 == 11. 15 станет 11-м днем. Точно так же 8-й день становится 6-м днем; 8 - (8/7 * 2) == 6.
Weekends are not in attendance(e.g. 6,7,13,14)
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15
применение вычисления ко всем числам буднего дня даст следующие значения:
1 2 3 4 5
6 7 8 9 10
11
для праздников, вам нужно слот их на посещаемость, так что подряд-Несс можно легко определить, а затем просто удалить их из окончательного запроса. Выше посещаемость дает 11 подряд хорошая посещаемость.
подробное объяснение логики запроса здесь:http://www.ienablemuch.com/2012/07/monitoring-perfect-attendance.html