При разработке интерфейсов в «1С:Предприятие 8» программисты часто сталкиваются с задачей вывода пронумерованного списка данных на управляемую форму. Если данные берутся из табличной части документа или справочника, платформа предоставляет готовое системное поле НомерСтроки. Однако, если источником данных служит Регистр сведений, в нем изначально отсутствует последовательная нумерация записей. Когда этот результат запроса выгружается в таблицу значений на форме, разработчику приходится самостоятельно искать способы отображения номеров строк.
В этой статье мы подробно и по шагам разберем все доступные методы решения данной задачи. Мы проанализируем классический обход в цикле на сервере, оценим технические риски использования левых соединений в запросе, изучим возможности современной встроенной функции АВТОНОМЕРЗАПИСИ(), а также разберем особенности работы с идентификаторами строк управляемой формы.
Рассмотрим самый надежный, производительный и простой в реализации способ. Большинство ведущих разработчиков сходятся во мнении, что нумеровать строки непосредственно в запросе СУБД ради простого вывода на форму нецелесообразно. Гораздо эффективнее произвести быструю постобработку полученной таблицы значений в оперативной памяти сервера приложений перед передачей её на клиент.
Разберем этот процесс по шагам:
Число.Посмотрим на пример программного кода, реализующего этот алгоритм:
// 1. Выполняем запрос и выгружаем результат в Таблицу Значений (ТЗ)
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| КурсыВалют.Период КАК Период,
| КурсыВалют.Валюта КАК Валюта,
| КурсыВалют.Курс КАК Курс
|ИЗ
| РегистрСведений.КурсыВалют КАК КурсыВалют";
ТЗ = Запрос.Выполнить().Выгрузить();
// 2. Добавляем колонку для нумерации
ТЗ.Колонки.Добавить("НомерСтроки", Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(10, 0)));
// 3. Заполняем номера строк в цикле
Для Счетчик = 0 По ТЗ.Количество() - 1 Цикл
ТЗ[Счетчик].НомерСтроки = Счетчик + 1;
КонецЦикла;
// 4. Загружаем подготовленные данные в реквизит управляемой формы
ЗначениеВРеквизитФормы(ТЗ, "РеквизитНаФорме");
Преимущества данного метода:
Если вы работаете на современных версиях платформы «1С:Предприятие 8.3», у вас есть возможность использовать штатный механизм автоматической нумерации прямо внутри запроса. Для этого предназначена функция АВТОНОМЕРЗАПИСИ().
Выясним особенности применения этой функции. Главное правило — её нельзя использовать непосредственно в финальном выводе запроса. Она работает исключительно при помещении результатов во временную таблицу.
Проанализируем пример структуры такого запроса:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| АВТОНОМЕРЗАПИСИ() КАК НомерСтроки,
| КурсыВалют.Период КАК Период,
| КурсыВалют.Валюта КАК Валюта,
| КурсыВалют.Курс КАК Курс
|ПОМЕСТИТЬ ВТ_ПронумерованныеДанные
|ИЗ
| РегистрСведений.КурсыВалют КАК КурсыВалют
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ВТ_ПронумерованныеДанные.НомерСтроки КАК НомерСтроки,
| ВТ_ПронумерованныеДанные.Период КАК Период,
| ВТ_ПронумерованныеДанные.Валюта КАК Валюта,
| ВТ_ПронумерованныеДанные.Курс КАК Курс
|ИЗ
| ВТ_ПронумерованныеДанные КАК ВТ_ПронумерованныеДанные";
ТЗ = Запрос.Выполнить().Выгрузить();
ЗначениеВРеквизитФормы(ТЗ, "РеквизитНаФорме");
Несмотря на кажущееся удобство, использование функции АВТОНОМЕРЗАПИСИ() имеет множество подводных камней и ограничений, о которых необходимо помнить:
ПОМЕСТИТЬ во временную таблицу.АВТОНОМЕРЗАПИСИ() + 1 вызовет ошибку компиляции).ОБЪЕДИНИТЬ и ОБЪЕДИНИТЬ ВСЕ) на верхнем уровне.В классическом SQL для подобных целей применяются оконные функции вроде ROW_NUMBER() OVER (ORDER BY ...). В языке запросов 1С такой механизм отсутствует напрямую. Тем не менее, решить задачу в рамках одного запроса (без использования новых функций платформы) можно путем левого соединения исходной таблицы с самой собой по условию «больше» или «меньше».
Рассмотрим пример реализации такой логики для справочника или таблицы с уникальными идентификаторами:
ВЫБРАТЬ
Номенклатура.Ссылка КАК Ссылка
ПОМЕСТИТЬ ВТ_ИсходныеДанные
ИЗ
Справочник.Номенклатура КАК Номенклатура
ГДЕ
Номенклатура.Родитель В ИЕРАРХИИ(&Группа)
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
ИсходнаяТаблица.Ссылка КАК Ссылка,
КОЛИЧЕСТВО(РАЗЛИЧНЫЕ ТаблицаСравнения.Ссылка) + 1 КАК НомерСтроки
ИЗ
ВТ_ИсходныеДанные КАК ИсходнаяТаблица
ЛЕВОЕ СОЕДИНЕНИЕ ВТ_ИсходныеДанные КАК ТаблицаСравнения
ПО ИсходнаяТаблица.Ссылка > ТаблицаСравнения.Ссылка
СГРУППИРОВАТЬ ПО
ИсходнаяТаблица.Ссылка
УПОРЯДОЧИТЬ ПО
НомерСтроки
Почему этот способ опасен для практического применения?
Если мы работаем с Регистром сведений, у нас нет уникального поля Ссылка. Нам придется связывать таблицы по набору измерений, что сильно усложнит условие связи. Но самая большая проблема заключается в производительности СУБД.
При левом соединении таблицы «сама на себя» по неравенству СУБД строит декартово произведение строк. Вычислительная сложность такого запроса оценивается как $O(N^2)$. Если ваша таблица содержит всего 100 строк, СУБД обработает около 10 000 сопоставлений. Если же в таблице окажется 10 000 строк, серверу придется проанализировать до 50 миллионов записей. Это гарантированно перегрузит оперативную память сервера и приведет к зависанию сеанса пользователя.
Иногда у начинающих разработчиков возникает соблазн использовать системное свойство элементов коллекции формы. При загрузке таблицы значений на управляемую форму, она преобразуется в объект ДанныеФормыКоллекция. Каждая строка этой коллекции имеет метод ПолучитьИдентификатор().
Важно понимать, почему нельзя использовать идентификатор в качестве номера строки:
Подводя итог нашему анализу, сформулируем основные правила выбора метода нумерации строк:
СистемныеПоля.НомерПоПорядку.