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

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

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

Разберем причину «размножения» строк

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

Выясним причину на конкретном примере. Допустим, у нас есть запись в регистре Продажи по товару «Стол кухонный». Мы хотим подтянуть цену из документа Заказ покупателя. Если в этом заказе товар «Стол кухонный» был добавлен двумя разными строками (например, с разными скидками или просто по ошибке пользователя), то система найдет две подходящие пары для одной записи регистра. В итоге одна строка из регистра превратится в две строки в результате запроса. Когда мы применяем функцию СУММА, количество товара будет посчитано дважды.

Важно понимать: табличные части документов в 1С (такие как Товары) не имеют уникального индекса по паре Номенклатура + Характеристика. Пользователь может добавить одну и ту же позицию несколько раз. Именно это и создает «мину замедленного действия» при прямом соединении регистра с табличной частью документа.

Способ 1. Использование агрегатных функций (МАКСИМУМ и РАЗЛИЧНЫЕ)

Если нам нужно просто избавиться от дублей и нам не принципиально, из какой именно строки табличной части документа придет дополнительная информация (например, если мы уверены, что цена там одинаковая), мы можем воспользоваться простейшими методами агрегации.

Рассмотрим вариант с использованием ключевого слова РАЗЛИЧНЫЕ. Этот метод подходит, когда нам нужно выбрать уникальный набор данных, но он не сработает внутри запроса с группировками и суммами, так как РАЗЛИЧНЫЕ применяется ко всей результирующей строке.

Более надежный вариант — использование функций МАКСИМУМ или МИНИМУМ для тех полей, которые вызывают дублирование. Посмотрим на пример модифицированного запроса:


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

Однако помните: если в заказе две строки с разной ценой, МАКСИМУМ выберет только одну из них, что может исказить логику отчета. Поэтому перейдем к более профессиональному решению.

Способ 2. Предварительная группировка во временных таблицах

Это «золотой стандарт» разработки в 1С. Чтобы исключить умножение строк, мы должны гарантировать, что в правой таблице (которую мы присоединяем) на один ключ (связку полей) приходится строго одна строка. Для этого сначала выделим данные табличной части во временную таблицу и сгруппируем их.

Разберем процесс по шагам:

  1. Выбираем необходимые данные из табличной части документа во временную таблицу.
  2. Группируем их по полям связи (Ссылка, Номенклатура, Характеристика).
  3. Для ресурсов (Цена, Скидка) применяем агрегатную функцию (например, СРЕДНЕЕ или МАКСИМУМ).
  4. Индексируем временную таблицу по полям связи для ускорения работы.
  5. Соединяем основной регистр с подготовленной временной таблицей.

Посмотрим, как это выглядит в коде запроса:


// Шаг 1 и 2: Подготовка данных документа
ВЫБРАТЬ
    ЗаказТовары.Ссылка КАК Ссылка,
    ЗаказТовары.Номенклатура КАК Номенклатура,
    ЗаказТовары.ХарактеристикаНоменклатуры КАК Характеристика,
    СРЕДНЕЕ(ЗаказТовары.Цена) КАК ЦенаСредняя
ПОМЕСТИТЬ ВТ_ДанныеЗаказов
ИЗ
    Документ.ЗаказПокупателя.Товары КАК ЗаказТовары
ГДЕ
    ЗаказТовары.Ссылка.Дата МЕЖДУ &ДатаНач И &ДатаКон
СГРУППИРОВАТЬ ПО
    ЗаказТовары.Ссылка,
    ЗаказТовары.Номенклатура,
    ЗаказТовары.ХарактеристикаНоменклатуры
ИНДЕКСИРОВАТЬ ПО
    Ссылка,
    Номенклатура,
    Характеристика
;

// Шаг 5: Соединение с регистром
ВЫБРАТЬ
    Продажи.Количество КАК Количество,
    ДанныеЗаказов.ЦенаСредняя КАК Цена
ИЗ
    РегистрНакопления.Продажи КАК Продажи
        ЛЕВОЕ СОЕДИНЕНИЕ ВТ_ДанныеЗаказов КАК ДанныеЗаказов
        ПО Продажи.ЗаказПокупателя = ДанныеЗаказов.Ссылка
            И Продажи.Номенклатура = ДанныеЗаказов.Номенклатура
            И Продажи.ХарактеристикаНоменклатуры = ДанныеЗаказов.Характеристика
ГДЕ
    Продажи.Период МЕЖДУ &ДатаНач И &ДатаКон

Проанализируем ситуацию: теперь, даже если в одном заказе товар повторится десять раз, во временной таблице ВТ_ДанныеЗаказов он схлопнется в одну уникальную строку. При соединении с регистром Продажи количество строк не изменится, и итоговая сумма останется верной.

Проблема «пустых» характеристик при соединении

Частая ошибка, которую мы можем совершить — не учесть пустые значения. В системе 1С СправочникСсылка.ХарактеристикиНоменклатуры.ПустаяСсылка() — это конкретное значение. Если в регистре характеристика не заполнена (пустая), а в документе поле характеристики NULL (что бывает редко, но возможно при определенных типах соединений) или заполнено пробелом, связь не установится.

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

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

Прямое соединение с табличной частью документа, как в самом первом примере, крайне неэффективно. Табличные части в базе данных SQL индексируются по полю Ссылка, но не по полю Номенклатура. Когда мы пишем ПО Продажи.Номенклатура = ЗаказТовары.Номенклатура, SQL-серверу приходится выполнять полное сканирование (Table Scan) всей таблицы товаров всех документов, что при больших объемах данных приведет к колоссальным тормозам.

Использование временных таблиц с явным указанием ИНДЕКСИРОВАТЬ ПО решает эту проблему. Система создаст временный индекс в tempdb, и соединение произойдет мгновенно по индексному поиску.

Когда все же стоит использовать соединение напрямую?

Единственный случай, когда прямое соединение допустимо и не вызовет дублей — это связь с данными, где гарантирована уникальность. Например, соединение с самим справочником Номенклатура по полю Ссылка или с основной таблицей документа (шапкой). Там одна ссылка всегда соответствует одной строке, и «размножения» записей не произойдет.

Подведем итог: если вы видите, что сумма в отчете «поплыла» после добавления соединения — ищите дубли в правой таблице. Используйте временные таблицы с группировкой, и ваши запросы всегда будут возвращать корректные и быстрые результаты.

← На главную