Номер по порядку в запросе 1С: поле НомерСтроки, соединение с собой и нумерация в СКД

· 9 мин

Содержание

В языке запросов 1С нет оконной функции вроде ROW_NUMBER() из SQL, поэтому пронумеровать записи «одной командой» нельзя. Готовый номер есть только у строк табличных частей и наборов записей регистров — это поле НомерСтроки. Для любой другой выборки номер строки получают тремя путями: соединением временной таблицы с самой собой и подсчётом, нумерацией результата в коде или функцией НомерПоПорядку() в системе компоновки данных.

Какой способ выбрать, зависит от того, где нужен номер. Если он нужен только в печатной форме или отчёте — нумеруйте после запроса. Если номер участвует в дальнейших соединениях внутри пакета — считайте его в самом запросе. Ниже разобраны оба варианта с готовым текстом запросов, их цена по скорости и типичные ошибки.

Поле НомерСтроки: готовая нумерация записей табличной части

Многие ищут «функцию НОМЕРСТРОКИ». Такой функции в языке запросов нет. Есть поле НомерСтроки у двух видов таблиц:

  • 📄 табличные части документов и справочников (Документ.РеализацияТоваровУслуг.Товары);
  • 📊 физические таблицы регистров, подчинённых регистратору: номер записи внутри набора одного документа.

У виртуальных таблиц (Остатки, Обороты, СрезПоследних) этого поля нет. У основной таблицы документа (Документ.РеализацияТоваровУслуг) тоже нет — там одна строка на документ.

Пример: строки товаров в порядке, в каком их ввёл пользователь.

ВЫБРАТЬ
    Товары.НомерСтроки КАК Номер,
    Товары.Номенклатура,
    Товары.Количество,
    Товары.Сумма
ИЗ
    Документ.РеализацияТоваровУслуг.Товары КАК Товары
ГДЕ
    Товары.Ссылка = &Документ
УПОРЯДОЧИТЬ ПО
    Товары.НомерСтроки

У этого поля два ограничения:

  1. Нумерация своя для каждого документа: у двух документов будут строки с номером 1, 2, 3.
  2. После отбора появляются дыры. Отфильтровали услуги — останутся номера 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 операцийНомер не доступен в следующих запросах пакета
НомерПоПорядку() в СКДОтчёты на компоновкеСчитается при выводеНомер зависит от настроек группировок

Практический ориентир: соединение с собой нормально работает на сотнях и единицах тысяч строк. На десятках тысяч его уже заметно ждать, особенно в файловой базе.

Как ускорить, если без номера в запросе не обойтись:

  1. 🔎 Индексируйте временную таблицу по полям из условия соединения (ИНДЕКСИРОВАТЬ ПО).
  2. ✂️ Сначала отфильтруйте данные, потом нумеруйте. Не тяните в ВТ_Данные лишние строки.
  3. 🧩 При нумерации внутри групп поле группы ставьте первым в условие — пары между разными группами тогда не строятся.
  4. 📏 Сравнивайте короткие поля: число или дату, а не строку на 150 символов.

Частый реальный сценарий — номер нужен, чтобы потом соединить строку N со строкой N−1 (рассчитать разницу между соседними записями). Тогда считайте номер в запросе один раз, кладите результат во вторую временную таблицу с индексом по номеру и соединяйте по Т2.Номер = Т1.Номер - 1.

Номера строк при группировках и ИТОГИ ПО

С группировками есть две разные задачи. Важно не перепутать, что именно нумеруете.

Нумерация групп. Сначала сгруппируйте данные в отдельной временной таблице, затем нумеруйте уже её. Если считать номер в том же запросе, где СГРУППИРОВАТЬ ПО, КОЛИЧЕСТВО посчитает строки исходной таблицы, а не группы.

Нумерация детальных записей в запросе с ИТОГИ. Конструкция ИТОГИ ... ПО добавляет строки итогов, и в них поле номера тоже попадает в агрегат. Чтобы номер не превращался в сумму номеров:

  • не указывайте поле номера в списке агрегатов секции ИТОГИ — в строках итогов будет NULL;
  • при обходе выборки по группировкам (Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам)) заводите счётчик на каждом уровне и обнуляйте его при переходе к новой группе.

Пример счётчика, который начинается заново у каждого покупателя:

ВыборкаПокупатели = Результат.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);
Пока ВыборкаПокупатели.Следующий() Цикл
    Номер = 0;
    ВыборкаДетали = ВыборкаПокупатели.Выбрать();
    Пока ВыборкаДетали.Следующий() Цикл
        Номер = Номер + 1;
    КонецЦикла;
КонецЦикла;

Нумерация в СКД (системе компоновки данных)

В отчётах на СКД номер строки удобнее считать не в запросе набора данных, а в выражениях компоновки. Запрос остаётся простым, а номер подстраивается под группировки, которые пользователь выбрал в настройках.

Функции НомерПоПорядку и НомерПоПорядкуВГруппировке

В языке выражений СКД есть две функции:

  • НомерПоПорядку() — сквозной номер записи по всему отчёту;
  • НомерПоПорядкуВГруппировке() — номер внутри текущей группировки, у каждой группы начинается с 1.

Как добавить колонку «№»:

  1. Откройте схему компоновки, вкладка Вычисляемые поля.
  2. Добавьте поле, например НомерПП, заголовок «№».
  3. В колонке выражения напишите НомерПоПорядкуВГруппировке().
  4. На вкладке Настройки добавьте поле в выбранные поля нужной группировки и поставьте его первым.

Если функции недоступны

В очень старых релизах платформы этих функций нет. Тогда используют ВычислитьВыражение с накоплением от первой записи до текущей:

ВычислитьВыражение("Количество(Номенклатура)", , , "Первая", "Текущая")

Выражение считает число записей от начала группировки до текущей строки — это и есть порядковый номер.

Номер в СКД считается по итоговому порядку вывода. Если пользователь поменяет сортировку или группировки в варианте отчёта, номера пересчитаются. Не используйте такой номер как постоянный идентификатор строки.

Пример из практики: интервал между соседними продажами

Задача: по каждому покупателю показать, сколько дней прошло между его продажами. Без номера строки соседние записи в запросе не связать, поэтому нумерация здесь — промежуточный шаг.

План пакета из трёх запросов:

  1. ВТ_Продажи — покупатель, дата, номер документа. Индекс по покупателю и дате.
  2. ВТ_Нумерованные — номер внутри покупателя через соединение с собой. Индекс по покупателю и номеру.
  3. Итоговый запрос — левое соединение строки 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?
Нет. Оконных функций в языке запросов 1С нет. Номер получают соединением таблицы с собой, нумерацией в коде или функциями СКД.
Можно ли получить номер строки без временной таблицы?
Можно соединить с собой вложенный запрос, но временная таблица с индексом работает предсказуемее и читается проще. Для больших выборок используйте именно её.
Почему НомерСтроки в документе идёт с пропусками?
В самой табличной части номера идут подряд. Пропуски в результате запроса появляются из-за условий ГДЕ, которые отбросили часть строк.
Как вывести только строки с 11-й по 20-ю?
Пронумеруйте выборку соединением с собой во временную таблицу, затем отберите ГДЕ НомерПоПорядку МЕЖДУ 11 И 20. Для первых N строк достаточно ВЫБРАТЬ ПЕРВЫЕ N.
Номер из запроса сохранится, если данные изменятся?
Нет. Номер вычисляется при каждом выполнении запроса. Добавили или удалили запись — номера сдвинутся. Для постоянного идентификатора храните реквизит в базе.