Как в запросе 1С выбрать последний введённый элемент справочника и почему сортировка по Ссылке не работает

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

При разработке отчетов и механизмов интеграции в 1С программисты часто сталкиваются с задачей выбора самого «свежего» элемента справочника из нескольких дублирующих записей. Типичный пример: по условию ПОДОБНО в запросе отобрано несколько статей затрат (например, «Суточные 2018» и «Суточные 2019»). Нам необходимо программно определить, какой из этих элементов был создан последним.

Среди разработчиков существует популярный миф: поскольку уникальный идентификатор (GUID) в платформе 1С генерируется на основе времени (Time-based UUID v1), то простая сортировка по полю Ссылка по возрастанию гарантирует хронологический порядок создания элементов. В рамках этой статьи мы подробно разберем, почему этот метод принципиально неверен, как устроен GUID в 1С на физическом уровне и какие существуют надежные способы решения данной задачи.

Внутреннее устройство GUID в 1С: почему Ссылка не гарантирует хронологию

Для генерации уникальных идентификаторов платформа 1С использует стандарт UUID версии 1. Конструкция этого идентификатора действительно включает в себя временную метку (timestamp), представляющую собой количество 100-наносекундных интервалов, прошедших с момента начала Григорианского календаря (15 октября 1582 года). Однако использовать его для прямой сортировки в запросе СУБД невозможно.

Давайте проанализируем структуру стандартного GUID в 1С. Он состоит из 32 шестнадцатеричных символов, разделенных дефисами, например: bdb62d89-cede-11e4-b12b-d4ae52b5e909. Данные времени распределены по группам символов следующим образом:

  1. Младшая часть времени (time_low): первые 8 символов (bdb62d89). Этот блок изменяется быстрее всего (наносекунды и микросекунды).
  2. Средняя часть времени (time_mid): следующие 4 символа (cede). Изменяется примерно каждые 7 минут.
  3. Старшая часть времени и версия (time_hi_and_version): следующие 4 символа (11e4). Этот блок меняется медленнее всего (раз в несколько лет). Первая цифра (в данном случае 1) указывает на версию UUID.

Когда СУБД (например, MS SQL Server или PostgreSQL) выполняет сортировку по полю Ссылка (которое на уровне базы данных хранится в бинарном формате binary(16)), она сравнивает байты последовательно слева направо. Поскольку в самом начале строки находятся самые быстроменяющиеся наносекунды (time_low), элемент, созданный на долю секунды позже, может получить значение первого байта меньше, чем созданный ранее. В результате сортировки по ссылке хронологический порядок полностью перемешивается.

Иллюзия монотонности: почему тесты иногда «работают»

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

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

Однако эта монотонность мгновенно нарушается при наступлении любого из следующих условий:

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

Способ 1. Добавление реквизита даты создания (Рекомендуемый)

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

Выясним алгоритм реализации этого подхода по шагам:

  1. Добавим в нужный справочник новый реквизит, например, с именем ДатаСоздания (Тип: Дата, состав даты: Дата и время). Для ускорения запросов обязательно установим для этого реквизита свойство Индексировать.
  2. В модуле объекта справочника пропишем заполнение этого реквизита в обработчике события ПередЗаписью:

Процедура ПередЗаписью(Отказ)
	
	// Заполняем дату создания только для новых элементов
	Если ЭтоНовый() Тогда
		ДатаСоздания = ТекущаяДатаСеанса();
	КонецЕсли;
	
КонецПроцедуры

Теперь мы можем написать простой и эффективный запрос, который гарантированно вернет последний введенный элемент по условию соответствия наименования:


ВЫБРАТЬ РАЗРЕШЕННЫЕ ПЕРВЫЕ 1
	СтатьиЗатрат.Ссылка КАК Ссылка,
	СтатьиЗатрат.Наименование КАК Наименование,
	СтатьиЗатрат.ДатаСоздания КАК ДатаСоздания
ИЗ
	Справочник.СтатьиЗатрат КАК СтатьиЗатрат
ГДЕ
	СтатьиЗатрат.Наименование ПОДОБНО &ШаблонПоиска
УПОРЯДОЧИТЬ ПО
	ДатаСоздания УБЫВ

Способ 2. Использование подсистемы «Версионирование объектов» БСП

Если в вашей конфигурации используется Библиотека Стандартных Подсистем (БСП) и для исследуемого справочника включено версионирование, мы можем получить дату создания объекта без изменения метаданных справочника.

Каждая запись или изменение версионируемого объекта фиксируется в регистре сведений ВерсииОбъектов. Самая первая версия объекта по времени как раз соответствует моменту его создания. Рассмотрим, как составить запрос к этому регистру:


ВЫБРАТЬ
	СтатьиЗатрат.Ссылка КАК Ссылка
ПОМЕСТИТЬ ВТ_Статьи
ИЗ
	Справочник.СтатьиЗатрат КАК СтатьиЗатрат
ГДЕ
	СтатьиЗатрат.Наименование ПОДОБНО &ШаблонПоиска
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ РАЗРЕШЕННЫЕ ПЕРВЫЕ 1
	ВТ_Статьи.Ссылка КАК Ссылка,
	МИНИМУМ(ВерсииОбъектов.ДатаВерсии) КАК ДатаСоздания
ИЗ
	ВТ_Статьи КАК ВТ_Статьи
	ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрСведений.ВерсииОбъектов КАК ВерсииОбъектов
		ПО ВТ_Статьи.Ссылка = ВерсииОбъектов.Объект
ГРУППИРОВАТЬ ПО
	ВТ_Статьи.Ссылка
УПОРЯДОЧИТЬ ПО
	ДатаСоздания УБЫВ

Этот способ избавляет от необходимости добавлять новые реквизиты, но работает медленнее первого метода из-за необходимости соединения с большой таблицей регистра версий.

Способ 3. Получение даты создания из GUID программным путем

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

Как мы выяснили ранее, в GUID «зашито» точное UTC-время. Чтобы восстановить его, нам нужно переставить блоки символов GUID в обратном порядке (из формата time_low-time_mid-time_hi в хронологический time_hi + time_mid + time_low) и перевести полученное шестнадцатеричное число в системную дату.

Проанализируем пример функции на языке 1С, которая извлекает дату создания из ссылки:


Функция ПолучитьДатуСозданияИзСсылки(Ссылка) Экспорт
	
	УникальныйИд = Строка(Ссылка.УникальныйИдентификатор());
	
	// Выделяем сегменты времени из GUID
	СтрокаМладшаяЧасть = Сред(УникальныйИд, 1, 8);
	СтрокаСредняяЧасть = Сред(УникальныйИд, 10, 4);
	СтрокаСтаршаяЧасть = Сред(УникальныйИд, 15, 4);
	
	// Отрезаем первый символ версии (обычно "1") из старшей части
	СтрокаСтаршаяЧастьБезВерсии = Сред(СтрокаСтаршаяЧасть, 2, 3);
	
	// Собираем хронологическую шестнадцатеричную строку
	ШестнадцатеричноеВремя = СтрокаСтаршаяЧастьБезВерсии + СтрокаСредняяЧасть + СтрокаМладшаяЧасть;
	
	// Преобразуем Hex в десятичное число (100-наносекундные интервалы)
	ЧислоИнтервалов = 0;
	Для Позиция = 1 По СтрДлина(ШестнадцатеричноеВремя) Цикл
		Символ = Сред(ШестнадцатеричноеВремя, Позиция, 1);
		ЗначениеСимвола = Найти("0123456789abcdef", Символ) - 1;
		ЧислоИнтервалов = ЧислоИнтервалов * 16 + ЗначениеСимвола;
	КонецЦикла;
	
	// Переводим интервалы в секунды (деление на 10 000 000)
	КоличествоСекунд = ЧислоИнтервалов / 10000000;
	
	// Базовая дата Григорианского календаря: 15.10.1582
	БазоваяДата = Дата(1582, 10, 15, 0, 0, 0);
	ДатаСозданияUTC = БазоваяДата + КоличествоСекунд;
	
	// Прибавляем смещение часового пояса для получения локального времени
	Возврат ТекущийЧасовойПояс() + ДатаСозданияUTC;

КонецФункции

Важное ограничение метода: Данный способ будет работать корректно только для тех объектов, ссылки которых были сгенерированы самой платформой 1С штатным образом. Если GUID элемента был задан вручную (например, методом УстановитьСсылкуНового(), загружен через внешние XML/JSON-файлы или создан как случайный UUID версии 4), то извлеченная дата окажется неверной.

← На главную