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