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