Каждый разработчик 1С рано или поздно сталкивается с удивительным явлением: один и тот же запрос в консоли запросов выдает абсолютно корректные данные, но при переносе его в схему компоновки данных (СКД) результаты начинают искажаться или «схлопываться». Особенно ярко это проявляется, когда мы пытаемся получить агрегированные данные (например, количество дней отсутствия товара на складе, средние цены за определенные периоды) в пакете запросов с временными таблицами.
В этой статье мы подробно проанализируем, почему СКД может возвращать разные данные для временных таблиц (ВТ) и вложенных запросов, заглянем внутрь «черного ящика» оптимизатора компоновки и разберем проверенные способы решения этой проблемы без потери производительности СУБД.
Представим типовую задачу. Нам необходимо в рамках одного отчета получить срез последних и остатки товаров на каждый день, посчитать количество дней, когда остатки были нулевыми, и вывести среднюю цену за эти дни. Конечная группировка в отчете должна выполняться только по номенклатуре.
Логика построения запроса следующая:
Номенклатура и Дата во временную таблицу.КОЛИЧЕСТВО(РАЗЛИЧНЫЕ Дата) и вычисляем среднюю цену, группируя результат уже только по Номенклатуре (поле Дата в итоговую выборку отчета не выводится).Запустив этот запрос в обычной консоли, мы получаем верные цифры (например, товар отсутствовал 5 дней). Однако в отчете СКД поле количества дней всегда возвращает 1. Стоит нам заменить временную таблицу на обычный вложенный запрос — и данные в СКД мгновенно становятся правильными. Давайте выясним, почему так происходит.
Главный виновник этой ситуации — встроенный оптимизатор Системы компоновки данных. СКД — это не просто транслятор текста запроса в СУБД. Перед выполнением запроса СКД анализирует настройки компоновки (какие поля выбраны пользователем, какие группировки настроены в схеме, какие наложены отборы) и динамически перестраивает исходный SQL-код.
Рассмотрим, как оптимизатор обрабатывает временные таблицы и вложенные запросы:
1. Поведение при использовании временных таблиц (ВТ):
Когда мы используем пакет запросов с ВТ, СКД анализирует всю цепочку. Если во временной таблице мы используем поле Дата для группировки, но в конечном запросе пакета (и в настройках самого отчета) это поле не выбрано пользователем напрямую и не используется в группировках отчета, оптимизатор СКД принимает решение, что это поле «лишнее» и его извлечение нецелесообразно.
В итоге СКД выполняет следующие действия:
Дата из секции ВЫБРАТЬ временной таблицы.СГРУППИРОВАТЬ ПО этой же временной таблицы.В результате наша детальная временная таблица по дням «схлопывается» до группировки только по номенклатуре еще на этапе выполнения промежуточного запроса. Расчет КОЛИЧЕСТВО(РАЗЛИЧНЫЕ Дата) превращается в подсчет по уже сгруппированной таблице, где на одну номенклатуру осталась ровно одна строка. Результат закономерно равен единице.
2. Поведение при использовании вложенных запросов:
Оптимизатор СКД работает с вложенными запросами гораздо аккуратнее. Ему значительно сложнее «вырезать» поле из структуры вложенного запроса, не нарушив синтаксическую корректность всего SQL-выражения. В большинстве случаев СКД оставляет структуру вложенного запроса нетронутой. Именно поэтому замена ВТ на вложенный запрос временно решает проблему — группировка внутри вложенного запроса сохраняется, и расчет дней проходит корректно.
Несмотря на то, что замена ВТ на вложенный запрос кажется простым и быстрым решением, использовать его в реальных задачах (особенно при работе со сложными отчетами на больших базах данных) крайне не рекомендуется по следующим причинам:
Nested Loops вместо быстрого Hash Match).Номенклатуре). Вложенный запрос проиндексировать невозможно, что приведет к колоссальному падению производительности при соединении с другими таблицами пакета.Рассмотрим правильные и безопасные способы заставить СКД сохранять нужные нам группировки во временных таблицах.
Самый простой и методологически верный способ запретить СКД оптимизировать (выбрасывать) поле из временных таблиц — это явное указание его роли в наборе данных.
Разберем этот процесс по шагам:
Дата или вспомогательное поле связи).Флаг Обязательное сообщает оптимизатору СКД, что данное поле должно присутствовать в результирующем SQL-запросе всегда, независимо от того, выбрал ли его пользователь в настройках структуры отчета или нет. Группировка во временной таблице гарантированно сохранится.
Если мы хотим полностью контролировать текст запроса, который будет отправлен в СУБД, мы можем отключить механизм автоматической модификации запросов компоновщиком.
Для этого выполним следующие действия:
При отключенном автозаполнении СКД перестает вмешиваться в текст запроса, не накладывает неявные фильтры в секции временных таблиц и не удаляет группировочные поля. Однако учитывайте, что в этом случае разработчику придется вручную настраивать параметры, роли полей и доступные отборы.
Часто программисты пытаются выполнить все сложные математические расчеты (итоги, средние показатели, расчет дней) непосредственно внутри SQL-запроса. Однако СКД проектировалась с расчетом на то, что агрегатные функции должны вычисляться на уровне самого процессора компоновки данных.
Проанализируем альтернативный подход к проектированию отчета:
Количество(Различные Дата)Номенклатуре.При таком подходе СКД сама корректно сгруппирует детальные записи в оперативной памяти и посчитает итоги по ресурсам. Этот способ особенно эффективен, когда отчет строится по нескольким несвязанным регистрам.
Если по каким-то причинам установить роль поля или отключить автозаполнение невозможно, можно прибегнуть к хитрости и «обмануть» оптимизатор СКД. Для этого достаточно сделать так, чтобы оптимизатор посчитал поле используемым.
Достичь этого можно добавлением фиктивного условия в секцию ГДЕ конечного запроса пакета, где выбираются данные из временной таблицы:
ВЫБРАТЬ
ВТ_Данные.Номенклатура,
ВТ_Данные.КоличествоДней
ИЗ
ВТ_Данные КАК ВТ_Данные
ГДЕ
ВТ_Данные.Дата <> ДАТАВРЕМЯ(1, 1, 1)
Поскольку поле Дата теперь участвует в условиях фильтрации конечного запроса, оптимизатор СКД не сможет исключить его из выборки промежуточной временной таблицы ВТ_Данные, и группировка внутри ВТ останется неизменной.
Чтобы не гадать, почему расходятся данные, мы всегда можем посмотреть окончательный текст SQL-запроса, сгенерированный компоновщиком. Сделать это можно следующими способами:
ПриКомпоновкеРезультата мы можем перехватить макет компоновки и проанализировать свойство:
МакетКомпоновки.НаборыДанных.НаборДанных1.ЗапросПонимание принципов работы оптимизатора запросов СКД позволяет избежать досадных ошибок при проектировании сложных отчетов и создавать быстрые, надежные и отказоустойчивые алгоритмы получения данных в системе 1С:Предприятие.