В практике разработки на платформе 1С:Предприятие часто возникает ситуация, когда стандартная виртуальная таблица СрезПоследних возвращает несколько записей по одним и тем же измерениям. Особенно остро эта проблема проявляется в периодических регистрах сведений, подчиненных регистратору. Мы можем столкнуться с тем, что в пределах одной секунды несколько разных документов делают записи по идентичному набору измерений. Давайте разберем, почему это происходит и как гарантированно получить только одну, действительно «последнюю» запись.
Для начала проанализируем, как платформа обеспечивает уникальность записей. В обычном периодическом регистре сведений с периодичностью В пределах секунды ключом уникальности является комбинация Период + Измерения. Если два разных документа попытаются записать данные с одинаковыми измерениями в одну и ту же секунду, система выдаст ошибку «Запись с такими ключевыми полями существует».
Однако ситуация меняется, если в настройках регистра установлена периодичность По позиции регистратора. В этом случае Регистратор неявно становится частью ключа уникальности. Платформа позволяет записывать данные в одну и ту же секунду разными документами, так как их «момент времени» (сочетание даты и ссылки на документ) различается. При получении среза последних через виртуальную таблицу 1С на уровне SQL пытается определить самую свежую запись, но иногда логика группировки требует дополнительного уточнения со стороны разработчика.
Рассмотрим подробнее основные способы решения этой задачи.
С выходом новых версий платформы у разработчиков появился мощный инструмент — функция АВТОНОМЕРЗАПИСИ. Она позволяет присвоить каждой строке в выборке уникальный целочисленный номер. Это особенно полезно, когда нам нужно программно имитировать срез последних через физическую таблицу регистра, когда стандартная виртуальная таблица по каким-то причинам не подходит или работает не так, как ожидается.
Разберем по шагам, как применить этот метод. Сначала мы выбираем данные во временную таблицу, добавляя автонумерацию, а затем выбираем записи с максимальным номером для каждой группы измерений.
ВЫБРАТЬ
РегистрСведений.Период КАК Период,
РегистрСведений.ЗалоговыйБилет КАК ЗалоговыйБилет,
АВТОНОМЕРЗАПИСИ() КАК НомерПП,
РегистрСведений.ВидОперации
ПОМЕСТИТЬ втВсеЗаписи
ИЗ
РегистрСведений.ЛМБ_СведенияЗалоговогоБилета КАК РегистрСведений
ГДЕ
РегистрСведений.ЗалоговыйБилет = &ЗалоговыйБилет;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
втВсеЗаписи.ЗалоговыйБилет,
втВсеЗаписи.ВидОперации
ИЗ
(ВЫБРАТЬ
МАКСИМУМ(вт.НомерПП) КАК НомерПП,
вт.ЗалоговыйБилет КАК ЗалоговыйБилет
ИЗ
втВсеЗаписи КАК вт
СГРУППИРОВАТЬ ПО
вт.ЗалоговыйБилет) КАК ВложенныйЗапрос
ВНУТРЕННЕЕ СОЕДИНЕНИЕ втВсеЗаписи КАК втВсеЗаписи
ПО ВложенныйЗапрос.ЗалоговыйБилет = втВсеЗаписи.ЗалоговыйБилет
И ВложенныйЗапрос.НомерПП = втВсеЗаписи.НомерПП
Важно понимать принцип работы: АВТОНОМЕРЗАПИСИ гарантирует стабильность результата, если в запросе определен четкий порядок. Если мы хотим, чтобы номер соответствовал хронологии, в первом запросе следует использовать УПОРЯДОЧИТЬ ПО с указанием периода и момента времени.
Если ваша версия платформы ниже 8.3.20, использование Момента Времени — это классический и надежный способ. Поле МоментВремени в языке запросов 1С для документов представляет собой комбинацию даты и ссылки, что позволяет СУБД однозначно определить последовательность документов даже внутри одной секунды.
Выясним причину, по которой обычная группировка по периоду может не срабатывать: период — это просто дата и время (секунда), а документов в этой секунде может быть несколько. Чтобы найти «самый последний» документ, нам нужно использовать МАКСИМУМ(Регистратор) или МАКСИМУМ(МоментВремени).
Проанализируем пример запроса к физической таблице для получения корректного среза:
ВЫБРАТЬ
МаксимальныеДанные.ЗалоговыйБилет,
РегистрСведений.ВидОперации
ИЗ
(ВЫБРАТЬ
Регистр.ЗалоговыйБилет КАК ЗалоговыйБилет,
МАКСИМУМ(Регистр.Период) КАК Период,
МАКСИМУМ(Регистр.Регистратор) КАК Регистратор
ИЗ
РегистрСведений.ЛМБ_СведенияЗалоговогоБилета КАК Регистр
СГРУППИРОВАТЬ ПО
Регистр.ЗалоговыйБилет) КАК МаксимальныеДанные
ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрСведений.ЛМБ_СведенияЗалоговогоБилета КАК РегистрСведений
ПО МаксимальныеДанные.ЗалоговыйБилет = РегистрСведений.ЗалоговыйБилет
И МаксимальныеДанные.Период = РегистрСведений.Период
И МаксимальныеДанные.Регистратор = РегистрСведений.Регистратор
Обратите внимание: использование МАКСИМУМ(Регистратор) работает корректно, так как ссылки в 1С при сравнении ведут себя детерминировано (сравниваются их внутренние идентификаторы GUID). Однако более идеологически правильным считается использование именно Момента Времени в условиях связи.
Иногда проблема «дублей» в срезе последних — это не техническая ошибка платформы, а следствие проектирования метаданных. Посмотрим на ситуацию с другой стороны. Если вы видите две строки по одному и тому же (как вам кажется) объекту, выполните следующие действия:
Организация или Склад), которое не учитывается в коде, и именно из-за него записи считаются разными.В пределах секунды. Это заставит платформу контролировать уникальность на уровне записи и не допустит появления «двойников».СрезПоследних работает специфически: система сначала отсекает записи, не подходящие под фильтр, а потом ищет среди оставшихся последнюю. Это может привести к тому, что вы получите «старую» запись, потому что новая не прошла по условию реквизита.Для глубокого понимания процесса полезно знать, что 1С при обращении к виртуальной таблице СрезПоследних транслирует запрос в SQL, который часто содержит оконные функции (в современных версиях) или вложенные подзапросы с TOP 1. Если в регистре включена периодичность Позиция регистратора, в SQL-запросе обязательно будут присутствовать поля _Period (дата и время) и _RecorderTRef / _RecorderRRef (тип и ссылка регистратора).
Оптимизатор SQL-запросов (будь то MS SQL или PostgreSQL) будет использовать наиболее подходящий индекс. Для того чтобы срез последних работал быстро и без ошибок, критически важно наличие индекса по измерениям, который платформа создает автоматически. Если же вы пытаетесь строить срез вручную через физическую таблицу с фильтрами по реквизитам, производительность может значительно снизиться (произойдет Index Scan вместо Index Seek).
Таким образом, для получения гарантированно единственной записи в срезе последних, мы рекомендуем либо использовать стандартную виртуальную таблицу с корректно настроенной периодичностью регистра, либо, при работе с физическими таблицами, применять комбинацию МАКСИМУМ(Период) и МАКСИМУМ(МоментВремени), а в современных конфигурациях — функцию АВТОНОМЕРЗАПИСИ.