Как в запросе 1С получить данные из непериодического регистра на наиболее близкую дату?

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

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

Постановка задачи и анализ структуры данных

Представим типичную ситуацию: у нас есть две таблицы. Первая таблица (например, список ИТ-платформ) содержит дату релиза. Вторая таблица (регистр сведений «Ответственные лица») хранит информацию о том, какой сотрудник был назначен ответственным с определенной даты. Нам необходимо для каждой платформы определить, кто был ответственным на момент её релиза. То есть, нам нужно найти запись в регистре ответственных, где ДатаПриема максимально близка к ДатеРелиза платформы, но не превышает её.

Проанализируем основные сложности: простое соединение по условию ДатаРелиза >= ДатаПриема выдаст нам все записи, которые были созданы до релиза, а не только последнюю. Поэтому решение задачи всегда состоит из нескольких этапов фильтрации и группировки данных.

Метод временных таблиц: пошаговый алгоритм

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

Шаг 1: Первичное соединение данных

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


ВЫБРАТЬ
    Платформа.Название КАК НазваниеПлатформы,
    Платформа.ДатаРелиза КАК ДатаРелиза,
    ОтветственныеЛица.ФИО КАК ФИО,
    ОтветственныеЛица.ДатаПриема КАК ДатаПриема
ПОМЕСТИТЬ Вт_ВсеВозможныеВарианты
ИЗ
    РегистрСведений.Платформа КАК Платформа
        ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ОтветственныеЛица КАК ОтветственныеЛица
        ПО Платформа.ДатаРелиза >= ОтветственныеЛица.ДатаПриема
;

Шаг 2: Поиск максимально близкой даты

Теперь, когда у нас есть «раздутая» таблица со всеми предшествующими датами, нам нужно оставить для каждой платформы только одну — самую позднюю (максимальную). Для этого мы используем группировку по ключевым полям объекта и агрегатную функцию МАКСИМУМ для даты. Проанализируем ситуацию: если мы сгруппируем только по названию платформы, мы потеряем информацию о том, какой именно сотрудник соответствует этой дате. Поэтому на данном шаге мы получаем только пары «Объект — Максимальная Дата».


ВЫБРАТЬ
    Вт_ВсеВозможныеВарианты.НазваниеПлатформы КАК НазваниеПлатформы,
    МАКСИМУМ(Вт_ВсеВозможныеВарианты.ДатаПриема) КАК МаксимальнаяДатаПриема
ПОМЕСТИТЬ Вт_МаксимальныеДаты
ИЗ
    Вт_ВсеВозможныеВарианты КАК Вт_ВсеВозможныеВарианты
СГРУППИРОВАТЬ ПО
    Вт_ВсеВозможныеВарианты.НазваниеПлатформы
;

Шаг 3: Получение итоговых данных

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


ВЫБРАТЬ
    Вт_МаксимальныеДаты.НазваниеПлатформы КАК НазваниеПлатформы,
    ОтветственныеЛица.ФИО КАК ФИО,
    Вт_МаксимальныеДаты.МаксимальнаяДатаПриема КАК ДатаПриема
ИЗ
    Вт_МаксимальныеДаты КАК Вт_МаксимальныеДаты
        ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрСведений.ОтветственныеЛица КАК ОтветственныеЛица
        ПО Вт_МаксимальныеДаты.МаксимальнаяДатаПриема = ОтветственныеЛица.ДатаПриема
        // Если в регистре есть другие измерения, их также нужно включить в соединение
;

Оптимизированный вариант запроса

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


// 1. Сразу находим нужную дату для каждой платформы
ВЫБРАТЬ
    Платформа.Название КАК Название,
    МАКСИМУМ(ОтветственныеЛица.ДатаПриема) КАК ДатаПриема
ПОМЕСТИТЬ Вт_Срез
ИЗ
    РегистрСведений.Платформа КАК Платформа
        ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ОтветственныеЛица КАК ОтветственныеЛица
        ПО Платформа.ДатаРелиза >= ОтветственныеЛица.ДатаПриема
СГРУППИРОВАТЬ ПО
    Платформа.Название
;

// 2. Получаем результат, соединяя дату с данными регистра
ВЫБРАТЬ
    Вт_Срез.Название КАК НазваниеПлатформы,
    ОтветственныеЛица.ФИО КАК ФИО,
    Вт_Срез.ДатаПриема КАК ДатаПриема
ИЗ
    Вт_Срез КАК Вт_Срез
        ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ОтветственныеЛица КАК ОтветственныеЛица
        ПО Вт_Срез.ДатаПриема = ОтветственныеЛица.ДатаПриема
;

Важные технические нюансы и производительность

При использовании соединений по неравенству (>= или <=) важно помнить о производительности системы. Рассмотрим критические моменты, которые помогут избежать зависаний сервера при работе с большими объемами данных:

  1. Индексирование полей: Поле, по которому происходит сравнение (в нашем примере ДатаПриема), обязательно должно быть индексировано. В непериодических регистрах это достигается установкой свойства «Индексировать» у измерения или ресурса. Без индекса СУБД будет вынуждена выполнять полное сканирование таблицы, что крайне медленно.
  2. Проблема «многих записей»: Если в одну и ту же секунду (или день, если время не учитывается) в регистр записано несколько строк с одинаковой датой, запрос вернет несколько строк для одного объекта. Чтобы этого избежать, необходимо добавлять в соединение дополнительные уникальные поля (например, номер строки или другой уникальный идентификатор записи).
  3. Работа с NULL: Если для какой-то платформы в регистре вообще нет подходящих дат (все даты в регистре позже даты релиза), то после левого соединения и группировки поле ДатаПриема примет значение NULL. Рекомендуем обрабатывать такие случаи функцией ЕСТЬNULL(), чтобы избежать пустых значений в итоговых отчетах.
  4. Преимущества периодических регистров: Если логика вашей бизнес-задачи постоянно требует получения данных «на дату», проанализируйте возможность перевода регистра из непериодического в периодический. Платформа 1С оптимизирует виртуальные таблицы срезов на уровне СУБД, создавая специальные индексы, что работает на порядок быстрее, чем ручные соединения с группировками.

Альтернатива: Коррелированные подзапросы

В некоторых случаях программисты используют подзапросы в блоке ГДЕ. Хотя для платформы 1С это считается менее предпочтительным, чем временные таблицы, такой подход иногда выглядит компактнее. Мы выбираем запись из первой таблицы и в условии прописываем, что дата из второй таблицы должна быть равна максимальной дате, полученной во вложенном запросе. Однако помните: временные таблицы — это стандарт «золотого сечения» в 1С, так как они позволяют СУБД более эффективно строить план выполнения запроса.

Подводя итог, можно сказать, что работа с непериодическими регистрами в разрезе дат требует дисциплинированного подхода к структуре запроса. Использование связки «Соединение по неравенству — Группировка МАКСИМУМ — Соединение по результату» является наиболее прозрачным и контролируемым способом получения корректных данных.

← На главную