В современной разработке на платформе 1С (начиная с версии 8.3.13) задача нумерации строк решается элегантно с помощью оконных функций, таких как ПОРЯДКОВЫЙ_НОМЕР(). Однако перед многими программистами до сих пор стоит задача поддержки старых конфигураций или работа на устаревших версиях платформы, где встроенных средств нумерации внутри языка запросов попросту нет. Рассмотрим подробнее, как можно обойти это ограничение, разберем популярные методики и проанализируем их производительность.
Это классический способ имитации функции ROW_NUMBER(). Основная идея заключается в том, чтобы для каждой записи подсчитать количество записей, которые «меньше» её согласно определенному критерию сортировки. Проанализируем ситуацию, когда у нас есть два поля: Количество (может повторяться) и Артикул (уникальная строка).
Чтобы нумерация была стабильной и однозначной, нам необходимо обеспечить строгий порядок. Если мы будем сравнивать только по полю Количество, то при одинаковых значениях веса записей запрос не сможет их различить. Поэтому мы используем составное условие: сначала сравниваем по основному полю, а при равенстве — по уникальному идентификатору (в нашем случае это Артикул).
Разберем по шагам реализацию этого метода:
Пример кода запроса:
// Помещаем данные во временную таблицу для индексации
ВЫБРАТЬ
ИсходныеДанные.Количество КАК Количество,
ИсходныеДанные.Артикул КАК Артикул
ПОМЕСТИТЬ ВТ_Данные
ИЗ
&ТаблицаЗначений КАК ИсходныеДанные
;
////////////////////////////////////////////////////////////////////////////////
// Выполняем нумерацию через левое соединение
ВЫБРАТЬ
Т1.Артикул КАК Артикул,
Т1.Количество КАК Количество,
КОЛИЧЕСТВО(Т2.Артикул) + 1 КАК НомерСтроки
ИЗ
ВТ_Данные КАК Т1
ЛЕВОЕ СОЕДИНЕНИЕ ВТ_Данные КАК Т2
ПО Т1.Количество < Т2.Количество
ИЛИ (Т1.Количество = Т2.Количество И Т1.Артикул < Т2.Артикул)
СГРУППИРОВАТЬ ПО
Т1.Артикул,
Т1.Количество
УПОРЯДОЧИТЬ ПО
НомерСтроки
Важный момент: данный метод имеет вычислительную сложность O(n²). Если в вашей таблице 10 000 записей, серверу базы данных придется выполнить колоссальное количество сравнений. Для больших наборов данных этот способ категорически не рекомендуется.
Если ваша задача — вывести пронумерованный список в отчет или программно получить таблицу с номерами, лучшим решением будет использование механизмов СКД. В Системе Компоновки Данных нумерация реализована на уровне процессора вывода, что практически не влияет на скорость выполнения самого запроса.
Посмотрим на пример настройки:
№ п/п (Порядковый номер).Преимущество СКД в том, что она корректно обрабатывает группировки и итоги, позволяя использовать поле Порядковый номер в группе, что штатными средствами SQL-запроса в 1С реализовать крайне сложно без оконных функций.
Часто нумерация нужна именно для того, чтобы отобрать «Топ-3 продаж по каждой категории». В 1С выясним причину, по которой обычный вложенный запрос с ВЫБРАТЬ ПЕРВЫЕ не работает: платформа не поддерживает коррелированные подзапросы (подзапросы, ссылающиеся на поля внешнего запроса).
Однако, если количество групп невелико, можно использовать конструкцию ОБЪЕДИНИТЬ ВСЕ:
ВЫБРАТЬ ПЕРВЫЕ 3
Продажи.Номенклатура,
Продажи.Сумма
ИЗ
РегистрНакопления.Продажи КАК Продажи
ГДЕ
Продажи.Категория = &Категория1
УПОРЯДОЧИТЬ ПО
Продажи.Сумма УБЫВ
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ ПЕРВЫЕ 3
Продажи.Номенклатура,
Продажи.Сумма
ИЗ
РегистрНакопления.Продажи КАК Продажи
ГДЕ
Продажи.Категория = &Категория2
УПОРЯДОЧИТЬ ПО
Продажи.Сумма УБЫВ
Если групп много, этот текст запроса придется формировать динамически в коде на языке 1С. Это гораздо быстрее «селф-джойна» из первого метода.
Рассмотрим подробнее самый производительный и простой способ для больших объемов данных на старых платформах. Иногда эффективнее выполнить простой запрос без нумерации, выгрузить его в ТаблицаЗначений и проставить номера строк обычным циклом.
РезультатЗапроса = Запрос.Выполнить();
ТаблицаРезультат = РезультатЗапроса.Выгрузить();
ТаблицаРезультат.Колонки.Добавить("НомерСтроки", Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(10)));
Счетчик = 0;
Для Каждого СтрокаТЗ Из ТаблицаРезультат Цикл
Счетчик = Счетчик + 1;
СтрокаТЗ.НомерСтроки = Счетчик;
КонецЦикла;
Проанализируем этот подход: он работает за линейное время O(n). Если вам не нужно использовать этот номер для дальнейших соединений внутри этого же запроса, то программная нумерация — это «золотой стандарт» для версий платформы ниже 8.3.13.
Выбор метода зависит от контекста задачи:
Помните, что использование сложных соединений для нумерации (как в Методе 1) сильно нагружает tempdb на SQL-сервере. Всегда индексируйте временные таблицы по полям, участвующим в условиях связи Т1.Количество < Т2.Количество, чтобы минимизировать время выполнения.