Почему СрезПоследних в 1С возвращает несколько записей и как получить только одну последнюю?

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

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

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

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

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

Ресурсом выступает поле СтавкаОплаты.

Когда мы обращаемся к виртуальной таблице РегистрСведений.СоставГруппДетскогоУчреждения.СрезПоследних(&Период, ), платформа 1С рассчитывает срез последних в разрезе всех измерений регистра, определенных в конфигураторе. Для базы данных комбинация значений всех измерений является уникальным ключом.

Проанализируем простую ситуацию. Ребенок Иванов за время пребывания в учреждении сменил две группы:

  1. 01.09.2019 года он был зачислен в группу "Младшая группа".
  2. 01.11.2019 года его перевели в группу "Средняя группа".

Для СУБД в регистре существуют две абсолютно разные уникальные записи:

Когда мы запрашиваем СрезПоследних на дату 18.11.2019, система ищет последние записи по каждой комбинации измерений. И для комбинации с "Младшей группой" последней датой будет 01.09.2019 (она попадает в период до 18.11.2019), а для "Средней группы" — 01.11.2019. В результате виртуальная таблица вернет обе записи. При соединении этой таблицы с данными регистра бухгалтерии или справочника детей в отчете неизбежно появятся две строки на одного ребенка.

Почему популярные "простые" решения не работают

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

Разбираем правильное решение по шагам: эмуляция среза последних

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

Давайте пошагово реализуем этот алгоритм в конструкторе запросов.

Шаг 1: Получение максимальных дат для каждого объекта

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

Посмотрим на пример запроса для первого шага:


ВЫБРАТЬ
    СоставГрупп.Ребенок КАК Ребенок,
    МАКСИМУМ(СоставГрупп.Period) КАК Период
ПОМЕСТИТЬ ВТ_ПоследниеПериоды
ИЗ
    РегистрСведений.СоставГруппДетскогоУчреждения КАК СоставГрупп
ГДЕ
    СоставГрупп.Period <= &Период
СГРУППИРОВАТЬ ПО
    СоставГрупп.Ребенок
;

Шаг 2: Получение актуальных данных на срез дат

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

Запишем этот шаг в пакет запросов:


ВЫБРАТЬ
    ВТ.Ребенок КАК Ребенок,
    СоставГрупп.ГруппаДетскогоУчреждения КАК ГруппаДетскогоУчреждения,
    СоставГрупп.СтавкаОплаты КАК СтавкаОплаты
ПОМЕСТИТЬ ВТ_АктуальныйСостав
ИЗ
    ВТ_ПоследниеПериоды КАК ВТ
    ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрСведений.СоставГруппДетскогоУчреждения КАК СоставГрупп
        ПО ВТ.Ребенок = СоставГрупп.Ребенок
        И ВТ.Период = СоставГрупп.Период
;

Шаг 3: Соединение с основными данными отчета

После того как во временной таблице ВТ_АктуальныйСостав мы получили чистые данные (ровно одна актуальная строка на каждого ребенка), мы можем смело соединять ее с основным запросом — например, с движениями по бухгалтерским счетам.

Проанализируем итоговый собранный запрос:


ВЫБРАТЬ
    СоставГрупп.Ребенок КАК Ребенок,
    МАКСИМУМ(СоставГрупп.Период) КАК Период
ПОМЕСТИТЬ ВТ_ПоследниеПериоды
ИЗ
    РегистрСведений.СоставГруппДетскогоУчреждения КАК СоставГрупп
ГДЕ
    СоставГрупп.Период <= &Период
СГРУППИРОВАТЬ ПО
    СоставГрупп.Ребенок
;

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

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    ХозрасчетныйДвиженияССубконто.Период КАК Период,
    ХозрасчетныйДвиженияССубконто.Учреждение КАК Учреждение1,
    ХозрасчетныйДвиженияССубконто.Регистратор,
    ХозрасчетныйДвиженияССубконто.Активность,
    ХозрасчетныйДвиженияССубконто.СчетДт,
    ХозрасчетныйДвиженияССубконто.СубконтоДт1,
    ХозрасчетныйДвиженияССубконто.СубконтоДт2,
    ХозрасчетныйДвиженияССубконто.СубконтоДт3,
    ХозрасчетныйДвиженияССубконто.СчетКт,
    ХозрасчетныйДвиженияССубконто.СубконтоКт1,
    ХозрасчетныйДвиженияССубконто.СубконтоКт2,
    ХозрасчетныйДвиженияССубконто.Сумма,
    Дети.Ссылка КАК Ребенок,
    Дети.Учреждение КАК Учреждение,
    ВТ_АктуальныйСостав.ГруппаДетскогоУчреждения КАК ГруппаДетскогоУчреждения,
    ВТ_АктуальныйСостав.СтавкаОплаты КАК СтавкаОплаты
ИЗ
    РегистрБухгалтерии.ЕПСБУ.ДвиженияССубконто(
            ДАТАВРЕМЯ(2019, 11, 1, 0, 0, 0),
            ДАТАВРЕМЯ(2019, 11, 18, 23, 59, 59),
            Активность = ИСТИНА И СчетКт = &ВыбСчет, , ) КАК ХозрасчетныйДвиженияССубконто
        
        ВНУТРЕННЕЕ СОЕДИНЕНИЕ Справочник.Дети КАК Дети
            ПО ХозрасчетныйДвиженияССубконто.СубконтоКт2 = Дети.Контрагент
            
        ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_АктуальныйСостав КАК ВТ_АктуальныйСостав
            ПО Дети.Ссылка = ВТ_АктуальныйСостав.Ребенок
ГДЕ
    ХозрасчетныйДвиженияССубконто.Регистратор В(&Массив)

УПОРЯДОЧИТЬ ПО
    Период,
    Ребенок

Важные нюансы при совпадении периодов в одну секунду

При использовании этого алгоритма необходимо помнить об одной редкой технической коллизии. Что произойдет, если по одному ребенку в одну и ту же секунду (или в пределах одной даты, если периодичность регистра установлена "В пределах дня") будут введены две разные записи с разными группами?

В такой ситуации связь по равенству периодов ВТ.Период = СоставГрупп.Период на втором шаге пакета запросов снова вернет обе записи, так как время у них полностью совпадает.

Если специфика вашей конфигурации или бизнес-процессов допускает возможность существования записей с одинаковым периодом по одному ключевому измерению, то на втором шаге запроса рекомендуется использовать дополнительное агрегирование. Например, можно выбрать МАКСИМУМ(СоставГрупп.ГруппаДетскогоУчреждения) или, если регистр подчинен регистратору, осуществлять дополнительное соединение по максимальной позиции регистратора на оси времени или по максимальному номеру строки.

Применение описанной методики эмуляции среза последних гарантирует получение точных и непротиворечивых данных в ваших отчетах и существенно снижает нагрузку на СУБД по сравнению с неоптимальными попытками свертки данных на стороне кода «1С:Предприятия».

← На главную