Почему левое соединение в запросе 1С возвращает больше строк, чем в левой таблице?

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

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

Разберем механику левого соединения

Для начала проанализируем само определение. Левое внешнее соединение означает, что в результат запроса будут включены все записи из первой (левой) таблицы. Если для строки из левой таблицы в правой таблице найдены соответствующие условию записи, они присоединяются. Если соответствий нет, поля правой таблицы заполняются значением NULL.

Ключевой момент, который часто упускают: если одной строке из левой таблицы соответствует несколько строк из правой, то строка левой таблицы будет продублирована в результате столько раз, сколько соответствий найдено в правой. Это не ошибка платформы 1С или СУБД, это фундаментальный принцип реляционной алгебры.

Рассмотрим пример из практики. Допустим, у нас есть сотрудник Иванов (1 строка в левой таблице). В правой таблице (например, список рабочих групп) Иванов числится в двух командах: «Команда А» и «Команда Б». При левом соединении по полю Сотрудник мы получим две строки:

  1. Иванов — Команда А
  2. Иванов — Команда Б

Именно эту ситуацию мы видим в коде автора вопроса:


ВЫБРАТЬ
    РаботающиеСотрудники.Сотрудник КАК Сотрудник,
    ВТ_Команда.Сотрудник КАК Сотрудник1
ИЗ
    РаботающиеСотрудники КАК РаботающиеСотрудники
ЛЕВОЕ СОЕДИНЕНИЕ ВТ_Команда КАК ВТ_Команда
    ПО РаботающиеСотрудники.Сотрудник = ВТ_Команда.Сотрудник

Если в ВТ_Команда по одному и тому же сотруднику есть две записи, итоговая таблица неизбежно «вырастет» в объеме.

Метод 1: Использование ключевого слова РАЗЛИЧНЫЕ

Если нам не важны данные из правой таблицы (например, мы просто хотим убедиться, что сотрудник входит хоть в какую-то команду, но не хотим видеть список всех команд), самым простым способом будет использование конструкции РАЗЛИЧНЫЕ (или DISTINCT в терминах SQL). Проанализируем, как изменится запрос:


ВЫБРАТЬ РАЗЛИЧНЫЕ
    КадроваяИсторияСотрудниковСрезПоследних.Сотрудник КАК Сотрудник
ИЗ
    РегистрСведений.КадроваяИсторияСотрудников.СрезПоследних КАК КадроваяИсторияСотрудниковСрезПоследних
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.Расш1_Команда КАК Расш1_Команда
    ПО (КадроваяИсторияСотрудниковСрезПоследних.Сотрудник = Расш1_Команда.Сотрудник)
ГДЕ
    КадроваяИсторияСотрудниковСрезПоследних.Сотрудник = &Сотрудник

В этом случае, сколько бы дублей ни возникло в процессе соединения, платформа отсечет повторяющиеся комбинации значений и вернет только уникальные строки. Однако помните, что РАЗЛИЧНЫЕ создает дополнительную нагрузку на СУБД, так как требует сортировки данных для поиска дублей.

Метод 2: Группировка данных (СГРУППИРОВАТЬ ПО)

Более гибкий и часто более производительный способ — это использование секции СГРУППИРОВАТЬ ПО. Это решение позволяет не только избавиться от дублей, но и выполнить агрегатные расчеты (например, посчитать количество команд, в которых состоит сотрудник).

Выясним, как применить группировку в нашей ситуации:


ВЫБРАТЬ
    РаботающиеСотрудники.Сотрудник КАК Сотрудник,
    МАКСИМУМ(ВТ_Команда.Сотрудник) КАК ПолеДляПроверки
ИЗ
    РаботающиеСотрудники КАК РаботающиеСотрудники
ЛЕВОЕ СОЕДИНЕНИЕ ВТ_Команда КАК ВТ_Команда
    ПО РаботающиеСотрудники.Сотрудник = ВТ_Команда.Сотрудник
СГРУППИРОВАТЬ ПО
    РаботающиеСотрудники.Сотрудник

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

Проблема «размножения» строк при расчете итогов

Важно понимать, что «магия» соединения опасна не только лишними строками, но и искажением числовых данных. Представим, что в левой таблице у нас сумма долга контрагента, а в правой — список его расчетных счетов. Если у контрагента 3 счета, после ЛЕВОЕ СОЕДИНЕНИЕ сумма долга в итоговом отчете увеличится в три раза, если вы просто просуммируете колонку. Это классическая ошибка при построении отчетов в СКД.

Совет: Всегда проверяйте уникальность ключей в правой таблице перед соединением. Если в правой таблице возможны дубли по полю связи, предварительно сверните эту таблицу во временную таблицу с помощью СГРУППИРОВАТЬ ПО.

Альтернатива: Использование конструкции СУЩЕСТВУЕТ

Если ваша задача — просто отфильтровать сотрудников, которые есть в правой таблице, не присоединяя оттуда данные, лучше вообще отказаться от ЛЕВОЕ СОЕДИНЕНИЕ в пользу условий. Это работает быстрее и никогда не дублирует строки левой таблицы.

Рассмотрим вариант с ГДЕ ... В (ВЫБРАТЬ ...):


ВЫБРАТЬ
    РаботающиеСотрудники.Сотрудник
ИЗ
    РаботающиеСотрудники КАК РаботающиеСотрудники
ГДЕ
    РаботающиеСотрудники.Сотрудник В (ВЫБРАТЬ Команда.Сотрудник ИЗ РегистрСведений.Расш1_Команда КАК Команда)

Или еще более производительный вариант с СУЩЕСТВУЕТ:


ВЫБРАТЬ
    РаботающиеСотрудники.Сотрудник
ИЗ
    РаботающиеСотрудники КАК РаботающиеСотрудники
ГДЕ
    СУЩЕСТВУЕТ (ВЫБРАТЬ 1 ИЗ РегистрСведений.Расш1_Команда КАК Команда ГДЕ Команда.Сотрудник = РаботающиеСотрудники.Сотрудник)

Нюанс с фильтрацией в секции ГДЕ

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

Чтобы сохранить «левизну» соединения, условия на правую таблицу нужно выносить непосредственно в секцию ПО:


// Правильно для левого соединения
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.Расш1_Команда КАК Команда
    ПО РаботающиеСотрудники.Сотрудник = Команда.Сотрудник
    И Команда.Статус = ЗНАЧЕНИЕ(Перечисление.Статусы.Активен)

Подведем итоги

Для корректной работы с запросами в 1С запомните следующие правила:

  1. Левое соединение не гарантирует сохранение исходного количества строк, если в правой таблице нарушена уникальность ключа связи.
  2. Если возникли дубли — проверьте данные в правой таблице. Скорее всего, одному объекту соответствует несколько записей.
  3. Для устранения дублей используйте РАЗЛИЧНЫЕ или СГРУППИРОВАТЬ ПО.
  4. Если данные из правой таблицы не нужны для вывода, а нужны только для фильтрации, используйте ГДЕ Сотрудник В (...) или СУЩЕСТВУЕТ.
  5. Всегда следите за тем, чтобы поля, по которым идет соединение, были проиндексированы (для виртуальных таблиц это измерения, для обычных — измерения или индексированные реквизиты).

Понимание этих базовых принципов SQL позволит вам писать предсказуемые и оптимальные запросы, избегая «магических» ошибок в отчетах и алгоритмах.

← На главную