Как правильно выполнить соединение с табличной частью документа в запросе по условию

Программист Управленческий учет
← На главную

В процессе разработки отчетов или обработок в системе 1С:Предприятие (особенно в таких конфигурациях, как УПП или КА 1.1) программисты часто сталкиваются с ситуацией, когда данные в регистрах накопления заполнены не полностью. Типичный пример: в регистре ВзаиморасчетыСКонтрагентами поле Сделка может быть пустым для платежных документов, хотя аналитика по заказам ведется. В этом случае возникает необходимость «дотянуться» до данных, которые хранятся непосредственно в документе-регистраторе, а именно в его табличной части РасшифровкаПлатежа.

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

Проблема использования внутреннего соединения

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

Правильным подходом здесь будет использование ЛЕВОГО СОЕДИНЕНИЯ. Это позволит нам сохранить все записи из регистра и дополнить их данными из документа только тогда, когда это возможно.

Решение 1: Условие типа в секции соединения

Рассмотрим наиболее производительный способ наложения условия. Вместо того чтобы проверять тип документа в секции ГДЕ или внутри конструкции ВЫБОР, мы можем указать ограничение прямо в условии связи ПО. Это позволит SQL-серверу более оптимально построить план запроса.

Проанализируем пример кода:


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

Обратите внимание на конструкцию И Взаиморасчеты.Регистратор ССЫЛКА Документ.ПлатежноеПоручениеВходящее в секции соединения. Это критически важный момент: соединение с таблицей расшифровки платежа будет предприниматься только для документов нужного типа. Для всех остальных типов документов 1С даже не будет пытаться искать соответствия в таблице расшифровки, что экономит ресурсы сервера.

Решение 2: Использование временных таблиц для оптимизации

Если выборка из регистра предполагается масштабной, прямое соединение виртуальной таблицы с табличной частью документа может «подвесить» базу данных. Выясним причину: виртуальные таблицы сами по себе являются сложными запросами к СУБД, и соединение их с физическими таблицами (ТЧ документа) на лету заставляет сервер выполнять огромный объем работы.

Рассмотрим правильный алгоритм действий:

  1. Выгрузим данные из регистра во временную таблицу.
  2. Проиндексируем временную таблицу по полю Регистратор.
  3. Выполним соединение временной таблицы с табличной частью документа.

Пример реализации через временные таблицы:


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

////////////////////////////////////////////////////////////////////////////////
// Шаг 2: Индексация для ускорения поиска
ИНДЕКСИРОВАТЬ ПО
    Регистратор
;

////////////////////////////////////////////////////////////////////////////////
// Шаг 3: Соединение с получением данных из ТЧ
ВЫБРАТЬ
    ВТ.Контрагент,
    ВЫБОР
        КОГДА ВТ.Сделка = ЗНАЧЕНИЕ(Документ.ЗаказПокупателя.ПустаяСсылка) 
              ИЛИ ВТ.Сделка ЕСТЬ NULL 
              ИЛИ ВТ.Сделка = НЕОПРЕДЕЛЕНО
            ТОГДА ТЧ.Сделка
        ИНАЧЕ ВТ.Сделка
    КОНЕЦ КАК Заказ,
    ВТ.СуммаВзаиморасчетовОборот
ИЗ
    ВТ_Обороты КАК ВТ
        ЛЕВОЕ СОЕДИНЕНИЕ Документ.ПлатежноеПоручениеВходящее.РасшифровкаПлатежа КАК ТЧ
        ПО ВТ.Регистратор = ТЧ.Ссылка
        И ВТ.Регистратор ССЫЛКА Документ.ПлатежноеПоручениеВходящее

Проблема дублирования строк при соединении с табличной частью

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

Если в регистре накопления в УПП данные уже детализированы до каждой строки платежки (а это часто бывает так при включенном ведении расчетов по документам), то простое соединение по Регистратор = ТЧ.Ссылка может привести к тому, что на каждую строку из регистра наложится каждая строка из ТЧ. В итоге суммы в отчете «задвоятся» или «затроятся».

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


ВЫБРАТЬ
    ВТ.Контрагент,
    МАКСИМУМ(ЕСТЬNULL(ТЧ.Сделка, ВТ.Сделка)) КАК Заказ,
    ВТ.Регистратор,
    СУММА(ВТ.СуммаВзаиморасчетовОборот) КАК Сумма
ИЗ
    ВТ_Обороты КАК ВТ
...
СГРУППИРОВАТЬ ПО
    ВТ.Контрагент,
    ВТ.Регистратор

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

Решение 3: Разделение запроса через ОБЪЕДИНИТЬ ВСЕ

Если логика получения «Сделки» для разных типов документов сильно различается, или если соединение с ТЧ вызывает серьезные тормоза, мы можем применить архитектурный подход ОБЪЕДИНИТЬ ВСЕ.

Разберем, как это работает:

  1. В первом подзапросе мы выбираем все документы, кроме платежек. Здесь мы берем Сделку напрямую из регистра.
  2. Во втором подзапросе мы выбираем только платежки и соединяем их с их расшифровкой.

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

ОБЪЕДИНИТЬ ВСЕ

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

Этот метод часто оказывается самым быстрым, так как SQL-сервер получает два простых и понятных плана выполнения вместо одного сложного с кучей условий ВЫБОР и необязательных соединений.

Резюме и рекомендации

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

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

← На главную