Почему полное соединение в запросе 1С не выводит все записи и как это исправить?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

При работе с запросами в 1С разработчики часто сталкиваются с ситуацией, когда ПОЛНОЕ СОЕДИНЕНИЕ (Full Join) работает некорректно: данные из одной таблицы пропадают, если в другой таблице нет соответствующих записей. На первый взгляд кажется, что платформа игнорирует правила соединения, но на самом деле причина кроется в логике обработки значений NULL в секции ГДЕ.

В этой статье мы подробно разберем, почему это происходит, и рассмотрим несколько эффективных способов решения проблемы — от простых правок условий до профессионального использования временных таблиц.

Выясним причину: почему ПОЛНОЕ СОЕДИНЕНИЕ превращается во ВНУТРЕННЕЕ?

Давайте проанализируем ситуацию. Когда мы используем ПОЛНОЕ СОЕДИНЕНИЕ, система пытается сопоставить строки из двух таблиц (например, Док1 и Док2) по ключевым полям. Если соответствие не найдено, в полях отсутствующей таблицы появляется значение NULL.

Проблема возникает в секции ГДЕ. Если мы пишем условие типа:


ГДЕ
    Док1.Проведен = ИСТИНА
    И Док2.Проведен = ИСТИНА

Происходит следующее: если для записи из Док1 не нашлось пары в Док2, все поля Док2 примут значение NULL. Условие NULL = ИСТИНА всегда дает результат ЛОЖЬ. В итоге вся строка исключается из итоговой выборки. Таким образом, жесткие условия в секции ГДЕ фактически превращают ваше полное соединение во ВНУТРЕННЕЕ, оставляя только те строки, которые есть в обеих таблицах одновременно и удовлетворяют фильтрам.

Решение №1: Перенос условий в секцию соединения ПО

Самый простой и быстрый способ исправить запрос — перенести все фильтры, относящиеся к конкретным таблицам, непосредственно в условие связи ПО. Это заставит систему сначала отфильтровать данные при соединении, не затрагивая общий результат выборки.

Рассмотрим, как должен выглядеть исправленный запрос из примера:


ВЫБРАТЬ
    Док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-сервера такие условия внутри соединения иногда могут привести к неоптимальному плану выполнения, если таблицы очень большие.

Решение №2: Проверка на NULL в секции ГДЕ

Если вы все же хотите оставить условия в секции ГДЕ, необходимо явно разрешить системе пропускать записи, в которых одна из сторон соединения отсутствует. Для этого используется проверка ЕСТЬ NULL.

Разберем логику построения такого условия:


ГДЕ
    (Док1.Ссылка ЕСТЬ NULL ИЛИ (Док1.Проведен = ИСТИНА И Док1.Дата МЕЖДУ &Дата1 И &Дата2))
    И (Док2.Ссылка ЕСТЬ NULL ИЛИ (Док2.Проведен = ИСТИНА И Док2.Дата МЕЖДУ &Дата1 И &Дата2))

Здесь мы говорим запросу: "Выведи строку, если либо данные первой таблицы соответствуют фильтрам, либо данных первой таблицы просто не существует (она NULL)". Это позволяет сохранить все записи, но делает блок ГДЕ громоздким.

Решение №3: Использование временных таблиц (Лучшая практика)

Профессиональный подход к разработке на 1С подразумевает разделение сложных задач на этапы. Вместо того чтобы соединять "сырые" таблицы документов с кучей фильтров, мы сначала подготовим данные.

Проанализируем пошагово:

  1. Создаем временную таблицу ВТ_Док1, куда выбираем уже проведенные документы за нужный период и по нужному подразделению.
  2. Создаем вторую временную таблицу ВТ_Док2 с аналогичными фильтрами.
  3. В финальном запросе соединяем две маленькие, уже отфильтрованные таблицы.

Посмотрим на пример кода:


// Шаг 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.Смена

Этот метод значительно повышает производительность, так как индексы документов работают на этапе заполнения временных таблиц, а соединение происходит по уже отобранным данным.

Решение №4: ОБЪЕДИНИТЬ ВСЕ и ГРУППИРОВКА

В некоторых случаях ПОЛНОЕ СОЕДИНЕНИЕ может приводить к "размножению" строк (декартову произведению), если по ключевым полям (Сотрудник + Смена) в одной таблице несколько записей. Чтобы этого избежать, можно использовать альтернативный метод — объединение таблиц.

Рассмотрим алгоритм:

  1. Выбираем данные из Док1, заполняя поля из Док2 нулями.
  2. Используем оператор ОБЪЕДИНИТЬ ВСЕ.
  3. Выбираем данные из Док2, заполняя поля из Док1 нулями.
  4. Группируем результат по ключевым полям (Сотрудник, Смена), суммируя числовые показатели.

Этот способ часто работает быстрее ПОЛНОЕ СОЕДИНЕНИЕ и легче поддается отладке, так как логика фильтрации в каждом блоке ОБЪЕДИНИТЬ изолирована.

Важные советы по оптимизации

При написании таких запросов обратите внимание на следующие моменты:

Используя эти методы, вы сможете строить надежные и быстрые отчеты, которые корректно обрабатывают данные даже при отсутствии записей в одной из таблиц.

← На главную