Выборка предстоящих событий (например, дней рождения клиентов или сотрудников) за определенный интервал времени — типовая задача при разработке отчетов в системе компоновки данных (СКД). Однако при попытке написать условие «в лоб» разработчики часто сталкиваются с логическими и синтаксическими ошибками. Разберем исходный пример, выясним причину некорректной работы запроса и рассмотрим правильные варианты реализации отбора.
Посмотрим на условие, которое было предложено изначально:
ГДЕ
ДЕНЬ(Клиенты.ДатаРождения) >= ДЕНЬ(&ДатаКонтрол)
И ДЕНЬ(Клиенты.ДатаРождения) <= ДЕНЬ(&ДатаКонтрол + 7)
И МЕСЯЦ(Клиенты.ДатаРождения) = МЕСЯЦ(&ДатаКонтрол)
В этом фрагменте допущено сразу несколько критических ошибок:
&ДатаКонтрол + 7. Прибавление интервалов в запросе выполняется строго через специализированную функцию ДОБАВИТЬКДАТЕ().&ДатаКонтрол равен, например, 28 октября, то дата через 7 дней приходится на 4 ноября. В этом случае условие ДЕНЬ(Дата) >= 28 И ДЕНЬ(Дата) <= 4 превращается в логический тупик, так как число дня не может быть одновременно больше 28 и меньше 4. Запрос вернет пустой результат.МЕСЯЦ(Клиенты.ДатаРождения) = МЕСЯЦ(&ДатаКонтрол) сразу отсекает дни рождения людей, родившихся в первых числах следующего месяца, даже если они попадают в нужный семидневный диапазон.Выясним, как правильно рассчитать границы периода без усложнения текста запроса. Наиболее эффективный подход в СКД — перенести расчет конечной даты в параметры схемы компоновки данных, а в запросе использовать стандартную проверку.
В схеме компоновки данных настроим параметры следующим образом:
&ДатаКонтрол, который будет доступен пользователю для выбора (или заполняться текущей датой по умолчанию).&ДатаКонтролКонец, включим для него флаг Ограничение доступности (чтобы скрыть от пользователя) и в поле Выражение пропишем вычисление: КонецПериода(ДобавитьКДате(&ДатаКонтрол, "ДЕНЬ", 7), "ДЕНЬ"). Функция КонецПериода() критически важна, так как тип Дата содержит время, и без захвата конца дня (23:59:59) события последнего дня интервала будут потеряны.Если требуется выборка документов за реальный период, используется условие Клиенты.ДатаРождения МЕЖДУ &ДатаКонтрол И &ДатаКонтролКонец. Однако с днями рождения такой фильтр напрямую работать не будет, так как год рождения клиента не совпадает с текущим годом. Для сопоставления дней рождения без учета года воспользуемся следующими способами.
Проанализируем универсальный математический способ, который исключает зависимость от конкретного года. Преобразуем дату рождения и границы диапазона в числовой код формата Месяц * 100 + День (например, 15 мая превращается в 5 * 100 + 15 = 515).
Настроим в СКД вычисляемые параметры:
&КодНачала с выражением: Месяц(&ДатаКонтрол) * 100 + День(&ДатаКонтрол).&КодОкончания с выражением: Месяц(ДобавитьКДате(&ДатаКонтрол, "ДЕНЬ", 7)) * 100 + День(ДобавитьКДате(&ДатаКонтрол, "ДЕНЬ", 7)).Посмотрим на текст запроса с обработкой перехода через границу года (декабрь — январь):
ВЫБРАТЬ
Клиенты.Ссылка КАК Клиент,
Клиенты.ДатаРождения КАК ДатаДняРождения,
Клиенты.Ответственный КАК Ответственный
ИЗ
Справочник.Клиенты КАК Клиенты
ГДЕ
Клиенты.ДатаРождения <> ДАТАВРЕМЯ(1, 1, 1)
И ВЫБОР
КОГДА &КодНачала <= &КодОкончания ТОГДА
МЕСЯЦ(Клиенты.ДатаРождения) * 100 + ДЕНЬ(Клиенты.ДатаРождения) МЕЖДУ &КодНачала И &КодОкончания
ИНАЧЕ
// Случай перехода через Новый год (например, с 28 декабря по 4 января)
МЕСЯЦ(Клиенты.ДатаРождения) * 100 + ДЕНЬ(Клиенты.ДатаРождения) >= &КодНачала
ИЛИ МЕСЯЦ(Клиенты.ДатаРождения) * 100 + ДЕНЬ(Клиенты.ДатаРождения) <= &КодОкончания
КОНЕЦ
Такая конструкция корректно отрабатывает как обычный переход между месяцами внутри одного года, так и смену календарного года.
Разберем еще один надежный подход: генерацию таблицы из 8 календарных дней интересующего периода непосредственно в запросе с последующим соединением с базой клиентов. Этот метод гарантирует корректность при любых переходах дат.
ВЫБРАТЬ
ДОБАВИТЬКДАТЕ(&ДатаКонтрол, ДЕНЬ, 0) КАК ПериодДень
ПОМЕСТИТЬ ВТ_Период
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ ДОБАВИТЬКДАТЕ(&ДатаКонтрол, ДЕНЬ, 1)
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ ДОБАВИТЬКДАТЕ(&ДатаКонтрол, ДЕНЬ, 2)
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ ДОБАВИТЬКДАТЕ(&ДатаКонтрол, ДЕНЬ, 3)
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ ДОБАВИТЬКДАТЕ(&ДатаКонтрол, ДЕНЬ, 4)
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ ДОБАВИТЬКДАТЕ(&ДатаКонтрол, ДЕНЬ, 5)
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ ДОБАВИТЬКДАТЕ(&ДатаКонтрол, ДЕНЬ, 6)
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ ДОБАВИТЬКДАТЕ(&ДатаКонтрол, ДЕНЬ, 7)
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
Клиенты.Наименование КАК Клиент,
Клиенты.ДатаРождения КАК ДатаДняРождения,
Клиенты.Ответственный КАК Ответственный
ИЗ
ВТ_Период КАК ВТ_Период
ВНУТРЕННЕЕ СОЕДИНЕНИЕ Справочник.Клиенты КАК Клиенты
ПО ДЕНЬ(Клиенты.ДатаРождения) = ДЕНЬ(ВТ_Период.ПериодДень)
И МЕСЯЦ(Клиенты.ДатаРождения) = МЕСЯЦ(ВТ_Период.ПериодДень)
ГДЕ
Клиенты.ДатаРождения <> ДАТАВРЕМЯ(1, 1, 1)
В этом решении логика полностью переносится на прямое совпадение дня и месяца каждого элемента сгенерированного диапазона, что снимает необходимость вручную рассчитывать условия переходов через край месяца или года.
При реализации подобных отчетов обратим внимание на следующие практические нюансы:
Клиенты.ДатаРождения <> ДАТАВРЕМЯ(1, 1, 1), чтобы исключить попадание некорректных строк в выборку.ДЕНЬ(), МЕСЯЦ() или арифметических выражений над полями таблиц базы данных в условии ГДЕ или в условии соединения ПО делает невозможным использование индексов СУБД. Происходит полное сканирование таблицы (Table Scan). Для баз данных с миллионами контрагентов оптимальным решением является хранение вычисленного дня и месяца (или числа MMDD) в отдельных индексированных реквизитах справочника. Для небольших и средних справочников приведенных выше решений в СКД более чем достаточно.