Часто на устных тестированиях и собеседованиях программистам 1С задают каверзный вопрос: что такого можно делать в последнем запросе пакета, чего нельзя делать в предыдущих его запросах? Если вы используете метод Запрос.ВыполнитьПакет(), все инструкции внутри пакета синтаксически равноправны. Однако есть ряд архитектурных нюансов платформы и контекстов использования, в которых последний запрос действительно играет уникальную роль. Давайте вместе разберем по шагам этот вопрос, проанализируем популярные заблуждения и выясним настоящие правильные ответы.
Самый частый неверный ответ, который дают разработчики — это утверждение, что в промежуточных запросах пакета нельзя использовать секции УПОРЯДОЧИТЬ ПО (сортировка) и ИТОГИ. Проверим это утверждение на практике. Рассмотрим пример, где мы выполняем сортировку и объединение не в последнем запросе:
ВЫБРАТЬ
"Реквизит1" КАК Реквизит,
1 КАК АбстрактныйНомер
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ
"Реквизит1",
2
УПОРЯДОЧИТЬ ПО
АбстрактныйНомер
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
"Реквизит1" КАК ЕщеРеквизит,
1 КАК ОпятьЖеНомер
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ
"Реквизит1",
2
УПОРЯДОЧИТЬ ПО
ОпятьЖеНомер
Данный код прекрасно отрабатывает без ошибок. Теперь посмотрим на пример с итогами:
ВЫБРАТЬ
"Реквизит1" КАК Реквизит,
2 КАК АбстрактныйНомер
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ
"Реквизит1",
1
УПОРЯДОЧИТЬ ПО
АбстрактныйНомер
ИТОГИ
КОЛИЧЕСТВО(РАЗЛИЧНЫЕ АбстрактныйНомер)
ПО
Реквизит
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
"Реквизит1" КАК ЕщеРеквизит,
2 КАК ОпятьЖеНомер
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ
"Реквизит1",
1
УПОРЯДОЧИТЬ ПО
ОпятьЖеНомер
ИТОГИ
СУММА(ОпятьЖеНомер)
ПО
ЕщеРеквизит
Как видим, и этот пакетный запрос выполняется без проблем. Таким образом, синтаксис промежуточных запросов позволяет использовать и сортировку, и итоги, и объединения.
Реальная разница начинает проявляться на этапе получения результатов выполнения пакета. Разберем ситуацию, когда в коде вызывается метод Запрос.Выполнить(), а не Запрос.ВыполнитьПакет().
В этом случае платформа 1С возвращает в качестве результата исключительно выборку последнего запроса в пакете. Все предыдущие запросы будут выполнены в фоне. Их главная и единственная задача в таком сценарии — подготовить данные (например, собрать временные таблицы). Любой прямой результат выборки (вывод таблиц без помещения во временные), написанный до точки с запятой перед последним запросом, будет попросту проигнорирован сервером 1С, и мы не сможем к нему обратиться из встроенного языка.
Проанализируем еще одну интересную особенность, о которой упомянули на форуме. Выборка табличной части во вложенную выборку (когда колонка результата содержит вложенную таблицу) имеет смысл и корректно возвращается в код только в том случае, если этот результат является конечным. Опять же, это тесно связано с использованием метода Выполнить(). В промежуточных запросах формирование вложенных таблиц не даст практического результата для дальнейшей обработки программным кодом, если результат не является финальной возвращаемой таблицей.
Рассмотрим использование пакетных запросов в качестве источника данных (набора данных) для СКД. Здесь платформа накладывает жесткое концептуальное ограничение:
ВЫБРАТЬ и возвращать итоговый набор данных.ПОМЕСТИТЬ ИмяТаблицы).Выясним причину этого ограничения. Результатом выполнения запроса с ключевым словом ПОМЕСТИТЬ является техническая таблица, состоящая всего из одной строки и одной колонки "Количество" (отражающая число записей, помещенных во временную таблицу). Если такой запрос окажется последним в наборе данных СКД, система попытается построить отчет именно по этой единственной цифре, игнорируя все подготовленные ранее данные. Кроме того, внутренние алгоритмы оптимизации СКД всегда ориентируются на структуру полей финальной выборки для генерации конечного макета компоновки.
Разберем то, как платформа управляет оперативной памятью сервера при работе с пакетными запросами. Если мы не используем явную привязку объекта МенеджерВременныхТаблиц к нашему запросу, 1С создает неявный (локальный) менеджер автоматически.
Область видимости для созданных временных таблиц открывается перед выполнением первой инструкции пакета. А вот закрывается она (с полной очисткой таблиц из памяти) сразу после выполнения последнего запроса пакета. Это означает, что последний запрос — это финальная точка, в которой данные, накопленные в промежуточных временных таблицах без явного менеджера, еще физически существуют. После выполнения пакета обратиться к ним будет невозможно.
Проанализируем ситуацию с возникновением ошибок. Несмотря на то, что запросы объединены в один текст (один пакет), платформа не оборачивает их выполнение в единую транзакцию автоматически.
Если во время выполнения последнего запроса произойдет критическая ошибка (например, деление на ноль или конфликт типов), выполнение пакета прервется. Однако все изменения, которые совершили предыдущие запросы (например, создание временных таблиц или, в более широком смысле, блокировки, если таковые были инициированы неявно), не откатываются автоматически на начало пакета.
Чтобы пакетный запрос выполнялся строго атомарно (выполнился полностью до последнего запроса включительно, либо не выполнилось ничего при ошибке на любом этапе), необходимо явно использовать в коде конструкцию НачатьТранзакцию() перед вызовом метода Запрос.ВыполнитьПакет() и ЗафиксироватьТранзакцию() после него.
Подведем итоги: если на собеседовании вас спросят про уникальность последнего запроса в пакете, смело отбрасывайте варианты с синтаксисом (итоги, сортировки, объединения). Правильный ответ кроется в архитектуре получения данных: особенности метода Запрос.Выполнить(), строгие правила финальной выборки для СКД и границы жизненного цикла временных таблиц (при отсутствии внешнего менеджера).