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