В практике разработки на платформе 1С:Предприятие часто возникают ситуации, когда данные хранятся в непериодических регистрах сведений, но логика задачи требует получения записи, актуальной на определенный момент времени. В отличие от периодических регистров, у которых есть виртуальная таблица СрезПоследних, для непериодических регистров такую выборку приходится формировать вручную с помощью механизмов языка запросов. Разберем подробно, как реализовать этот алгоритм максимально эффективно.
Представим типичную ситуацию: у нас есть две таблицы. Первая таблица (например, список ИТ-платформ) содержит дату релиза. Вторая таблица (регистр сведений «Ответственные лица») хранит информацию о том, какой сотрудник был назначен ответственным с определенной даты. Нам необходимо для каждой платформы определить, кто был ответственным на момент её релиза. То есть, нам нужно найти запись в регистре ответственных, где ДатаПриема максимально близка к ДатеРелиза платформы, но не превышает её.
Проанализируем основные сложности: простое соединение по условию ДатаРелиза >= ДатаПриема выдаст нам все записи, которые были созданы до релиза, а не только последнюю. Поэтому решение задачи всегда состоит из нескольких этапов фильтрации и группировки данных.
Рассмотрим наиболее надежный и классический способ решения этой задачи, который часто называют «ручным срезом последних». Мы разделим логику на три последовательных шага, используя временные таблицы.
Шаг 1: Первичное соединение данных
На этом этапе мы выбираем все записи из основной таблицы и соединяем их с регистром сведений по условию неравенства. Мы должны получить все возможные варианты назначений, которые теоретически могли действовать на дату события. Посмотрим на пример кода:
ВЫБРАТЬ
Платформа.Название КАК НазваниеПлатформы,
Платформа.ДатаРелиза КАК ДатаРелиза,
ОтветственныеЛица.ФИО КАК ФИО,
ОтветственныеЛица.ДатаПриема КАК ДатаПриема
ПОМЕСТИТЬ Вт_ВсеВозможныеВарианты
ИЗ
РегистрСведений.Платформа КАК Платформа
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ОтветственныеЛица КАК ОтветственныеЛица
ПО Платформа.ДатаРелиза >= ОтветственныеЛица.ДатаПриема
;
Шаг 2: Поиск максимально близкой даты
Теперь, когда у нас есть «раздутая» таблица со всеми предшествующими датами, нам нужно оставить для каждой платформы только одну — самую позднюю (максимальную). Для этого мы используем группировку по ключевым полям объекта и агрегатную функцию МАКСИМУМ для даты. Проанализируем ситуацию: если мы сгруппируем только по названию платформы, мы потеряем информацию о том, какой именно сотрудник соответствует этой дате. Поэтому на данном шаге мы получаем только пары «Объект — Максимальная Дата».
ВЫБРАТЬ
Вт_ВсеВозможныеВарианты.НазваниеПлатформы КАК НазваниеПлатформы,
МАКСИМУМ(Вт_ВсеВозможныеВарианты.ДатаПриема) КАК МаксимальнаяДатаПриема
ПОМЕСТИТЬ Вт_МаксимальныеДаты
ИЗ
Вт_ВсеВозможныеВарианты КАК Вт_ВсеВозможныеВарианты
СГРУППИРОВАТЬ ПО
Вт_ВсеВозможныеВарианты.НазваниеПлатформы
;
Шаг 3: Получение итоговых данных
На финальном этапе мы соединяем нашу таблицу максимальных дат обратно с исходным регистром или первой временной таблицей, чтобы «подтянуть» остальные реквизиты (в нашем случае — ФИО сотрудника), соответствующие этой конкретной максимальной дате.
ВЫБРАТЬ
Вт_МаксимальныеДаты.НазваниеПлатформы КАК НазваниеПлатформы,
ОтветственныеЛица.ФИО КАК ФИО,
Вт_МаксимальныеДаты.МаксимальнаяДатаПриема КАК ДатаПриема
ИЗ
Вт_МаксимальныеДаты КАК Вт_МаксимальныеДаты
ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрСведений.ОтветственныеЛица КАК ОтветственныеЛица
ПО Вт_МаксимальныеДаты.МаксимальнаяДатаПриема = ОтветственныеЛица.ДатаПриема
// Если в регистре есть другие измерения, их также нужно включить в соединение
;
Если данных не слишком много, процесс можно сократить до двух временных таблиц, объединив первый и второй шаги. Это сделает код более лаконичным без потери читаемости. Выясним, как это выглядит на практике:
// 1. Сразу находим нужную дату для каждой платформы
ВЫБРАТЬ
Платформа.Название КАК Название,
МАКСИМУМ(ОтветственныеЛица.ДатаПриема) КАК ДатаПриема
ПОМЕСТИТЬ Вт_Срез
ИЗ
РегистрСведений.Платформа КАК Платформа
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ОтветственныеЛица КАК ОтветственныеЛица
ПО Платформа.ДатаРелиза >= ОтветственныеЛица.ДатаПриема
СГРУППИРОВАТЬ ПО
Платформа.Название
;
// 2. Получаем результат, соединяя дату с данными регистра
ВЫБРАТЬ
Вт_Срез.Название КАК НазваниеПлатформы,
ОтветственныеЛица.ФИО КАК ФИО,
Вт_Срез.ДатаПриема КАК ДатаПриема
ИЗ
Вт_Срез КАК Вт_Срез
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ОтветственныеЛица КАК ОтветственныеЛица
ПО Вт_Срез.ДатаПриема = ОтветственныеЛица.ДатаПриема
;
При использовании соединений по неравенству (>= или <=) важно помнить о производительности системы. Рассмотрим критические моменты, которые помогут избежать зависаний сервера при работе с большими объемами данных:
ДатаПриема), обязательно должно быть индексировано. В непериодических регистрах это достигается установкой свойства «Индексировать» у измерения или ресурса. Без индекса СУБД будет вынуждена выполнять полное сканирование таблицы, что крайне медленно.ДатаПриема примет значение NULL. Рекомендуем обрабатывать такие случаи функцией ЕСТЬNULL(), чтобы избежать пустых значений в итоговых отчетах.В некоторых случаях программисты используют подзапросы в блоке ГДЕ. Хотя для платформы 1С это считается менее предпочтительным, чем временные таблицы, такой подход иногда выглядит компактнее. Мы выбираем запись из первой таблицы и в условии прописываем, что дата из второй таблицы должна быть равна максимальной дате, полученной во вложенном запросе. Однако помните: временные таблицы — это стандарт «золотого сечения» в 1С, так как они позволяют СУБД более эффективно строить план выполнения запроса.
Подводя итог, можно сказать, что работа с непериодическими регистрами в разрезе дат требует дисциплинированного подхода к структуре запроса. Использование связки «Соединение по неравенству — Группировка МАКСИМУМ — Соединение по результату» является наиболее прозрачным и контролируемым способом получения корректных данных.