Как пронумеровать строки в запросе 1С по нескольким полям без автономера?

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

В практике разработки на платформе 1С нередко возникает задача сформировать порядковые номера записей непосредственно внутри запроса. Ситуация усложняется, когда сортировка выполняется по нескольким разнотипным полям (например, строка, дата и число), а использование конструкции АвтономерЗаписи невозможно из-за требований к структуре запроса или условий задачи. Разберем по шагам, как решить эту проблему с помощью языка запросов и встроенного языка платформы.

Способ 1. Каскадное самосоединение по нескольким полям (классический подход)

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

Когда сортировка выполняется по нескольким колонкам (в нашем примере: Наименование, Дата, Код), условие предшествования одной строки другой описывается каскадом логических операторов И и ИЛИ без преобразования типов данных. Рассмотрим текст запроса:


ВЫБРАТЬ
	"Облако" КАК Наименование,
	ДАТАВРЕМЯ(2022, 2, 1) КАК Дата,
	123 КАК Код
ПОМЕСТИТЬ ТаблицаДанных

ОБЪЕДИНИТЬ ВСЕ

ВЫБРАТЬ
	"Аквамарин",
	ДАТАВРЕМЯ(2022, 2, 2),
	100

ОБЪЕДИНИТЬ ВСЕ

ВЫБРАТЬ
	"Пробел",
	ДАТАВРЕМЯ(2022, 1, 7),
	20

ОБЪЕДИНИТЬ ВСЕ

ВЫБРАТЬ
	"Машина",
	ДАТАВРЕМЯ(2022, 1, 10),
	15

ОБЪЕДИНИТЬ ВСЕ

ВЫБРАТЬ
	"Строка",
	ДАТАВРЕМЯ(2022, 1, 5),
	10

ОБЪЕДИНИТЬ ВСЕ

ВЫБРАТЬ
	"Пробел",
	ДАТАВРЕМЯ(2022, 1, 7),
	100

ОБЪЕДИНИТЬ ВСЕ

ВЫБРАТЬ
	"Статика",
	ДАТАВРЕМЯ(2022, 1, 15),
	87

ОБЪЕДИНИТЬ ВСЕ

ВЫБРАТЬ
	"Интер",
	ДАТАВРЕМЯ(2022, 2, 5),
	50

ОБЪЕДИНИТЬ ВСЕ

ВЫБРАТЬ
	"Интер",
	ДАТАВРЕМЯ(2022, 2, 7),
	34
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
	Т1.Наименование КАК Наименование,
	Т1.Дата КАК Дата,
	Т1.Код КАК Код,
	КОЛИЧЕСТВО(Т2.Код) КАК НомерСтроки
ИЗ
	ТаблицаДанных КАК Т1
		ЛЕВОЕ СОЕДИНЕНИЕ ТаблицаДанных КАК Т2
		ПО (Т1.Наименование > Т2.Наименование)
			ИЛИ (Т1.Наименование = Т2.Наименование И Т1.Дата > Т2.Дата)
			ИЛИ (Т1.Наименование = Т2.Наименование И Т1.Дата = Т2.Дата И Т1.Код >= Т2.Код)

СГРУППИРОВАТЬ ПО
	Т1.Наименование,
	Т1.Дата,
	Т1.Код

УПОРЯДОЧИТЬ ПО
	Наименование,
	Дата,
	Код

Разберем подробнее, почему этот способ является наиболее надежным:

  1. Корректность типов: даты и числа сравниваются по своим нативным правилам СУБД, а не как строки. Исключаются ошибки сопоставления разрядов и форматов.
  2. Универсальность: данный запрос поддерживается абсолютно всеми версиями платформы 1С:Предприятие 8 вне зависимости от установленного режима совместимости.
  3. Точность подсчета: если строки в источнике гарантированно уникальны, достаточно стандартного агрегата КОЛИЧЕСТВО(Т2.Код) без использования ресурсоемкого модификатора РАЗЛИЧНЫЕ.

Способ 2. Нумерация через формирование составного строкового ключа

Рассмотрим альтернативное решение, предложенное участниками обсуждения. Идея состоит в том, чтобы склеить все поля сортировки в единую текстовую строку фиксированного формата во временной таблице, после чего выполнить левое соединение по нестрогому неравенству ВТПоля.Ключ >= ВТПоля1.Ключ:


ВЫБРАТЬ
	ТаблицаДанных.Наименование КАК Наименование,
	ТаблицаДанных.Дата КАК Дата,
	ТаблицаДанных.Код КАК Код,
	ТаблицаДанных.Наименование + СТРОКА(ТаблицаДанных.Дата) + ВЫБОР
		КОГДА ДЛИНАСТРОКИ(СТРОКА(ТаблицаДанных.Код)) = 1
			ТОГДА "0000" + СТРОКА(ТаблицаДанных.Код)
		КОГДА ДЛИНАСТРОКИ(СТРОКА(ТаблицаДанных.Код)) = 2
			ТОГДА "000" + СТРОКА(ТаблицаДанных.Код)
		КОГДА ДЛИНАСТРОКИ(СТРОКА(ТаблицаДанных.Код)) = 3
			ТОГДА "00" + СТРОКА(ТаблицаДанных.Код)
		КОГДА ДЛИНАСТРОКИ(СТРОКА(ТаблицаДанных.Код)) = 4
			ТОГДА "0" + СТРОКА(ТаблицаДанных.Код)
	КОНЕЦ КАК Ключ
ПОМЕСТИТЬ ВТПоля
ИЗ
	ТаблицаДанных КАК ТаблицаДанных
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
	ВТПоля.Наименование КАК Наименование,
	ВТПоля.Дата КАК Дата,
	ВТПоля.Код КАК Код,
	КОЛИЧЕСТВО(РАЗЛИЧНЫЕ ВТПоля1.Ключ) КАК Номер
ИЗ
	ВТПоля КАК ВТПоля
		ЛЕВОЕ СОЕДИНЕНИЕ ВТПоля КАК ВТПоля1
		ПО (ВТПоля.Ключ >= ВТПоля1.Ключ)

СГРУППИРОВАТЬ ПО
	ВТПоля.Наименование,
	ВТПоля.Дата,
	ВТПоля.Код

УПОРЯДОЧИТЬ ПО
	Наименование,
	Дата,
	Код

Выясним причину, почему данный способ требует повышенной осторожности и на практике часто приводит к скрытым ошибкам:

  1. Искажение хронологии дат: функция СТРОКА(Дата) преобразует значение в формат с днем на первом месте (например, «01.02.2022»). При алфавитном сравнении строка «01.02.2022» окажется меньше, чем «07.01.2022», что полностью сломает хронологическую сортировку.
  2. Требования к версии платформы: применение функции СТРОКА() для приведения типов в блоке ВЫБРАТЬ доступно только начиная с релиза 8.3.20 (при отключенном режиме совместимости).
  3. Ограничение разрядности чисел: ручное дополнение нулями через ДЛИНАСТРОКИ жестко зашито под фиксированное число знаков. Если в коде окажется больше знаков, сортировка нарушится.

Ограничение производительности самосоединения

Важно помнить, что любые способы нумерации внутри запроса через соединение таблицы с самой собой по условиям >= имеют алгоритмическую сложность O(N²). На таблице из 100 строк СУБД обработает порядка 10 000 сравнений, однако на выборке в 50 000 строк объем соединяемых данных превысит миллиарды записей, что приведет к переполнению диска и зависанию сеанса. Поэтому в реальных рабочих конфигурациях нумерацию запросом применяют только к небольшим порциям данных.

Альтернативные способы нумерации строк

Если нет жесткого требования формировать номер исключительно средствами SQL-запроса, проанализируем гораздо более производительные штатные инструменты:

  1. Система компоновки данных (СКД): если данные предназначены для вывода пользователю, в схеме компоновки достаточно задействовать системные поля НомерПоПорядку или НомерПоПорядкуВГруппировке(). В этом случае нумерация выполняется линейно ядром компоновки без создания тяжелых запросов.
  2. Обход выборки на встроенном языке: при выводе в табличный документ или обработке данных в коде заводится простой счетчик:
    
    Выборка = Запрос.Выполнить().Выбрать();
    Номер = 0;
    Пока Выборка.Следующий() Цикл
    	Номер = Номер + 1;
    	// Использование переменной Номер
    КонецЦикла;
    
  3. Заполнение в объекте ТаблицаЗначений: если результат выгружен в память, добавляется колонка и заполняется простым циклом:
    
    ТЗ = Запрос.Выполнить().Выгрузить();
    ТЗ.Колонки.Добавить("НомерСтроки", Новый ОписаниеТипов("Число"));
    Для Индекс = 0 По ТЗ.Количество() - 1 Цикл
    	ТЗ[Индекс].НомерСтроки = Индекс + 1;
    КонецЦикла;
    
← На главную