#sql #sql-server #date
#sql #sql-сервер #Дата
Вопрос:
Действие может запускаться и останавливаться 0 или более раз в течение заданного периода времени. Каждый раз, когда это происходит, создается запись с датой и действием — я буду называть действия «Активировать (A)» и «Деактивировать (D)». Мне нужно подвести итог общему количеству «активных» дней в данном месяце. Я хотел бы простой способ сделать это, не загроможденный исключениями и особыми случаями. Мне придется сделать это в tsql.
Вот процедурный подход, который я использовал до сих пор:
- Было ли событие start или stop в течение периода? а) если НЕТ, то найдите последнее событие, которое предшествует периоду времени i) если это событие было Start, то все дни периода активны ii) если событие было Stop, в периоде нет активных дней iii) выполнено б) если ДА … тогда крысиное гнездо процедурных проверок и if-thens.
Как обычно, я хотел бы понять, как другие увидят проблему, а не просто начать кодировать процедуру для сортировки записей в хронологическом порядке и перебирать их.
Типичный набор данных:
6/11/2014 A
6/20/2014 D
6/24/2014 A
6/26/2014 D
6/29/2014 A
Здесь интересующий период — июнь, и первым действием было активировать — так что действие было неактивным до 6/11. Аналогично, 6/29 все снова становится активным, и поэтому оставшаяся часть месяца считается активной. Используя функцию datediff() для D и A дат, я получаю 9 2, а последний день месяца составляет 12 активных дней.
Опять же, только для набора данных за один период может быть 0 или более записей.
Естественно, данные могли бы также выглядеть следующим образом:
6/11/2014 D
6/20/2014 A
6/24/2014 D
6/26/2014 A
6/29/2014 D
Здесь действие было «активным» до 6/11, когда оно было деактивировано. Просто datediff()’ing (D — A) dates дает мне дополнительные 4 3 дня, в общей сложности 17.
Я боюсь, что если я ВЫБЕРУ, ОТСОРТИРУЮ, а затем СДЕЛАЮ, пока я собираюсь обернуться вокруг оси. Интересно, какой подход slicker могли бы использовать другие.
Ответ №1:
Этот запрос предоставит вам все дни за данный период с соответствующим событием за день:
Настройка схемы MS SQL Server 2008:
CREATE TABLE Table1
([dt] date, [event] varchar(1))
;
INSERT INTO Table1
([dt], [event])
VALUES
('2014-06-11', 'A'),
('2014-06-20', 'D'),
('2014-06-24', 'A'),
('2014-06-26', 'D'),
('2014-06-29', 'A')
;
Запрос 1:
declare @start date, @end date
select @start = '2014-06-01', @end = '2014-06-30'
;with dates as (
SELECT @start AS d, (SELECT TOP 1 CASE WHEN event = 'A' THEN 'D' ELSE 'A' END FROM Table1 ORDER BY dt) AS e
UNION ALL
SELECT dateadd(day, 1, d), COALESCE((SELECT event FROM Table1 WHERE dt = dateadd(day, 1, dates.d)), dates.e)
FROM dates
WHERE dates.d < @end)
select * from dates
OPTION (MAXRECURSION 0)
| D | E |
|------------|---|
| 2014-06-01 | D |
| 2014-06-02 | D |
| 2014-06-03 | D |
| 2014-06-04 | D |
| 2014-06-05 | D |
| 2014-06-06 | D |
| 2014-06-07 | D |
| 2014-06-08 | D |
| 2014-06-09 | D |
| 2014-06-10 | D |
| 2014-06-11 | A |
| 2014-06-12 | A |
| 2014-06-13 | A |
| 2014-06-14 | A |
| 2014-06-15 | A |
| 2014-06-16 | A |
| 2014-06-17 | A |
| 2014-06-18 | A |
| 2014-06-19 | A |
| 2014-06-20 | D |
| 2014-06-21 | D |
| 2014-06-22 | D |
| 2014-06-23 | D |
| 2014-06-24 | A |
| 2014-06-25 | A |
| 2014-06-26 | D |
| 2014-06-27 | D |
| 2014-06-28 | D |
| 2014-06-29 | A |
| 2014-06-30 | A |
Используя приведенный выше запрос, вы сможете легко создавать запросы для подсчета, либо следующим образом:
declare @start date, @end date
select @start = '2014-06-01', @end = '2014-06-30'
;with dates as (
SELECT @start AS d, (SELECT TOP 1 CASE WHEN event = 'A' THEN 'D' ELSE 'A' END FROM Table1 ORDER BY dt) AS e
UNION ALL
SELECT dateadd(day, 1, d), COALESCE((SELECT event FROM Table1 WHERE dt = dateadd(day, 1, dates.d)), dates.e)
FROM dates
WHERE dates.d < @end)
select e, count(*) from dates GROUP BY e
OPTION (MAXRECURSION 0)
Результаты:
| E | COLUMN_1 |
|---|----------|
| A | 13 |
| D | 17 |
или как это:
declare @start date, @end date
select @start = '2014-06-01', @end = '2014-06-30'
;with dates as (
SELECT @start AS d, (SELECT TOP 1 CASE WHEN event = 'A' THEN 'D' ELSE 'A' END FROM Table1 ORDER BY dt) AS e
UNION ALL
SELECT dateadd(day, 1, d), COALESCE((SELECT event FROM Table1 WHERE dt = dateadd(day, 1, dates.d)), dates.e)
FROM dates
WHERE dates.d < @end)
select
sum(case when e='A' THEN 1 ELSE 0 END) as A_days,
sum(case when e='D' THEN 1 ELSE 0 END) as D_days
from dates
OPTION (MAXRECURSION 0)
Результаты:
| A_DAYS | D_DAYS |
|--------|--------|
| 13 | 17 |
Комментарии:
1. Законно ли также определять вашу table1 как CTE после WITH, например, так: С table1 (d, t) КАК (…), даты КАК ([ваше определение выше] )? Я не видел ссылок (пока) на смешивание нерекурсивных и рекурсивных определений в пределах одной области видимости WITH.
2. не понял вашего вопроса
3. вот так: sqlfiddle.com / #!3/d41d8 /36209 Я думаю, вы могли бы просто попробовать сами….
4. Да — я сделал, и это делает именно то, что мне было нужно … работает отлично.
5. Кстати, если вы заметили, я допустил ошибку. Вам нужно будет исправить это самостоятельно (очень легко исправить). Если у вас есть событие в день1 (т. Е. Если событие для
@start
даты существует в вашей таблице), начальноеe
значение вdates
CTE будет неправильно установлено на противоположное событие. Очень легко исправить, я оставлю это себе