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