Номер по порядку в запросе 1С: поле НомерСтроки, соединение с собой и нумерация в СКД
Содержание
В языке запросов 1С нет оконной функции вроде ROW_NUMBER() из SQL, поэтому пронумеровать записи «одной командой» нельзя. Готовый номер есть только у строк табличных частей и наборов записей регистров — это поле НомерСтроки. Для любой другой выборки номер строки получают тремя путями: соединением временной таблицы с самой собой и подсчётом, нумерацией результата в коде или функцией НомерПоПорядку() в системе компоновки данных.
Какой способ выбрать, зависит от того, где нужен номер. Если он нужен только в печатной форме или отчёте — нумеруйте после запроса. Если номер участвует в дальнейших соединениях внутри пакета — считайте его в самом запросе. Ниже разобраны оба варианта с готовым текстом запросов, их цена по скорости и типичные ошибки.
Поле НомерСтроки: готовая нумерация записей табличной части
Многие ищут «функцию НОМЕРСТРОКИ». Такой функции в языке запросов нет. Есть поле НомерСтроки у двух видов таблиц:
- 📄 табличные части документов и справочников (
Документ.РеализацияТоваровУслуг.Товары); - 📊 физические таблицы регистров, подчинённых регистратору: номер записи внутри набора одного документа.
У виртуальных таблиц (Остатки, Обороты, СрезПоследних) этого поля нет. У основной таблицы документа (Документ.РеализацияТоваровУслуг) тоже нет — там одна строка на документ.
Пример: строки товаров в порядке, в каком их ввёл пользователь.
ВЫБРАТЬ
Товары.НомерСтроки КАК Номер,
Товары.Номенклатура,
Товары.Количество,
Товары.Сумма
ИЗ
Документ.РеализацияТоваровУслуг.Товары КАК Товары
ГДЕ
Товары.Ссылка = &Документ
УПОРЯДОЧИТЬ ПО
Товары.НомерСтроки
У этого поля два ограничения:
- Нумерация своя для каждого документа: у двух документов будут строки с номером 1, 2, 3.
- После отбора появляются дыры. Отфильтровали услуги — останутся номера 1, 3, 4, 7.
Если нужна сплошная нумерация 1, 2, 3 без пропусков после отбора, НомерСтроки используйте как ключ порядка, а сам номер пересчитайте одним из способов ниже.
Как пронумеровать строки через временную таблицу и ВЫРАЗИТЬ
Классический приём для произвольной выборки — соединить временную таблицу саму с собой. Для каждой строки считаем, сколько строк стоит «не позже» неё в выбранном порядке. Это число и есть номер.
Шаг 1. Собрать данные во временную таблицу
ВЫБРАТЬ
Контрагенты.Ссылка КАК Контрагент,
ВЫРАЗИТЬ(Контрагенты.Наименование КАК СТРОКА(150)) КАК Наименование
ПОМЕСТИТЬ ВТ_Данные
ИЗ
Справочник.Контрагенты КАК Контрагенты
ГДЕ
НЕ Контрагенты.ЭтоГруппа
ИНДЕКСИРОВАТЬ ПО
Наименование
;
Зачем здесь ВЫРАЗИТЬ: строки неограниченной длины нельзя сравнивать, группировать и индексировать. Приведение к СТРОКА(150) снимает это ограничение. Если поле порядка — число или дата, ВЫРАЗИТЬ не нужен.
Шаг 2. Посчитать номер соединением с собой
ВЫБРАТЬ
Т1.Контрагент,
Т1.Наименование,
КОЛИЧЕСТВО(Т2.Контрагент) КАК НомерПоПорядку
ИЗ
ВТ_Данные КАК Т1
ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_Данные КАК Т2
ПО Т2.Наименование <= Т1.Наименование
СГРУППИРОВАТЬ ПО
Т1.Контрагент,
Т1.Наименование
УПОРЯДОЧИТЬ ПО
НомерПоПорядку
Для первой по алфавиту строки условие <= выполнится один раз — номер 1. Для второй дважды — номер 2. И так далее.
Шаг 3. Решить проблему одинаковых значений
Если у двух контрагентов одинаковое наименование, оба получат одинаковый номер, а следующий номер «перепрыгнет». Строки «Альфа», «Бета», «Бета», «Гамма» получат 1, 3, 3, 4.
Лечится составным ключом порядка. Добавьте второе уникальное поле примитивного типа — код, номер документа или НомерСтроки:
ПО Т2.Наименование < Т1.Наименование
ИЛИ (Т2.Наименование = Т1.Наименование
И Т2.Код <= Т1.Код)
Код предварительно положите в ВТ_Данные. Для документов надёжная пара — Дата и Номер.
Ссылки как ключ порядка не подходят: сравнение <, > для ссылочных полей в запросе не даёт осмысленного порядка. Берите код, номер, дату или число.
Нумерация внутри группы
Чтобы номер начинался с 1 в каждой группе (например, по каждому покупателю), добавьте в условие соединения равенство поля группы:
ПО Т2.Покупатель = Т1.Покупатель
И Т2.Дата <= Т1.Дата
Результат: у каждого покупателя свои 1, 2, 3 по дате продажи. Поле группы тоже включите в индекс временной таблицы.
Нумерация в коде после выполнения запроса
Когда номер нужен только для вывода — в печатной форме, табличном документе, выгрузке в файл, — проще и быстрее пронумеровать результат на сервере после запроса.
Через выборку — счётчик в цикле:
Выборка = Запрос.Выполнить().Выбрать();
Номер = 0;
Пока Выборка.Следующий() Цикл
Номер = Номер + 1;
// Номер — порядковый номер текущей записи
КонецЦикла;
Через таблицу значений — добавить колонку и заполнить:
Таблица = Запрос.Выполнить().Выгрузить();
Таблица.Колонки.Добавить("НомерПП", Новый ОписаниеТипов("Число"));
Для Каждого Строка Из Таблица Цикл
Строка.НомерПП = Таблица.Индекс(Строка) + 1;
КонецЦикла;
Индекс() возвращает позицию с нуля, поэтому прибавляем единицу. Порядок строк задаёт УПОРЯДОЧИТЬ ПО в запросе — без него номера получатся в случайном порядке.
Какой метод нумерации быстрее: сравнение
Соединение с собой порождает квадратичный объём работы. Для 1 000 строк СУБД переберёт до 500 тысяч пар, для 10 000 строк — до 50 миллионов. Нумерация в коде — один проход, 10 000 операций.
| Метод | Когда подходит | Объём работы на N строк | Риски |
|---|---|---|---|
Поле НомерСтроки | Строки табличной части или набора записей | Без доп. затрат | Дыры после отбора, своя нумерация в каждом документе |
| Соединение временной таблицы с собой | Номер нужен внутри пакета запросов | ≈ N²/2 сравнений | Тормозит на больших выборках, дубли номеров при одинаковых ключах |
| Счётчик в коде | Печать, выгрузка, вывод в форму | N операций | Номер не доступен в следующих запросах пакета |
НомерПоПорядку() в СКД | Отчёты на компоновке | Считается при выводе | Номер зависит от настроек группировок |
Практический ориентир: соединение с собой нормально работает на сотнях и единицах тысяч строк. На десятках тысяч его уже заметно ждать, особенно в файловой базе.
Как ускорить, если без номера в запросе не обойтись:
- 🔎 Индексируйте временную таблицу по полям из условия соединения (
ИНДЕКСИРОВАТЬ ПО). - ✂️ Сначала отфильтруйте данные, потом нумеруйте. Не тяните в
ВТ_Данныелишние строки. - 🧩 При нумерации внутри групп поле группы ставьте первым в условие — пары между разными группами тогда не строятся.
- 📏 Сравнивайте короткие поля: число или дату, а не строку на 150 символов.
Частый реальный сценарий — номер нужен, чтобы потом соединить строку N со строкой N−1 (рассчитать разницу между соседними записями). Тогда считайте номер в запросе один раз, кладите результат во вторую временную таблицу с индексом по номеру и соединяйте по Т2.Номер = Т1.Номер - 1.
Номера строк при группировках и ИТОГИ ПО
С группировками есть две разные задачи. Важно не перепутать, что именно нумеруете.
Нумерация групп. Сначала сгруппируйте данные в отдельной временной таблице, затем нумеруйте уже её. Если считать номер в том же запросе, где СГРУППИРОВАТЬ ПО, КОЛИЧЕСТВО посчитает строки исходной таблицы, а не группы.
Нумерация детальных записей в запросе с ИТОГИ. Конструкция ИТОГИ ... ПО добавляет строки итогов, и в них поле номера тоже попадает в агрегат. Чтобы номер не превращался в сумму номеров:
- не указывайте поле номера в списке агрегатов секции
ИТОГИ— в строках итогов будетNULL; - при обходе выборки по группировкам (
Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам)) заводите счётчик на каждом уровне и обнуляйте его при переходе к новой группе.
Пример счётчика, который начинается заново у каждого покупателя:
ВыборкаПокупатели = Результат.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);
Пока ВыборкаПокупатели.Следующий() Цикл
Номер = 0;
ВыборкаДетали = ВыборкаПокупатели.Выбрать();
Пока ВыборкаДетали.Следующий() Цикл
Номер = Номер + 1;
КонецЦикла;
КонецЦикла;
Нумерация в СКД (системе компоновки данных)
В отчётах на СКД номер строки удобнее считать не в запросе набора данных, а в выражениях компоновки. Запрос остаётся простым, а номер подстраивается под группировки, которые пользователь выбрал в настройках.
Функции НомерПоПорядку и НомерПоПорядкуВГруппировке
В языке выражений СКД есть две функции:
НомерПоПорядку()— сквозной номер записи по всему отчёту;НомерПоПорядкуВГруппировке()— номер внутри текущей группировки, у каждой группы начинается с 1.
Как добавить колонку «№»:
- Откройте схему компоновки, вкладка
Вычисляемые поля. - Добавьте поле, например
НомерПП, заголовок «№». - В колонке выражения напишите
НомерПоПорядкуВГруппировке(). - На вкладке
Настройкидобавьте поле в выбранные поля нужной группировки и поставьте его первым.
Если функции недоступны
В очень старых релизах платформы этих функций нет. Тогда используют ВычислитьВыражение с накоплением от первой записи до текущей:
ВычислитьВыражение("Количество(Номенклатура)", , , "Первая", "Текущая")
Выражение считает число записей от начала группировки до текущей строки — это и есть порядковый номер.
Номер в СКД считается по итоговому порядку вывода. Если пользователь поменяет сортировку или группировки в варианте отчёта, номера пересчитаются. Не используйте такой номер как постоянный идентификатор строки.
Пример из практики: интервал между соседними продажами
Задача: по каждому покупателю показать, сколько дней прошло между его продажами. Без номера строки соседние записи в запросе не связать, поэтому нумерация здесь — промежуточный шаг.
План пакета из трёх запросов:
ВТ_Продажи— покупатель, дата, номер документа. Индекс по покупателю и дате.ВТ_Нумерованные— номер внутри покупателя через соединение с собой. Индекс по покупателю и номеру.- Итоговый запрос — левое соединение строки N со строкой N−1 того же покупателя.
Третий запрос пакета выглядит так:
ВЫБРАТЬ
Тек.Покупатель,
Тек.Дата,
РАЗНОСТЬДАТ(Пред.Дата, Тек.Дата, ДЕНЬ) КАК ДнейСПрошлойПродажи
ИЗ
ВТ_Нумерованные КАК Тек
ЛЕВОЕ СОЕДИНЕНИЕ ВТ_Нумерованные КАК Пред
ПО Пред.Покупатель = Тек.Покупатель
И Пред.Номер = Тек.Номер - 1
УПОРЯДОЧИТЬ ПО
Тек.Покупатель,
Тек.Номер
У первой продажи каждого покупателя пары нет, и в колонке будет NULL. Это правильно: предыдущей продажи не было. Если в отчёте нужен ноль, оберните выражение в ЕСТЬNULL(..., 0).
На 300 покупателях по 20 продаж второй запрос сравнит около 300 × 20 × 21 / 2 = 63 000 пар. Это доли секунды. Без условия равенства покупателя пар было бы 6 000 × 6 001 / 2 ≈ 18 миллионов — разница в сотни раз.
Ошибки при нумерации строк и как их исправить
Ещё одна неочевидная ловушка — NULL в поле порядка. Сравнение с NULL всегда ложно, такие строки не найдут пары при внутреннем соединении и выпадут из результата. Замените пустые значения через ЕСТЬNULL(Поле, 0) до нумерации.
Частые вопросы
Есть ли в языке запросов 1С аналог ROW_NUMBER() из SQL?
Можно ли получить номер строки без временной таблицы?
Почему НомерСтроки в документе идёт с пропусками?
Как вывести только строки с 11-й по 20-ю?
ГДЕ НомерПоПорядку МЕЖДУ 11 И 20. Для первых N строк достаточно ВЫБРАТЬ ПЕРВЫЕ N.