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