При работе со сложными отчетами в системе 1С программисты часто сталкиваются со следующей проблемой: в финальной части запроса рассчитывается числовое поле (например, Остаток или Всего), настраивается его упорядочивание по убыванию, но при выводе данных строки отображаются хаотично. На первый взгляд кажется, что платформа игнорирует инструкцию УПОРЯДОЧИТЬ ПО. Однако это поведение абсолютно логично с точки зрения работы СУБД и платформы 1С.
В этой статье мы подробно разберем, почему возникает эффект «хаотичной» сортировки при использовании секции ИТОГИ, как устроена поуровневая группировка и какими способами можно добиться идеального порядка вывода данных.
Основная ошибка кроется в совместном использовании инструкций УПОРЯДОЧИТЬ ПО и ИТОГИ ПО в одном запросе. Рассмотрим, как платформа 1С обрабатывает такой запрос.
Когда в запросе присутствует секция ИТОГИ ПО, платформа переходит от линейной структуры данных к иерархической (дерево значений). Упорядочивание в этом случае происходит не по всей результирующей таблице сразу, а строго внутри каждого уровня иерархии:
Остаток для каждого контрагента (в запросе автора это МАКСИМУМ(Остаток)) и сортирует контрагентов между собой по убыванию этого максимума.МАКСИМУМ(Остаток).Остаток.Проанализируем простую ситуацию. Допустим, у нас есть два контрагента со следующими данными:
При сортировке по убыванию максимума сначала выведется Контрагент А (у него максимум 500 000, что больше 400 000 у Контрагента Б). Если мы будем обходить результат запроса линейно (например, выгрузим в плоскую таблицу значений или обойдем простым циклом выборки), то мы получим следующий порядок вывода документов:
Для конечного пользователя такой список выглядит абсолютно хаотичным, ведь после значения 10 000 неожиданно выводится 400 000. Но с точки зрения иерархической выборки сортировка выполнена абсолютно корректно.
Дополнительной проблемой в запросе автора является использование функции МАКСИМУМ(Остаток) в итогах. Если отчет строится для оценки общего долга, группировки должны сортироваться по сумме всех остатков, а не по максимальному единичному значению. Использование неверной агрегатной функции усугубляет визуальный хаос.
Также стоит помнить о потенциальных значениях NULL. При расчете поля Остаток используются левые соединения. Если хотя бы одно слагаемое в математическом выражении примет значение NULL, результатом всего выражения станет NULL. Различные СУБД сортируют NULL по-разному (в начале или в конце списка), что также ломает ожидаемый порядок.
---
Использование ручного расчета итогов и сортировки в коде запроса (особенно для формирования отчетов) считается устаревшим подходом. Наиболее простым, надежным и современным решением является перевод отчета на СКД.
Разберем по шагам, как решить проблему сортировки с помощью компоновки данных:
УПОРЯДОЧИТЬ ПО и ИТОГИ. Запрос должен возвращать «плоскую» таблицу данных.Остаток и укажите для него выражение расчета (например, Сумма(Остаток)).Контрагент -> Регистратор -> Детальные записи).Остаток (направление — по убыванию). СКД самостоятельно рассчитает итоги для каждого уровня и гарантирует правильное иерархическое упорядочивание.---
Если вам необходимо получить строго упорядоченный список в коде (например, для вывода в специализированный макет вручную), откажитесь от секции ИТОГИ в самом запросе. Мы получим плоскую таблицу, отсортируем ее средствами СУБД, а расчет итогов перенесем на уровень алгоритма 1С.
Рассмотрим пример реализации такого подхода:
// 1. Изменяем финальный пакет запроса (убираем ИТОГИ, оставляем только УПОРЯДОЧИТЬ)
ТекстЗапроса = "ВЫБРАТЬ
| вр_Осн.ОсновноеСредство КАК ОсновноеСредство,
| вр_Осн.Регистратор КАК Регистратор,
| вр_Осн.КонтрагентНаименование КАК Контрагент,
| вр_Осн.Остаток КАК Остаток,
| вр_доп.СуммаПлатежа - вр_Осн.Сумма51_62 КАК Всего
|ИЗ
| вр_Осн КАК вр_Осн
| ВНУТРЕННЕЕ СОЕДИНЕНИЕ вр_доп КАК вр_доп
| ПО вр_Осн.Регистратор = vr_доп.Ссылка
|УПОРЯДОЧИТЬ ПО
| Остаток УБЫВ";
Запрос = Новый Запрос(ТекстЗапроса);
// Настройка параметров запроса...
РезультатЗапроса = Запрос.Выполнить();
// 2. Выгружаем результат в плоскую Таблицу Значений
ТаблицаДанных = РезультатЗапроса.Выгрузить();
// Теперь данные гарантированно отсортированы строго по убыванию остатка.
// При необходимости мы можем свернуть таблицу или рассчитать итоги программно:
СуммаВсегоОстаток = ТаблицаДанных.Итог("Остаток");
Если вам все же требуется дерево значений с итогами, но отсортированное по правилам плоской таблицы, вы можете сначала выгрузить плоскую отсортированную таблицу, а затем программно построить из нее дерево с помощью обхода строк, сохраняя исходную сортировку детальных записей.
---
Иногда запрос выдает данные в нужном формате, но из-за специфики левых соединений или особенностей платформы СУБД выстраивает индексы неоптимально, что влияет на финальную сортировку. В этом случае эффективным решением будет явная сортировка результирующей таблицы значений в оперативной памяти.
Проанализируем этот метод по шагам:
ИТОГИ (получите плоскую таблицу).ТаблицаЗначений.Сортировать().Посмотрим на пример кода:
РезультатЗапроса = Запрос.Выполнить();
ТаблицаРезультата = РезультатЗапроса.Выгрузить();
// Принудительно сортируем таблицу значений по колонке Остаток по убыванию
ТаблицаРезультата.Сортировать("Остаток УБЫВ");
// Теперь выводим данные в макет или форму
Для Каждого СтрокаДанных Из ТаблицаРезультата Цикл
// Обработка строк в гарантированном порядке
КонецЦикла;
Важное примечание: Этот способ идеально работает на небольших и средних объемах данных (до десятков тысяч строк). На экстремально больших объемах данных сортировка в оперативной памяти может снизить производительность, поэтому для больших отчетов всегда рекомендуется использовать СКД.
---
При расчете сложных полей всегда оборачивайте все слагаемые в функцию ЕСТЬNULL. Посмотрим, как должен выглядеть надежный расчет поля Остаток, защищенный от искажения сортировки:
// Неправильно (любой NULL превратит весь результат в NULL, и строка уйдет в конец или начало списка):
// ЛК_ГрафикиДоговоровЛизингСрезПоследних.ОбщаяСуммаПлатежей - ВложенныйЗапрос51_62.СуммаОборот КАК Остаток
// Правильно:
ЕСТЬNULL(ЛК_ГрафикиДоговоровЛизингСрезПоследних.ОбщаяСуммаПлатежей, 0)
- ЕСТЬNULL(ВложенныйЗапрос51_62.СуммаОборот, 0)
- ЕСТЬNULL(ОплатаПрошлыхПериодов.СуммаОплаты, 0)
+ ЕСТЬNULL(ВложенныйЗапрос62_51.СуммаОборот, 0)
- ЕСТЬNULL(ВложенныйЗапрос62_62.СуммаОборот, 0) КАК Остаток
Использование конструкции ЕСТЬNULL(..., 0) гарантирует, что при отсутствии записей в соединенных таблицах расчет произведется корректно, поле получит валидное числовое значение, и сортировка отработает без сбоев на уровне базы данных.