Как сохранить порядок строк при передаче таблицы значений во временную таблицу запроса 1С?

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

При разработке отчетов и сложных алгоритмов обработки данных программисты 1С часто сталкиваются с ситуацией, когда заранее подготовленная и отсортированная коллекция передается в запрос в качестве параметра. Однако после помещения ее во временную таблицу и последующей выборки порядок строк кардинально меняется. Разберем, почему платформа ведет себя именно так и как гарантированно сохранить исходную последовательность записей.

Почему нарушается порядок строк при выборке из временной таблицы

Проанализируем механику работы платформы 1С с СУБД. Когда мы передаем объект типа ТаблицаЗначений в качестве параметра запроса &ВТ, платформа создает на стороне СУБД физическую временную таблицу и выполняет массовую вставку записей. В соответствии с реляционной теорией и стандартами SQL таблица представляет собой неупорядоченное множество строк. Физический порядок извлечения данных СУБД не гарантируется, если в запросе явно не указана инструкция УПОРЯДОЧИТЬ ПО (в терминах SQL — ORDER BY).

На порядок выборки влияют оптимизатор СУБД, распараллеливание запросов, фрагментация страниц и наличие индексов. Если мы используем конструкцию ИНДЕКСИРОВАТЬ ПО, СУБД считывает данные наиболее дешевым способом (по индексу), окончательно нарушая исходную сортировку переданной таблицы.

В языке запросов 1С действуют строгие синтаксические ограничения:

  1. Конструкция УПОРЯДОЧИТЬ ПО запрещена в запросах, создающих временную таблицу через оператор ПОМЕСТИТЬ.
  2. Обходной прием с использованием конструкции ВЫБРАТЬ ПЕРВЫЕ... ПОМЕСТИТЬ... УПОРЯДОЧИТЬ ПО формально допустим синтаксически, однако он не решает проблему: при последующем чтении из этой временной таблицы СУБД все равно может вернуть записи в произвольном порядке.
  3. В языке запросов 1С отсутствуют оконные функции (например, ROW_NUMBER() OVER()), поэтому вычислить физический номер строки внутри запроса невозможно.

Основное решение: добавление колонки-нумератора перед передачей в запрос

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

Посмотрим на пошаговый алгоритм реализации:

  1. Подготовка таблицы: создаем копию исходной таблицы значений, чтобы не модифицировать внешние данные, если это критично для вызывающего кода.
  2. Добавление служебного поля: создаем колонку НомерСтроки с типом Число.
  3. Заполнение номеров: в цикле присваиваем строкам последовательные индексы от 0 или 1.
  4. Передача в запрос: выбираем служебное поле во временную таблицу и пробрасываем его до завершающего блока.
  5. Финальная сортировка: в итоговой выборке указываем УПОРЯДОЧИТЬ ПО по нашему служебному полю.
  6. Очистка: после выгрузки результата удаляем техническую колонку из итоговой таблицы.

Рассмотрим пример кода на встроенном языке 1С:


// Создаем копию исходной таблицы, чтобы не портить структуру оригинала
ТЗДляЗапроса = ИсходнаяТЗ.Скопировать();

// Добавляем числовую колонку для сохранения исходного порядка
ТЗДляЗапроса.Колонки.Добавить("НомерСтроки", Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(10, 0)));

// Заполняем порядковые номера строк
Для Индекс = 0 По ТЗДляЗапроса.Количество() - 1 Цикл
    ТЗДляЗапроса[Индекс].НомерСтроки = Индекс;
КонецЦикла;

// Формируем запрос
Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |	ВТ.НомерСтроки КАК НомерСтроки,
    |	ВТ.Значение1 КАК Значение1,
    |	ВТ.Значение2 КАК Значение2
    |ПОМЕСТИТЬ ВТ2
    |ИЗ
    |	&ВТ КАК ВТ
    |;
    |
    |////////////////////////////////////////////////////////////////////////////////
    |ВЫБРАТЬ
    |	ВТ2.Значение1 КАК Значение1,
    |	ВТ2.Значение2 КАК Значение2,
    |	ВТ2.НомерСтроки КАК НомерСтроки
    |ИЗ
    |	ВТ2 КАК ВТ2
    |
    |УПОРЯДОЧИТЬ ПО
    |	НомерСтроки ВОЗР";

Запрос.УстановитьПараметр("ВТ", ТЗДляЗапроса);

РезультатТЗ = Запрос.Выполнить().Выгрузить();

// Удаляем служебную колонку из результата
РезультатТЗ.Колонки.Удалить("НомерСтроки");

Важный момент: если итоговый запрос содержит группировки (СГРУППИРОВАТЬ ПО), поле НомерСтроки должно быть включено в состав полей группировки либо агрегировано функцией МИНИМУМ(НомерСтроки), в зависимости от бизнес-логики решаемой задачи.

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

В зависимости от архитектуры обработки данных можно применить и другие варианты сохранения порядка:

  1. Сортировка результирующей таблицы встроенным языком: если алгоритм сортировки исходной таблицы основан на значениях конкретных полей, проще всего отказаться от нумератора и вызвать метод Сортировать() у полученной таблицы:
    
    РезультатТЗ = Запрос.Выполнить().Выгрузить();
    РезультатТЗ.Сортировать("Значение1 Возр, Значение2 Убыв");
    
    Этот вариант отлично подходит, когда правила сортировки детерминированы и легко воспроизводятся по полям результата.
  2. Сортировка по ключевым полям в самом запросе: если данные содержат поля с естественным порядком (например, Дата, Код, Номер), достаточно явно указать УПОРЯДОЧИТЬ ПО по этим полям в последнем запросе пакета, не создавая искусственных счетчиков.
  3. Позиционирование через соответствие по уникальному ключу: если исходная таблица имеет уникальный идентификатор строки, результат запроса можно поместить в объект Соответствие, после чего в цикле обойти исходную таблицу значений и заполнить выходные данные в строгом соответствии с оригинальным порядком.

Таким образом, единственным гарантированным способом сохранить нестандартный или произвольный порядок строк таблицы значений внутри запроса является явная передача служебного числового счетчика и финальное применение конструкции УПОРЯДОЧИТЬ ПО.

← На главную