При разработке сложных отчетов или доработке существующих механизмов в конфигурациях (в данном примере — «Управление торговлей 10.3»), программисты часто сталкиваются с диагностическим сообщением системы: «Нельзя использовать производные от полей составного типа в выражениях, не присутствующих полностью в предложении СГРУППИРОВАТЬ ПО». Эта ошибка блокирует выполнение запроса и часто вводит в ступор, так как простое добавление поля в группировку не всегда решает проблему.
В этой статье мы подробно разберем, почему возникает эта ситуация, проанализируем техническую подоплеку работы платформы 1С с составными типами данных и рассмотрим три эффективных способа решения задачи.
Разберем ситуацию на примере из практики. Автор темы изменил измерение ДокументПланирования в регистре накопления ПланируемыеПоступленияДенежныхСредств, сделав его составным (добавив, например, тип Документ.ЗаказПокупателя). В тексте запроса происходит обращение к реквизиту этого поля через точку: ПланируемыеДвиженияОстатки.ДокументПланирования.ВалютаДокумента.
Когда поле имеет составной тип, платформа 1С на уровне СУБД выполняет неявное левое соединение (LEFT JOIN) со всеми таблицами тех объектов, которые входят в этот тип. Если мы пытаемся сгруппировать результат по самому составному полю (ссылке), но в секции ВЫБРАТЬ используем его реквизиты внутри конструкций ВЫБОР или ВЫРАЗИТЬ, возникает конфликт. Система не может однозначно сопоставить «разыменованное» поле в выборке с полем группировки, так как для СУБД это разные колонки из разных таблиц.
Особенно остро эта проблема проявляется, когда обращение через точку используется внутри агрегатных функций или сложных логических условий.
Самый правильный и производительный способ решения проблемы — это подсказать платформе, к какому именно типу данных мы обращаемся. Это избавляет систему от необходимости строить соединения со всеми возможными таблицами составного типа.
Проанализируем, как это применить в коде. Вместо прямого обращения к реквизиту составного поля, мы используем конструкцию ВЫРАЗИТЬ:
ВЫБРАТЬ
СУММА(ПланируемыеОстатки.СуммаОстаток) КАК Сумма,
ВЫРАЗИТЬ(ПланируемыеОстатки.ДокументПланирования КАК Документ.ЗаказПокупателя).ВалютаДокумента КАК Валюта
ИЗ
РегистрНакопления.ПланируемыеПоступленияДенежныхСредств.Остатки КАК ПланируемыеОстатки
СГРУППИРОВАТЬ ПО
ВЫРАЗИТЬ(ПланируемыеОстатки.ДокументПланирования КАК Документ.ЗаказПокупателя).ВалютаДокумента
Важный момент: если вы используете выражение с ВЫРАЗИТЬ в секции ВЫБРАТЬ, оно должно быть точно таким же в секции СГРУППИРОВАТЬ ПО. Это позволяет оптимизатору запросов корректно сопоставить поля.
Если логика запроса слишком сложна (как в исходном сообщении темы, где присутствуют множественные проверки условий в блоке ВЫБОР), использование ВЫРАЗИТЬ в каждой строке может сделать код нечитаемым. В этом случае мы рекомендуем использовать механизм временных таблиц. Это «золотой стандарт» профессиональной разработки на 1С.
Рассмотрим алгоритм действий:
СУММА, МИНИМУМ и т.д.) уже по плоским полям.Пример реализации такой структуры:
// Шаг 1: Подготовка данных
ВЫБРАТЬ
ПланируемыеОстатки.ДокументПланирования КАК ДокументСсылка,
ВЫРАЗИТЬ(ПланируемыеОстатки.ДокументПланирования КАК Документ.ЗаказПокупателя).ВалютаДокумента КАК ВалютаДокумента,
ПланируемыеОстатки.СуммаОстаток КАК Сумма
ПОМЕСТИТЬ ВТ_ИсходныеДанные
ИЗ
РегистрНакопления.ПланируемыеПоступленияДенежныхСредств.Остатки КАК ПланируемыеОстатки;
// Шаг 2: Группировка и расчеты
ВЫБРАТЬ
ВТ.ДокументСсылка,
ВТ.ВалютаДокумента,
СУММА(ВТ.Сумма) КАК ИтоговаяСумма
ИЗ
ВТ_ИсходныеДанные КАК ВТ
СГРУППИРОВАТЬ ПО
ВТ.ДокументСсылка,
ВТ.ВалютаДокумента
Этот метод полностью устраняет ошибку «производных от полей составного типа», так как во втором запросе поля уже не являются производными — это обычные колонки временной таблицы.
Как отметил один из участников обсуждения (сообщение №9), в некоторых случаях (например, в динамических списках, где нельзя использовать временные таблицы) помогает «упаковка» исходного запроса во вложенный.
Выясним, как это работает. Мы выносим обращение к реквизитам «через точку» на нижний уровень вложенности, а группировку делаем на верхнем уровне. Платформа сначала получает выборку со всеми значениями, а затем агрегирует её.
Рассмотрим структуру такого запроса:
ВЫБРАТЬ
Вложенный.ДокументПланирования,
Вложенный.ВалютаДокумента,
СУММА(Вложенный.СуммаПлатежа) КАК СуммаПлатежа
ИЗ (
ВЫБРАТЬ
ПланируемыеДвижения.ДокументПланирования КАК ДокументПланирования,
ПланируемыеДвижения.ДокументПланирования.ВалютаДокумента КАК ВалютаДокумента,
ПланируемыеДвижения.СуммаПлатежа КАК СуммаПлатежа
ИЗ
РегистрНакопления.ПланируемыеПоступленияДенежныхСредств КАК ПланируемыеДвижения
) КАК Вложенный
СГРУППИРОВАТЬ ПО
Вложенный.ДокументПланирования,
Вложенный.ВалютаДокумента
Хотя этот метод рабочий, мы рекомендуем отдавать предпочтение временным таблицам (Способ №2) в обычных отчетах и обработках, так как это упрощает отладку и зачастую работает быстрее на больших объемах данных.
Важно помнить, что каждое обращение «через точку» от составного типа — это потенциальная проблема с производительностью. Если в составной тип входит 10 видов документов, СУБД придется выполнить 10 левых соединений, чтобы найти нужное значение.
Проанализируем несколько советов по оптимизации:
ВЫРАЗИТЬ, если вы точно знаете, какой тип документа должен обрабатываться в конкретном фрагменте кода.ВалютаДокумента, но у одного из документов в составе этого реквизита нет, запрос вернет NULL, что может привести к неожиданным результатам в группировках.Резюмируя ситуацию из темы: ошибка возникла из-за того, что в предложении СГРУППИРОВАТЬ ПО было указано поле ДокументПланирования, но в ВЫБРАТЬ использовалась его производная ДокументПланирования.ВалютаДокумента. После того как поле стало составным, платформа перестала «прощать» такое обращение. Применение временных таблиц или явное приведение типов — это надежный способ навсегда избавиться от подобных ошибок в разработке на платформе 1С.