Группируйте даты с учетом дней ожидания

#sql-server #group-by #window-functions

#sql-server #группируйте по #окно-функции

Вопрос:

Я использую SQL Server 2017. У меня есть таблица запросов, чтобы упростить задачу, есть только один столбец RequestDate . Например,

 RquestDate
4/11
4/12
4/13
4/16
4/18
 

Мне нужно сгруппировать по дате запроса, учитывая дни ожидания. Если день ожидания равен 0, результат должен быть таким же, как в необработанной таблице.

Если день ожидания равен 1, это означает, что когда я смотрю на 4/11, мне нужно проверить, существует ли 4/12, если да, сгруппируйте 4/12 в 4/11. Результат:

 4/11 --it groups 4/12
4/13
4/16
4/18
 

Если день ожидания равен 2, при просмотре 4/11 он группирует в него 4/12, 4/13.
Результат:

 4/11 -- group 4/12 and 4/13.
4/16 -- group 4/18
 

Таким образом, эта проблема отличается от типичной проблемы с пробелами и островами. Потому что, когда группируются даты, там может быть пробел, например, когда прогнозируемый день равен 2, 4/16, группам 4/17 и 4/18.

Я пробовал несколько способов, но не могу найти достойного решения.

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

1. Пожалуйста, покажите нам, что вы пробовали. И уточните, фиксировано ли количество дней ожидания для данного выполнения запроса? И я рекомендую вам добавить некоторые образцы данных, используя DDL DML, поскольку это облегчает людям возможность играть и, возможно, отвечать.

2. И хотя 13 всего на 1 больше, чем 12, он определенно запускает новую группу и не группируется с 11 и 12?

Ответ №1:

Может работать рекурсивное общее табличное выражение.

  1. Выберите дату начала запроса с помощью min() функции.
  2. Используйте ту же дату, что и дата начала группировки.

Шаги 1 и 2 составляют строку привязки / начала рекурсии.

  1. Рекурсивно перейдите к поиску даты следующего запроса. Эта дата выше, чем предыдущая дата ( r.RequestDate > c.RequestDate ), и перед ней нет другой строки, которая соответствует тем же критериям ( not exists ... r2.RequestDate < r.RequestDate ).
  2. Если текущая дата запроса (с шага 3) попадает в интервал ожидания, то сохраните дату начала группировки ( then c.RequestGroupDate ), в противном случае запустите новую группу в текущую дату запроса ( else r.RequestDate ).

Шаги 3 и 4 составляют рекурсивную часть CTE.

  1. После рекурсии каждая дата запроса в качестве соответствующей даты группировки запроса. group by r.RequestGroupDate Предложение уменьшает вывод результата до отдельных значений.

Примерные данные

 create table Requests
(
  RequestDate date
);

insert into Requests (RequestDate) values
('2021-04-11'),
('2021-04-12'),
('2021-04-13'),
('2021-04-16'),
('2021-04-18');
 

Решение

 declare @lookAhead int = 1; -- look ahead days parameter

with rcte as
(
  select min(r.RequestDate) as RequestDate,
         min(r.RequestDate) as RequestGroupDate
  from Requests r
union all
  select r.RequestDate,
         case
           when datediff(day, c.RequestGroupDate, r.RequestDate) <= @lookAhead
           then c.RequestGroupDate
           else r.RequestDate
         end
  from rcte c
  join Requests r
    on r.RequestDate > c.RequestDate
  where not exists ( select 'x'
                     from Requests r2
                     where r2.RequestDate > c.RequestDate
                       and r2.RequestDate < r.RequestDate )
)
select r.RequestGroupDate
from rcte r
group by r.RequestGroupDate;
 

Результат

Для @lookAhead = 1 :

 RequestGroupDate
----------------
2021-04-11
2021-04-13
2021-04-16
2021-04-18
 

Для @lookahead = 2 :

 RequestGroupDate
----------------
2021-04-11
2021-04-16
 

Поиграйте, чтобы увидеть вещи в действии.

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

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

2. Если я правильно понимаю, предложение «где не существует» заставляет CTE обрабатывать таблицу строка за строкой. Это похоже на перемещение курсора от самой старой даты запроса к самой новой дате запроса. Для каждого перемещения он переносит предыдущий RequestGroupDate и определяет, принадлежит ли текущая строка предыдущему ReuqestGroupDate. Если нет, он использует свой собственный RequestDate в качестве нового RequestGroupDate. Меня беспокоит только то, что максимальная глубина рекурсивного SQL CTE равна 32767. Означает ли это, что он не может обрабатывать таблицу с более чем 32767 строками. Я протестирую это. @Sander.

3. @DaleK, я добавил описание решения с пошаговыми инструкциями.

4. @FrankZhang, в случае, если ограничение рекурсии может создать проблему, пожалуйста, задайте другой вопрос и объясните, почему это решение не работает для вас.

5. @Sander спасибо за это — все еще пытаюсь разобраться в шаге 3 🙂 это требует некоторого сгибания ума.