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