Как создать отчет по дням рождения пациентов в заданном периоде без учета года в 1С

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

При разработке медицинских или CRM-систем часто возникает задача: вывести список пациентов, у которых день рождения выпадает на определенный период (например, на текущую неделю или месяц). Основная сложность заключается в том, что в базе данных хранится полная дата рождения с учетом года, а пользователю нужно найти совпадения только по месяцу и дню. В этой статье мы подробно разберем, как реализовать такой отчет на системе компоновки данных (СКД), используя только язык запросов 1С.

Постановка задачи и анализ проблемы

Представим ситуацию: пользователь вводит период с 25.12.2023 по 05.01.2024. Нам нужно найти всех пациентов, у которых день рождения попадает в интервал с 25 декабря по 5 января любого года. Если мы используем стандартное условие МЕЖДУ, система будет искать даты именно в указанном диапазоне лет, что нам не подходит.

Проанализируем основные технические препятствия:

  1. Разные года рождения: У одного пациента год рождения 1980, у другого — 2015. Нам нужно привести их к «общему знаменателю».
  2. Переход через год: Период может начинаться в декабре одного года и заканчиваться в январе следующего.
  3. Високосные года: Дата 29 февраля требует особого внимания, так как в обычные года она должна либо игнорироваться, либо интерпретироваться как 28 февраля или 1 марта.
  4. Ограничения языка запросов 1С: В запросе нельзя напрямую использовать конструкцию ДАТАВРЕМЯ с переменными параметрами (например, подставить текущий год в функцию), а функции работы с датами ограничены.

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

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

Проанализируем логику: мы вычитаем из даты рождения количество лет, прошедших с 1-го года. Таким образом, любая дата превращается в дату 1-го года с сохранением месяца и дня. Аналогичную операцию мы проводим с границами периода.


ВЫБРАТЬ
    ДанныеПациентов.Пациент КАК Пациент,
    ДанныеПациентов.ДатаРождения КАК ДатаРождения
ИЗ
    РегистрСведений.ДанныеПациентов КАК ДанныеПациентов
ГДЕ
    ВЫБОР
        КОГДА ГОД(&НачалоПериода) = ГОД(&КонецПериода)
            ТОГДА ДОБАВИТЬКДАТЕ(ДанныеПациентов.ДатаРождения, ГОД, -(ГОД(ДанныеПациентов.ДатаРождения) - 1)) 
                МЕЖДУ ДОБАВИТЬКДАТЕ(&НачалоПериода, ГОД, -(ГОД(&НачалоПериода) - 1)) 
                И ДОБАВИТЬКДАТЕ(&КонецПериода, ГОД, -(ГОД(&КонецПериода) - 1))
        ИНАЧЕ
            // Случай, когда период захватывает конец одного года и начало другого
            ДОБАВИТЬКДАТЕ(ДанныеПациентов.ДатаРождения, ГОД, -(ГОД(ДанныеПациентов.ДатаРождения) - 1)) 
                МЕЖДУ ДОБАВИТЬКДАТЕ(&НачалоПериода, ГОД, -(ГОД(&НачалоПериода) - 1)) 
                И ДАТАВРЕМЯ(1, 12, 31, 23, 59, 59)
            ИЛИ 
            ДОБАВИТЬКДАТЕ(ДанныеПациентов.ДатаРождения, ГОД, -(ГОД(ДанныеПациентов.ДатаРождения) - 1)) 
                МЕЖДУ ДАТАВРЕМЯ(1, 1, 1, 0, 0, 0) 
                И ДОБАВИТЬКДАТЕ(&КонецПериода, ГОД, -(ГОД(&КонецПериода) - 1))
    КОНЕЦ

Важный момент: использование конструкции -(ГОД(Дата) - 1) позволяет избежать ошибки выхода за границы допустимых дат 1С. Мы гарантированно приводим дату к 0001 году.

Вариант 2. Метод «Ближайшего дня рождения»

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

Рассмотрим пример реализации:


ВЫБРАТЬ
    ФизическиеЛица.Ссылка КАК Ссылка,
    ДОБАВИТЬКДАТЕ(ФизическиеЛица.ДатаРождения, ГОД, РАЗНОСТЬДАТ(ФизическиеЛица.ДатаРождения, &ДатаКонец, ГОД)) КАК ДеньРожденияВЭтомГоду
ИЗ
    Справочник.ФизическиеЛица КАК ФизическиеЛица
ГДЕ
    ДОБАВИТЬКДАТЕ(ФизическиеЛица.ДатаРождения, ГОД, РАЗНОСТЬДАТ(ФизическиеЛица.ДатаРождения, &ДатаКонец, ГОД)) 
        МЕЖДУ &ДатаНачало И &ДатаКонец

Этот метод хорош тем, что он автоматически обрабатывает високосные года благодаря внутренним алгоритмам функции ДОБАВИТЬКДАТЕ. Если пациент родился 29 февраля, а текущий год — обычный, платформа корректно вернет 28 февраля. Однако данный метод работает идеально только в том случае, если длина периода не превышает один год.

Вариант 3. Использование временных таблиц для генерации дат (Универсальный метод)

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

Посмотрим на алгоритм:

  1. Создаем таблицу цифр для генерации последовательности.
  2. Формируем список всех месяцев, входящих в выбранный пользователем период.
  3. Соединяем пациентов с этими месяцами по совпадению месяца рождения.
  4. Вычисляем точную дату дня рождения в каждом конкретном году периода и проверяем ее на вхождение в границы.

// Создаем последовательность чисел
ВЫБРАТЬ 0 КАК Цифра ПОМЕСТИТЬ ВТ_Цифры ОБЪЕДИНИТЬ ВЫБРАТЬ 1 ... ОБЪЕДИНИТЬ ВЫБРАТЬ 9;

// Генерируем список смещений (например, до 100 месяцев)
ВЫБРАТЬ
    Т1.Цифра + Т2.Цифра * 10 КАК Смещение
ПОМЕСТИТЬ ВТ_Смещения
ИЗ
    ВТ_Цифры КАК Т1, ВТ_Цифры КАК Т2;

// Получаем все даты начала месяцев в периоде
ВЫБРАТЬ
    ДОБАВИТЬКДАТЕ(НАЧАЛОПЕРИОДА(&НачалоПериода, МЕСЯЦ), МЕСЯЦ, ВТ_Смещения.Смещение) КАК МесяцПериода
ПОМЕСТИТЬ ВТ_Месяцы
ИЗ
    ВТ_Смещения КАК ВТ_Смещения
ГДЕ
    ДОБАВИТЬКДАТЕ(НАЧАЛОПЕРИОДА(&НачалоПериода, МЕСЯЦ), МЕСЯЦ, ВТ_Смещения.Смещение) <= &КонецПериода;

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

Преимущество: Этот метод абсолютно точен и справляется с любыми переходами через год, так как он фактически «примеряет» день рождения каждого пациента к каждому году, представленному в периоде.

Оптимизация и практические советы

Проанализируем ситуацию с производительностью. Если в вашей базе данных сотни тысяч пациентов, использование функций МЕСЯЦ() или ГОД() в условии ГДЕ приведет к полному сканированию таблицы (Full Scan), так как СУБД не сможет использовать индекс по полю ДатаРождения.

Выясним, как это можно оптимизировать:

Заключение

Мы рассмотрели несколько способов решения задачи. Для простых отчетов в СКД чаще всего достаточно Варианта 1 с нормализацией дат к первому году. Он компактен и легко вписывается в текст запроса. Если же отчет критичен к точности високосных дат и может формироваться за длительные периоды, лучше использовать Вариант 3 с временной таблицей месяцев. Выбирайте подходящий метод исходя из объема ваших данных и требований к точности.

← На главную