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

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

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

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

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

Проанализируем распространенную ошибку, с которой сталкиваются разработчики при попытке подтянуть поле НомерСтроки из табличной части справочника. Посмотрим на следующий пример текста запроса:


ВЫБРАТЬ
    ВТГотовый.Номенклатура КАК Номенклатура,
    ВТГотовый.Номенклатура.Родитель КАК ГруппаНоменклатуры,
    ВТГотовый.Номенклатура.ЕдиницаИзмеренияМест КАК Единица,
    СпискиНоменклатурыСписокНоменклатуры.НомерСтроки КАК Порядок
ИЗ
    ВТГотовый КАК ВТГотовый
        ЛЕВОЕ СОЕДИНЕНИЕ Справочник.СпискиНоменклатуры.СписокНоменклатуры КАК СпискиНоменклатурыСписокНоменклатуры
        ПО ВТГотовый.Номенклатура = СпискиНоменклатурыСписокНоменклатуры.Номенклатура
ГДЕ
    СпискиНоменклатурыСписокНоменклатуры.Ссылка = &СписокНоменклатуры

Выясним причину, по которой данный запрос возвращает только те строки, которые присутствуют в табличной части, отсекая все остальные:

  1. Условие в секции ГДЕ фильтрует результат после соединения. Когда для строки из левой таблицы (ВТГотовый) нет совпадения в правой таблице, все поля правой таблицы заполняются значениями NULL.
  2. Проверка на равенство с NULL всегда дает Ложь. Выражение NULL = &СписокНоменклатуры не выполняется, и СУБД удаляет такие строки из итоговой выборки. В результате ЛЕВОЕ СОЕДИНЕНИЕ фактически превращается во ВНУТРЕННЕЕ СОЕДИНЕНИЕ.

Способ 1. Перенос условия отбора в секцию ПО

Самый простой способ исправить запрос без изменения его структуры — перенести фильтрацию по конкретному элементу справочника непосредственно в условие соединения таблиц.

Посмотрим на пример запроса:


ВЫБРАТЬ
    ВТГотовый.Номенклатура КАК Номенклатура,
    ВТГотовый.Номенклатура.Родитель КАК ГруппаНоменклатуры,
    ВТГотовый.Номенклатура.ЕдиницаИзмеренияМест КАК Единица,
    ЕСТЬNULL(СпискиНоменклатуры.НомерСтроки, 99999999) КАК Порядок
ИЗ
    ВТГотовый КАК ВТГотовый
        ЛЕВОЕ СОЕДИНЕНИЕ Справочник.СпискиНоменклатуры.СписокНоменклатуры КАК СпискиНоменклатуры
        ПО ВТГотовый.Номенклатура = СпискиНоменклатуры.Номенклатура
            И (СпискиНоменклатуры.Ссылка = &СписокНоменклатуры)
УПОРЯДОЧИТЬ ПО
    Порядок ВОЗР

В этом случае отбор по ссылке применяется исключительно к строкам табличной части до выполнения соединения, благодаря чему все строки из таблицы ВТГотовый сохраняются в результате.

Способ 2. Предварительная выборка во временную таблицу с группировкой (рекомендуемый)

Рассмотрим более надежный и производительный подход. В реальных базах одна и та же номенклатура может встретиться в табличной части несколько раз (например, с разными характеристиками, сериями или из-за дублирования пользователем). Простое левое соединение в таком случае приведет к размножению строк и некорректным итогам.

Разберем по шагам построение запроса через временную таблицу:

  1. Выбираем строки табличной части по конкретному объекту-владельцу.
  2. Группируем выборку по номенклатуре и берем минимальный номер строки через МИНИМУМ(НомерСтроки), чтобы гарантировать уникальность ключа соединения.
  3. Индексируем временную таблицу для ускорения соединения.
  4. Соединяем подготовленную таблицу с основным результатом и настраиваем сортировку.

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


ВЫБРАТЬ
    ТЧ.Номенклатура КАК Номенклатура,
    МИНИМУМ(ТЧ.НомерСтроки) КАК НомерСтроки
ПОМЕСТИТЬ ВТ_ПорядокНоменклатуры
ИЗ
    Справочник.СпискиНоменклатуры.СписокНоменклатуры КАК ТЧ
ГДЕ
    ТЧ.Ссылка = &СписокНоменклатуры
СГРУППИРОВАТЬ ПО
    ТЧ.Номенклатура
ИНДЕКСИРОВАТЬ ПО
    Номенклатура
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    ВТГотовый.Номенклатура КАК Номенклатура,
    ВТГотовый.Номенклатура.Родитель КАК ГруппаНоменклатуры,
    ВТГотовый.Номенклатура.ЕдиницаИзмеренияМест КАК Единица,
    ЕСТЬNULL(ВТ_Порядок.НомерСтроки, 99999999) КАК Порядок
ИЗ
    ВТГотовый КАК ВТГотовый
        ЛЕВОЕ СОЕДИНЕНИЕ ВТ_ПорядокНоменклатуры КАК ВТ_Порядок
        ПО ВТГотовый.Номенклатура = ВТ_Порядок.Номенклатура
УПОРЯДОЧИТЬ ПО
    Порядок ВОЗР,
    ВТГотовый.Номенклатура.Наименование ВОЗР

Особенности сортировки значений NULL в запросах 1С

Обратим внимание на поведение значений NULL при сортировке. В языке запросов 1С при сортировке по возрастанию (ВОЗР) значения NULL по умолчанию оказываются в самом начале списка. Если для позиции нет соответствия в табличной части, она попадет на первые строки отчета, что нарушает требуемый порядок.

Для корректного размещения отсутствующих позиций в конце списка применим один из двух вариантов:

  1. Функция ЕСТЬNULL: заменяем NULL на заведомо большое число, например: ЕСТЬNULL(ВТ_Порядок.НомерСтроки, 99999999) КАК Порядок
  2. Конструкция ВЫБОР в секции УПОРЯДОЧИТЬ ПО:
    
    УПОРЯДОЧИТЬ ПО
        ВЫБОР
            КОГДА ВТ_Порядок.НомерСтроки ЕСТЬ NULL ТОГДА 1
            ИНАЧЕ 0
        КОНЕЦ ВОЗР,
        ВТ_Порядок.НомерСтроки ВОЗР
    
    Этот вариант гарантирует, что сначала пойдут элементы, найденные в табличной части, строго по их исходным номерам строк, а следом — все остальные позиции.

Альтернативный вариант: программная сортировка через ТаблицуЗначений

Если результат запроса выгружается в коллекцию для постобработки в коде на встроенном языке, упорядочивание можно выполнить средствами объекта ТаблицаЗначений без усложнения структуры запроса.

Рассмотрим пример реализации:


// 1. Формируем соответствие: Номенклатура -> НомерСтроки
СоответствиеПорядка = Новый Соответствие;
Для Каждого СтрТЧ Из СписокНоменклатуры.СписокНоменклатуры Цикл
    Если СоответствиеПорядка[СтрТЧ.Номенклатура] = Неопределено Тогда
        СоответствиеПорядка.Вставить(СтрТЧ.Номенклатура, СтрТЧ.НомерСтроки);
    КонецЕсли;
КонецЦикла;

// 2. Выполняем основной запрос и выгружаем результат
ТаблицаРезультата = Запрос.Выполнить().Выгрузить();
ТаблицаРезультата.Колонки.Добавить("Порядок", Новый ОписаниеТипов("Число"));

// 3. Заполняем веса сортировки
МаксимальныйПорядок = 99999999;
Для Каждого Стр Из ТаблицаРезультата Цикл
    Номер = СоответствиеПорядка[Стр.Номенклатура];
    Стр.Порядок = ?(Номер <> Неопределено, Номер, МаксимальныйПорядок);
КонецЦикла;

// 4. Сортируем готовую таблицу
ТаблицаРезультата.Сортировать("Порядок Возр, Номенклатура Возр");

Такой подход удобен в обработках и алгоритмах заполнения документов, когда требуется минимальная нагрузка на СУБД при небольшом объеме обрабатываемых строк.

← На главную