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