При работе с запросами в 1С:Предприятие 7.7 часто возникает задача получить данные, отсортированные в обратном порядке (по убыванию). Стандартный механизм для этого — использование метода Запрос.Группировка() с параметром направления -1. Однако иногда разработчики сталкиваются с ситуацией, когда такой вызов возвращает пустую выборку, хотя прямой обход работает корректно. Разберем, почему так происходит и какие существуют способы решения этой проблемы.
Проанализируем типичную ситуацию. Имеется запрос к справочнику, который нужно упорядочить по реквизиту с датой в обратном порядке.
Исходный код запроса:
Запрос = СоздатьОбъект("Запрос");
ТекстЗапроса =
"//{{ЗАПРОС(Сформировать)
|Период с ПериодС по ПериодПО;
|ВладелецЗап = Справочник.ГрафКалендарь.Владелец;
|СпрЗап = Справочник.ГрафКалендарь.ТекущийЭлемент;
|Группировка СпрЗап Упорядочить по СпрЗап.ДатаРеестр без групп;
|Условие (СпрЗап.ПометкаУдаления()=0);
|Условие (Запрос.ВладелецЗап = Название);
|Без итогов;
|"//}}ЗАПРОС
;
// Этот цикл работает
Пока Запрос.Группировка() = 1 Цикл
Сообщить("ДатаРеестр " + Запрос.СпрЗап.ДатаРеестр);
КонецЦикла;
// А такой вызов для обратной сортировки возвращает пустую выборку
// Пока Запрос.Группировка(1, -1) = 1 Цикл
// ...
// КонецЦикла;
Основной вопрос: почему вызов Запрос.Группировка(1, -1) не работает, хотя синтаксически он верен? Причина кроется в одной из опций самого запроса.
Выясним причину проблемы. Внутренний механизм выполнения запросов в 1С 7.7 имеет свои особенности. Одна из них — это взаимосвязь между расчетом итогов и возможностью обратного обхода группировок.
Когда в тексте запроса присутствует директива Без итогов;, платформа использует оптимизированный, "облегченный" алгоритм получения данных. Этот алгоритм работает быстрее, но имеет ограничения: он не поддерживает некоторые возможности, включая обратный обход результата. Именно поэтому вызов Запрос.Группировка(1, -1) с опцией Без итогов не дает результата.
Чтобы заставить обратную сортировку работать, необходимо включить полновесный механизм обработки запроса. Сделаем это по шагам:
Без итогов; из текста запроса. Это активирует механизм расчета итогов, который корректно обрабатывает параметр направления сортировки.Группировка() с двумя параметрами. Первый параметр — номер или имя группировки (в нашем случае она одна, поэтому ее номер 1). Второй параметр — направление обхода: 1 для прямого (по умолчанию) и -1 для обратного.Исправленный вариант кода будет выглядеть так:
Запрос = СоздатьОбъект("Запрос");
ТекстЗапроса =
"//{{ЗАПРОС(Сформировать)
|Период с ПериодС по ПериодПО;
|ВладелецЗап = Справочник.ГрафКалендарь.Владелец;
|СпрЗап = Справочник.ГрафКалендарь.ТекущийЭлемент;
|Группировка СпрЗап Упорядочить по СпрЗап.ДатаРеестр без групп;
|Условие (СпрЗап.ПометкаУдаления()=0);
|Условие (Запрос.ВладелецЗап = Название);
//|Без итогов; // <-- УБИРАЕМ ЭТУ СТРОКУ
|"//}}ЗАПРОС
;
Если Запрос.Выполнить(ТекстЗапроса) = 0 Тогда
Возврат;
КонецЕсли;
// Теперь обратный обход будет работать
Пока Запрос.Группировка(1, -1) = 1 Цикл
Сообщить("ДатаРеестр " + Запрос.СпрЗап.ДатаРеестр);
КонецЦикла;
Важный момент: после удаления опции Без итогов в результате запроса может появиться дополнительная итоговая строка по группировке. В цикле ее можно проигнорировать, если итоги не нужны для дальнейшей обработки.
Хотя первый способ решает проблему, существует более универсальный и предсказуемый подход — выгрузка результата запроса в ТаблицуЗначений (ТЗ) и ее последующая сортировка. Этот метод является предпочтительным во многих случаях.
Преимущества этого подхода:
Без итогов. Она всегда работает одинаково.Упорядочить по. Запрос отвечает только за отбор данных, а сортировка — это уже задача клиентского кода.Разберем реализацию этого метода по шагам:
ТаблицаЗначений с помощью метода Запрос.Выгрузить().ТЗ.Сортировать(). Для сортировки по убыванию к имени колонки добавляется знак "минус" (-).Посмотрим на пример кода:
// 1. Упрощаем запрос, убираем сортировку
Запрос = СоздатьОбъект("Запрос");
ТекстЗапроса =
"//{{ЗАПРОС(Сформировать)
|ВладелецЗап = Справочник.ГрафКалендарь.Владелец;
|СпрЗап = Справочник.ГрафКалендарь.ТекущийЭлемент;
|ДатаРеестр = Справочник.ГрафКалендарь.ДатаРеестр;
|Группировка СпрЗап без групп; // Убрали 'Упорядочить по'
|Условие (ВладелецЗап = Конт); // Используем переменную Конт для условия
|Обрабатывать НеПомеченныеНаУдаление; // Более эффективная проверка
|"//}}ЗАПРОС
;
Если Запрос.Выполнить(ТекстЗапроса) = 0 Тогда
Возврат;
КонецЕсли;
// 2. Выгружаем результат в ТаблицуЗначений
ТЗ = СоздатьОбъект("ТаблицаЗначений");
Запрос.Выгрузить(ТЗ);
// 3. Сортируем ТЗ по убыванию даты
ТЗ.Сортировать("ДатаРеестр-"); // Знак "-" означает обратный порядок
// 4. Перебираем отсортированную таблицу
ТЗ.ВыбратьСтроки();
Пока ТЗ.ПолучитьСтроку() = 1 Цикл
Сообщить("ДатаРеестр " + ТЗ.ДатаРеестр);
КонецЦикла;
В процессе анализа исходной проблемы были выявлены и другие моменты, которые стоит улучшить в запросе для повышения его эффективности и читаемости:
Условие (СпрЗап.ПометкаУдаления()=0), которое проверяется для каждой строки, лучше использовать специальную директиву Обрабатывать НеПомеченныеНаУдаление;. Она работает на более низком уровне и значительно быстрее.Период с ... по ... предназначена для работы с периодическими данными (документами, регистрами). При запросах к обычным справочникам она не имеет смысла и ее следует удалить.Выполнить(). Например, Запрос.УстановитьПараметр("НужныйВладелец", Название) и в тексте запроса Условие (ВладелецЗап = НужныйВладелец).В итоге, выбор между двумя способами сортировки зависит от задачи. Если нужна простая однократная сортировка и вы готовы отказаться от опции Без итогов, первый метод вполне подойдет. Однако для более сложных и гибких решений, а также для поддержания чистоты кода, рекомендуется использовать второй метод с выгрузкой данных в ТаблицуЗначений.