Почему сортировка по расчетному полю в запросе 1С с итогами работает некорректно и как это исправить?

Программист 1С v8.3 (Управляемые формы) Управленческий учет Финансы и профессиональные услуги
← На главную

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

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

Выясняем причину: как секция ИТОГИ ломает линейную сортировку

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

Когда в запросе присутствует секция ИТОГИ ПО, платформа переходит от линейной структуры данных к иерархической (дерево значений). Упорядочивание в этом случае происходит не по всей результирующей таблице сразу, а строго внутри каждого уровня иерархии:

  1. Первый уровень группировки (например, Контрагент): Платформа берет агрегатное значение поля Остаток для каждого контрагента (в запросе автора это МАКСИМУМ(Остаток)) и сортирует контрагентов между собой по убыванию этого максимума.
  2. Второй уровень группировки (Регистратор): Внутри каждого конкретного контрагента сортируются подчиненные ему регистраторы — также по убыванию своего значения МАКСИМУМ(Остаток).
  3. Детальные записи (ОсновноеСредство): На самом нижнем уровне детальные записи внутри каждого регистратора сортируются по значению поля Остаток.

Проанализируем простую ситуацию. Допустим, у нас есть два контрагента со следующими данными:

При сортировке по убыванию максимума сначала выведется Контрагент А (у него максимум 500 000, что больше 400 000 у Контрагента Б). Если мы будем обходить результат запроса линейно (например, выгрузим в плоскую таблицу значений или обойдем простым циклом выборки), то мы получим следующий порядок вывода документов:

  1. Документ №1: 500 000 руб.
  2. Документ №2: 10 000 руб.
  3. Документ №3: 400 000 руб.
  4. Документ №4: 250 000 руб.

Для конечного пользователя такой список выглядит абсолютно хаотичным, ведь после значения 10 000 неожиданно выводится 400 000. Но с точки зрения иерархической выборки сортировка выполнена абсолютно корректно.

Дополнительной проблемой в запросе автора является использование функции МАКСИМУМ(Остаток) в итогах. Если отчет строится для оценки общего долга, группировки должны сортироваться по сумме всех остатков, а не по максимальному единичному значению. Использование неверной агрегатной функции усугубляет визуальный хаос.

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

---

Вариант решения №1. Переход на Систему компоновки данных (СКД)

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

Разберем по шагам, как решить проблему сортировки с помощью компоновки данных:

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

---

Вариант решения №2. Использование плоской таблицы и программный расчет итогов

Если вам необходимо получить строго упорядоченный список в коде (например, для вывода в специализированный макет вручную), откажитесь от секции ИТОГИ в самом запросе. Мы получим плоскую таблицу, отсортируем ее средствами СУБД, а расчет итогов перенесем на уровень алгоритма 1С.

Рассмотрим пример реализации такого подхода:


// 1. Изменяем финальный пакет запроса (убираем ИТОГИ, оставляем только УПОРЯДОЧИТЬ)
ТекстЗапроса = "ВЫБРАТЬ
               |	вр_Осн.ОсновноеСредство КАК ОсновноеСредство,
               |	вр_Осн.Регистратор КАК Регистратор,
               |	вр_Осн.КонтрагентНаименование КАК Контрагент,
               |	вр_Осн.Остаток КАК Остаток,
               |	вр_доп.СуммаПлатежа - вр_Осн.Сумма51_62 КАК Всего
               |ИЗ
               |	вр_Осн КАК вр_Осн
               |	ВНУТРЕННЕЕ СОЕДИНЕНИЕ вр_доп КАК вр_доп
               |	ПО вр_Осн.Регистратор = vr_доп.Ссылка
               |УПОРЯДОЧИТЬ ПО
               |	Остаток УБЫВ";

Запрос = Новый Запрос(ТекстЗапроса);
// Настройка параметров запроса...
РезультатЗапроса = Запрос.Выполнить();

// 2. Выгружаем результат в плоскую Таблицу Значений
ТаблицаДанных = РезультатЗапроса.Выгрузить();

// Теперь данные гарантированно отсортированы строго по убыванию остатка.
// При необходимости мы можем свернуть таблицу или рассчитать итоги программно:
СуммаВсегоОстаток = ТаблицаДанных.Итог("Остаток");

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

---

Вариант решения №3. Программная сортировка выгруженной таблицы значений

Иногда запрос выдает данные в нужном формате, но из-за специфики левых соединений или особенностей платформы СУБД выстраивает индексы неоптимально, что влияет на финальную сортировку. В этом случае эффективным решением будет явная сортировка результирующей таблицы значений в оперативной памяти.

Проанализируем этот метод по шагам:

  1. Выполните запрос без секции ИТОГИ (получите плоскую таблицу).
  2. Выгрузите результат в объект ТаблицаЗначений.
  3. Примените метод Сортировать().

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


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

// Принудительно сортируем таблицу значений по колонке Остаток по убыванию
ТаблицаРезультата.Сортировать("Остаток УБЫВ");

// Теперь выводим данные в макет или форму
Для Каждого СтрокаДанных Из ТаблицаРезультата Цикл
    // Обработка строк в гарантированном порядке
КонецЦикла;

Важное примечание: Этот способ идеально работает на небольших и средних объемах данных (до десятков тысяч строк). На экстремально больших объемах данных сортировка в оперативной памяти может снизить производительность, поэтому для больших отчетов всегда рекомендуется использовать СКД.

---

Важный совет: защита от NULL при расчете Остатка

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


// Неправильно (любой NULL превратит весь результат в NULL, и строка уйдет в конец или начало списка):
// ЛК_ГрафикиДоговоровЛизингСрезПоследних.ОбщаяСуммаПлатежей - ВложенныйЗапрос51_62.СуммаОборот КАК Остаток

// Правильно:
ЕСТЬNULL(ЛК_ГрафикиДоговоровЛизингСрезПоследних.ОбщаяСуммаПлатежей, 0) 
- ЕСТЬNULL(ВложенныйЗапрос51_62.СуммаОборот, 0) 
- ЕСТЬNULL(ОплатаПрошлыхПериодов.СуммаОплаты, 0) 
+ ЕСТЬNULL(ВложенныйЗапрос62_51.СуммаОборот, 0) 
- ЕСТЬNULL(ВложенныйЗапрос62_62.СуммаОборот, 0) КАК Остаток

Использование конструкции ЕСТЬNULL(..., 0) гарантирует, что при отсутствии записей в соединенных таблицах расчет произведется корректно, поле получит валидное числовое значение, и сортировка отработает без сбоев на уровне базы данных.

← На главную