Как в отчете СКД отобрать дни рождения клиентов за ближайшие 7 дней?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

Выборка предстоящих событий (например, дней рождения клиентов или сотрудников) за определенный интервал времени — типовая задача при разработке отчетов в системе компоновки данных (СКД). Однако при попытке написать условие «в лоб» разработчики часто сталкиваются с логическими и синтаксическими ошибками. Разберем исходный пример, выясним причину некорректной работы запроса и рассмотрим правильные варианты реализации отбора.

Анализ ошибок в исходном запросе

Посмотрим на условие, которое было предложено изначально:


ГДЕ
    ДЕНЬ(Клиенты.ДатаРождения) >= ДЕНЬ(&ДатаКонтрол)
    И ДЕНЬ(Клиенты.ДатаРождения) <= ДЕНЬ(&ДатаКонтрол + 7)
    И МЕСЯЦ(Клиенты.ДатаРождения) = МЕСЯЦ(&ДатаКонтрол)

В этом фрагменте допущено сразу несколько критических ошибок:

  1. Синтаксическая ошибка работы со временем: язык запросов 1С не поддерживает прямое арифметическое сложение дат и чисел вида &ДатаКонтрол + 7. Прибавление интервалов в запросе выполняется строго через специализированную функцию ДОБАВИТЬКДАТЕ().
  2. Переход через границу месяца: если параметр &ДатаКонтрол равен, например, 28 октября, то дата через 7 дней приходится на 4 ноября. В этом случае условие ДЕНЬ(Дата) >= 28 И ДЕНЬ(Дата) <= 4 превращается в логический тупик, так как число дня не может быть одновременно больше 28 и меньше 4. Запрос вернет пустой результат.
  3. Игнорирование смены месяца: жесткая проверка МЕСЯЦ(Клиенты.ДатаРождения) = МЕСЯЦ(&ДатаКонтрол) сразу отсекает дни рождения людей, родившихся в первых числах следующего месяца, даже если они попадают в нужный семидневный диапазон.
  4. Переход через границу года: если интервал попадает на конец декабря и начало января, сбрасываются и день, и месяц, что требует отдельного алгоритмического ветвления.

Способ 1. Использование функции ДОБАВИТЬКДАТЕ и параметров СКД

Выясним, как правильно рассчитать границы периода без усложнения текста запроса. Наиболее эффективный подход в СКД — перенести расчет конечной даты в параметры схемы компоновки данных, а в запросе использовать стандартную проверку.

В схеме компоновки данных настроим параметры следующим образом:

  1. Создадим параметр &ДатаКонтрол, который будет доступен пользователю для выбора (или заполняться текущей датой по умолчанию).
  2. Создадим служебный параметр &ДатаКонтролКонец, включим для него флаг Ограничение доступности (чтобы скрыть от пользователя) и в поле Выражение пропишем вычисление: КонецПериода(ДобавитьКДате(&ДатаКонтрол, "ДЕНЬ", 7), "ДЕНЬ"). Функция КонецПериода() критически важна, так как тип Дата содержит время, и без захвата конца дня (23:59:59) события последнего дня интервала будут потеряны.

Если требуется выборка документов за реальный период, используется условие Клиенты.ДатаРождения МЕЖДУ &ДатаКонтрол И &ДатаКонтролКонец. Однако с днями рождения такой фильтр напрямую работать не будет, так как год рождения клиента не совпадает с текущим годом. Для сопоставления дней рождения без учета года воспользуемся следующими способами.

Способ 2. Сравнение числовых кодов дня (MMDD)

Проанализируем универсальный математический способ, который исключает зависимость от конкретного года. Преобразуем дату рождения и границы диапазона в числовой код формата Месяц * 100 + День (например, 15 мая превращается в 5 * 100 + 15 = 515).

Настроим в СКД вычисляемые параметры:

  1. Параметр &КодНачала с выражением: Месяц(&ДатаКонтрол) * 100 + День(&ДатаКонтрол).
  2. Параметр &КодОкончания с выражением: Месяц(ДобавитьКДате(&ДатаКонтрол, "ДЕНЬ", 7)) * 100 + День(ДобавитьКДате(&ДатаКонтрол, "ДЕНЬ", 7)).

Посмотрим на текст запроса с обработкой перехода через границу года (декабрь — январь):


ВЫБРАТЬ
    Клиенты.Ссылка КАК Клиент,
    Клиенты.ДатаРождения КАК ДатаДняРождения,
    Клиенты.Ответственный КАК Ответственный
ИЗ
    Справочник.Клиенты КАК Клиенты
ГДЕ
    Клиенты.ДатаРождения <> ДАТАВРЕМЯ(1, 1, 1)
    И ВЫБОР
        КОГДА &КодНачала <= &КодОкончания ТОГДА
            МЕСЯЦ(Клиенты.ДатаРождения) * 100 + ДЕНЬ(Клиенты.ДатаРождения) МЕЖДУ &КодНачала И &КодОкончания
        ИНАЧЕ
            // Случай перехода через Новый год (например, с 28 декабря по 4 января)
            МЕСЯЦ(Клиенты.ДатаРождения) * 100 + ДЕНЬ(Клиенты.ДатаРождения) >= &КодНачала
            ИЛИ МЕСЯЦ(Клиенты.ДатаРождения) * 100 + ДЕНЬ(Клиенты.ДатаРождения) <= &КодОкончания
    КОНЕЦ

Такая конструкция корректно отрабатывает как обычный переход между месяцами внутри одного года, так и смену календарного года.

Способ 3. Соединение с таблицей дат диапазона

Разберем еще один надежный подход: генерацию таблицы из 8 календарных дней интересующего периода непосредственно в запросе с последующим соединением с базой клиентов. Этот метод гарантирует корректность при любых переходах дат.


ВЫБРАТЬ
    ДОБАВИТЬКДАТЕ(&ДатаКонтрол, ДЕНЬ, 0) КАК ПериодДень
ПОМЕСТИТЬ ВТ_Период
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ ДОБАВИТЬКДАТЕ(&ДатаКонтрол, ДЕНЬ, 1)
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ ДОБАВИТЬКДАТЕ(&ДатаКонтрол, ДЕНЬ, 2)
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ ДОБАВИТЬКДАТЕ(&ДатаКонтрол, ДЕНЬ, 3)
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ ДОБАВИТЬКДАТЕ(&ДатаКонтрол, ДЕНЬ, 4)
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ ДОБАВИТЬКДАТЕ(&ДатаКонтрол, ДЕНЬ, 5)
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ ДОБАВИТЬКДАТЕ(&ДатаКонтрол, ДЕНЬ, 6)
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ ДОБАВИТЬКДАТЕ(&ДатаКонтрол, ДЕНЬ, 7)
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    Клиенты.Наименование КАК Клиент,
    Клиенты.ДатаРождения КАК ДатаДняРождения,
    Клиенты.Ответственный КАК Ответственный
ИЗ
    ВТ_Период КАК ВТ_Период
        ВНУТРЕННЕЕ СОЕДИНЕНИЕ Справочник.Клиенты КАК Клиенты
        ПО ДЕНЬ(Клиенты.ДатаРождения) = ДЕНЬ(ВТ_Период.ПериодДень)
            И МЕСЯЦ(Клиенты.ДатаРождения) = МЕСЯЦ(ВТ_Период.ПериодДень)
ГДЕ
    Клиенты.ДатаРождения <> ДАТАВРЕМЯ(1, 1, 1)

В этом решении логика полностью переносится на прямое совпадение дня и месяца каждого элемента сгенерированного диапазона, что снимает необходимость вручную рассчитывать условия переходов через край месяца или года.

Особые ситуации и производительность

При реализации подобных отчетов обратим внимание на следующие практические нюансы:

  1. Пустые значения дат: в справочнике обязательно найдутся записи с незаполненными реквизитами. Всегда добавляем условие Клиенты.ДатаРождения <> ДАТАВРЕМЯ(1, 1, 1), чтобы исключить попадание некорректных строк в выборку.
  2. Именинники 29 февраля: приведение дат к невисокосному году может приводить к ошибкам СУБД или потере именинников. Если используется приведение к фиксированному году (например, первому году нашей эры), безопаснее выбирать заведомо високосный год (например, 2000 или 2004), либо отдельно обрабатывать дату 29.02, выводя ее 28 февраля или 1 марта.
  3. Индексация и скорость работы: вызов функций ДЕНЬ(), МЕСЯЦ() или арифметических выражений над полями таблиц базы данных в условии ГДЕ или в условии соединения ПО делает невозможным использование индексов СУБД. Происходит полное сканирование таблицы (Table Scan). Для баз данных с миллионами контрагентов оптимальным решением является хранение вычисленного дня и месяца (или числа MMDD) в отдельных индексированных реквизитах справочника. Для небольших и средних справочников приведенных выше решений в СКД более чем достаточно.
← На главную