Разработчики в среде «1С:Предприятие 8» часто сталкиваются со следующей проблемой: пользователи заполняют список членов комиссии в документе в определенном порядке (например, сначала председатель, затем ключевые члены комиссии, потом остальные), эти данные записываются в независимый регистр сведений, но при выводе на печать или открытии формы список оказывается полностью перемешанным. Попытки упорядочить данные программно по ссылочному полю или наименованию приводят лишь к алфавитной сортировке, что не решает задачу сохранения пользовательского порядка ввода.
В этой статье мы подробно разберем, почему СУБД и платформа 1С ведут себя подобным образом, как устроено физическое хранение данных в регистрах сведений и какими методами можно гарантировать вывод строк в том порядке, в котором их ввел пользователь, даже если конфигурация находится на поддержке.
Для начала проанализируем базовые принципы работы реляционных баз данных (таких как MS SQL Server, PostgreSQL или IBM DB2). В реляционной теории таблицы представляют собой неупорядоченные множества. Это означает, что без явного указания инструкции ORDER BY (в языке запросов 1С — УПОРЯДОЧИТЬ ПО) СУБД имеет право вернуть строки в абсолютно любом порядке.
Когда мы пишем запрос без сортировки, СУБД обычно выдает строки в порядке их физического расположения на диске или в порядке сканирования индексов. Для независимого непериодического регистра сведений платформа 1С автоматически создает в базе данных уникальный кластерный индекс. Этот индекс строится по всем измерениям регистра в той последовательности, в которой они расположены в дереве метаданных конфигуратора.
Представим структуру регистра из нашего примера:
ДокументСсылкаСправочникСсылка.ФизическиеЛицаСправочникСсылка.ДолжностиПеречислениеСсылка.РолиЧленовКомиссийКластерный индекс для такой таблицы на уровне СУБД будет состоять из полей: Объект + ЧленКомиссии + Должность + Роль. СУБД физически сортирует и хранит строки таблицы на диске именно в соответствии с этим индексом. Когда мы выбираем данные по конкретному объекту (документу), СУБД делает выборку и возвращает строки, отсортированные по следующему по порядку полю индекса — ЧленКомиссии.
Многие начинающие разработчики полагают, что сортировка по полю типа СправочникСсылка (в нашем случае ЧленКомиссии) должна происходить по наименованию (ФИО физического лица). Однако это не так. На уровне базы данных любая ссылка представляет собой 16-байтовое бинарное число — уникальный идентификатор (UUID или GUID).
Когда СУБД сортирует записи по измерению ЧленКомиссии, она сравнивает именно эти шестнадцатеричные уникальные идентификаторы. Так как UUID генерируются платформой случайным образом (или последовательно, но без привязки к алфавиту), визуально для пользователя этот порядок кажется абсолютно хаотичным. Именно поэтому физическое лицо Власова может оказаться в списке выше Антоновой — просто шестнадцатеричный код UUID Власовой оказался математически меньше, чем у Антоновой.
Вывод очевиден: полагаться на естественный порядок записей в регистре сведений нельзя. Для сохранения пользовательской последовательности строк нам требуется явное числовое поле порядка.
Рассмотрим несколько практических способов реализации строгого порядка вывода строк, начиная от классического изменения структуры данных и заканчивая обходными путями с помощью механизма расширений 1С, если основная конфигурация находится на поддержке без возможности изменения.
Если у вас есть возможность вносить изменения в структуру метаданных основной конфигурации, это самый простой и надежный способ.
НомерСтроки (или Порядок).Число, длина 3, неотрицательное.
ВЫБРАТЬ
СоставыКомиссий.ЧленКомиссии КАК ЧленКомиссии,
СоставыКомиссий.Должность КАК Должность,
СоставыКомиссий.Роль КАК Роль
ИЗ
РегистрСведений.АХДКОСоставыКомиссийДокументов КАК СоставыКомиссий
ГДЕ
СоставыКомиссий.Объект В(&МассивОбъектов)
УПОРЯДОЧИТЬ ПО
СоставыКомиссий.НомерСтроки
Что делать, если конфигурация находится на строгой поддержке, а изменения вносить запрещено? Начиная с платформы 1С 8.3, мы можем использовать расширения конфигурации. Однако напрямую добавить новое измерение в заимствованный регистр сведений в расширении нельзя. В этом случае мы можем применить паттерн «Регистр-сателлит».
Разберем этот алгоритм подробно:
Расш_ПорядокСоставаКомиссии.Объект (тип заимствован из основной конфигурации, например, ДокументСсылка.АктСписания)ЧленКомиссии (тип СправочникСсылка.ФизическиеЛица)НомерСтроки (тип Число)
ВЫБРАТЬ
АХДКОСоставыКомиссий.ЧленКомиссии КАК ЧленКомиссии,
АХДКОСоставыКомиссий.Должность КАК Должность,
АХДКОСоставыКомиссий.Роль КАК Роль
ИЗ
РегистрСведений.АХДКОСоставыКомиссийДокументов КАК АХДКОСоставыКомиссий
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.Расш_ПорядокСоставаКомиссии КАК Расш_Порядок
ПО АХДКОСоставыКомиссий.Объект = Расш_Порядок.Объект
И АХДКОСоставыКомиссий.ЧленКомиссии = Расш_Порядок.ЧленКомиссии
ГДЕ
АХДКОСоставыКомиссий.Объект В(&МассивОбъектов)
УПОРЯДОЧИТЬ ПО
ЕСТЬNULL(Расш_Порядок.НомерСтроки, 999)
Если создание дополнительного регистра кажется вам избыточным, можно воспользоваться программным методом сохранения последовательности UUID в текстовом поле документа.
Расш_ПорядокЧленовКомиссии с типом Строка (неограниченной длины).
// Код выполняется на сервере при сохранении данных формы
МассивGUID = Новый Массив;
Для Каждого СтрокаКомиссии Из ТаблицаКомиссииНаФорме Цикл
МассивGUID.Добавить(Строка(СтрокаКомиссии.ЧленКомиссии.УникальныйИдентификатор()));
КонецЦикла;
ЗаписьJSON = Новый ЗаписьJSON;
ЗаписьJSON.УстановитьСтроку();
ЗаписатьJSON(ЗаписьJSON, МассивGUID);
ОбъектДокумента.Расш_ПорядокЧленовКомиссии = ЗаписьJSON.Закрыть();
Проанализируем архитектуру решения. Использование независимого регистра сведений для хранения состава комиссии конкретного документа часто является избыточным и проектируется лишь для того, чтобы «не снимать документ с поддержки». Если у вас есть возможность использовать расширения конфигурации, гораздо более правильным архитектурным решением будет полный отказ от использования внешнего регистра сведений для хранения строчной части документа.
Выполните следующие шаги:
Расш_СоставКомиссии.ЧленКомиссии, Должность, Роль.НомерСтроки на уровне базы данных и гарантирует, что порядок строк, заданный пользователем кнопками «Переместить вверх» / «Переместить вниз», всегда сохранится без написания дополнительного кода.
ВЫБРАТЬ
СоставКомиссии.ЧленКомиссии КАК ЧленКомиссии,
СоставКомиссии.Должность КАК Должность,
СоставКомиссии.Роль КАК Роль
ИЗ
Документ.АктСписания.Расш_СоставКомиссии КАК СоставКомиссии
ГДЕ
СоставКомиссии.Ссылка В(&МассивОбъектов)
УПОРЯДОЧИТЬ ПО
СоставКомиссии.НомерСтроки
Данный подход не только решает проблему с порядком строк раз и навсегда, но и существенно ускоряет работу системы, избавляя СУБД от лишних соединений таблиц, а также упрощает транзакционную целостность данных при записи документа.