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

Программист 1С v8.3 (Обычные формы) Управленческий учет Торговля и дистрибуция
← На главную

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

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

Рассмотрим ситуацию, представленную в исходном вопросе. Автор пытается получить список единиц измерения номенклатуры и соответствующие им цены. Если цены нет, он ожидает увидеть в результате строку с номенклатурой и нулевой ценой. Однако запрос возвращает только те позиции, по которым цена установлена. Выясним причину, проанализировав фрагмент кода:


ГДЕ
    ЦеныНоменклатурыСрезПоследних.ТипЦен = &ТипЦен
    И ЕдиницыИзмерения.ПометкаУдаления = ЛОЖЬ

Основная ошибка заключается в расположении условия по правой таблице (регистру цен) в секции ГДЕ. Логика работы SQL-движка (и платформы 1С) следующая:

  1. Сначала выполняется ЛЕВОЕ СОЕДИНЕНИЕ. Для тех записей справочника ЕдиницыИзмерения, по которым в регистре цен нет данных, в полях таблицы ЦеныНоменклатурыСрезПоследних (включая ТипЦен) проставляется значение NULL.
  2. Затем применяется фильтр ГДЕ. Программа проверяет условие: ЦеныНоменклатурыСрезПоследних.ТипЦен = &ТипЦен.
  3. Так как для отсутствующих цен поле ТипЦен равно NULL, сравнение NULL = &ТипЦен дает результат "Ложь" (или Unknown).
  4. В итоге все строки, где цена не была найдена, отсекаются фильтром, и левое соединение фактически превращается во ВНУТРЕННЕЕ СОЕДИНЕНИЕ.

Решение №1: Использование параметров виртуальной таблицы

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


ВЫБРАТЬ
    ЕдиницыИзмерения.Владелец КАК Номенклатура,
    ЕдиницыИзмерения.Ссылка КАК ЕдиницаИзмеренияНоменклатуры,
    ЕСТЬNULL(ЦеныНоменклатурыСрезПоследних.Цена, 0) КАК ЦенаНоменклатуры
ИЗ
    Справочник.ЕдиницыИзмерения КАК ЕдиницыИзмерения
        ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныНоменклатуры.СрезПоследних(&НаДату, ТипЦен = &ТипЦен) КАК ЦеныНоменклатурыСрезПоследних
        ПО ЕдиницыИзмерения.Ссылка = ЦеныНоменклатурыСрезПоследних.ЕдиницаИзмерения
ГДЕ
    ЕдиницыИзмерения.ПометкаУдаления = ЛОЖЬ
    И ЕдиницыИзмерения.Владелец В(&СписокНоменклатуры)

В данном примере условие ТипЦен = &ТипЦен перенесено во второй параметр виртуальной таблицы СрезПоследних. Теперь система сначала выберет последние цены только для нужного типа цен, а затем присоединит их к единицам измерения. Если цены для конкретной единицы не найдется, фильтр в ГДЕ уже не удалит эту строку, так как он проверяет только поля левой таблицы.

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

Если по каким-то причинам вы используете не виртуальную, а физическую таблицу (или если нужно наложить специфическое условие на соединение), условие по правой таблице следует переносить из блока ГДЕ в блок ПО. Рассмотрим, как это меняет логику:


ИЗ
    Справочник.Номенклатура КАК Номенклатура
        ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныНоменклатуры КАК Цены
        ПО Номенклатура.Ссылка = Цены.Номенклатура
        И Цены.ТипЦен = &ТипЦен

В этом случае условие Цены.ТипЦен = &ТипЦен является частью правила соединения. Если условие не выполняется, правая часть просто не присоединится (будет NULL), но сама строка из левой таблицы (Номенклатура) останется в результате.

Работа с отсутствующими значениями (NULL)

Чтобы в отчете или выборке вместо пустых ячеек отображались осмысленные данные (например, ноль), необходимо использовать функцию ЕСТЬNULL. Посмотрим на пример использования:

ЕСТЬNULL(ЦеныНоменклатурыСрезПоследних.Цена, 0) КАК Цена

Эта функция проверяет первое значение, и если оно равно NULL, возвращает второе (в данном случае — 0). Это критически важно для дальнейших математических операций в запросе, так как любые арифметические действия с NULL всегда дают NULL.

Оптимизация структуры: от чего "плясать"?

Проанализируем структуру данных. Если ваша задача — получить полный список товаров и их цены, то в качестве основной таблицы (первой в списке ИЗ) лучше использовать справочник Номенклатура, а не ЕдиницыИзмерения.

Выясним причину: у товара может не быть созданных единиц измерения в справочнике (если база заполнена некорректно) или вы можете ошибиться с выбором владельца. Правильная иерархия соединения выглядит так:

  1. Справочник.Номенклатура (Основной список товаров).
  2. Справочник.ЕдиницыИзмерения (Присоединяем левым соединением, чтобы получить коэффициенты).
  3. РегистрСведений.ЦеныНоменклатуры.СрезПоследних (Присоединяем левым соединением с фильтром по типу цен в параметрах).

Такой подход гарантирует, что ни один товар из входящего списка &СписокНоменклатуры не будет потерян, даже если у него не заданы ни цены, ни единицы измерения.

Полезные советы по производительности

Посмотрим на важный нюанс: фильтрация в ГДЕ по полям правой таблицы не только портит логику левого соединения, но и существенно замедляет запрос. Когда условие стоит в ГДЕ, СУБД вынуждена сначала выполнить соединение всех доступных записей, а потом "просеивать" огромный результат. Параметры виртуальной таблицы позволяют СУБД использовать индексы и работать с минимальным набором данных еще до начала операции соединения.

Также рекомендуем всегда проверять, по каким полям идет связь. В случае с ценами — это Номенклатура, ТипЦен и часто ХарактеристикаНоменклатуры. Если вы забудете указать характеристику в связи или параметрах, запрос может выдать "дубли", если у товара ведется учет по характеристикам.

← На главную