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

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

В практике разработки на платформе 1С:Предприятие часто возникает задача выборки данных из документа, который содержит несколько табличных частей. Классический пример — документ выплаты зарплаты (например, «Ведомость в банк» или «Ведомость в кассу» в конфигурациях ЗУП), где начисления находятся в одной табличной части (Зарплата), а удержанный налог — в другой (НДФЛ). Подобные задачи регулярно встречаются при разработке отчетов, таких как начисления, НДФЛ и взносы за период для ЗУП 3.1. Нам необходимо получить плоскую таблицу, где для каждого сотрудника в одной строке будут выведены и сумма к выплате, и сумма налога.

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

Постановка задачи

Имеем документ с двумя табличными частями:

  1. Зарплата: содержит поля ФизическоеЛицо, КВыплате, НомерЛицевогоСчета.
  2. НДФЛ: содержит поля ФизическоеЛицо, Сумма (налог), СуммаСПревышения.

Нам необходимо получить выборку, которая часто требуется для анализа НДФЛ по документам начислений за месяц:

Вариант 1: Использование Левого соединения (LEFT JOIN)

Самый простой способ, который приходит на ум, — выбрать основную таблицу (например, Зарплата) и присоединить к ней таблицу НДФЛ. Давайте проанализируем запрос, предложенный в ходе обсуждения.


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

Преимущества: Простота реализации. Если мы уверены, что строки по НДФЛ всегда сопровождаются строками выплаты, этот метод сработает.

Недостатки:

  1. Если у сотрудника есть запись в таблице НДФЛ (например, возврат налога или удержание без выплаты), но нет строки в таблице Зарплата, этот сотрудник не попадет в выборку.
  2. Если у сотрудника в одной из таблиц несколько строк (например, две строки в Зарплата по разным основаниям и одна в НДФЛ), данные могут "задвоиться" при соединении, исказив итоговые суммы.

Вариант 2: Использование Полного соединения (FULL JOIN)

Чтобы не потерять данные из второй таблицы (в нашем случае НДФЛ), если их нет в первой, следует использовать ПОЛНОЕ СОЕДИНЕНИЕ. Это позволит выбрать записи, присутствующие в любой из таблиц.

При использовании полного соединения важно помнить про поля, по которым идет объединение (в данном случае ФизическоеЛицо). Поскольку запись может отсутствовать в одной из таблиц, просто выбрать поле Таблица1.ФизическоеЛицо нельзя — оно может оказаться NULL. Необходимо использовать функцию ЕСТЬNULL.

Рассмотрим скорректированный пример с использованием агрегатных функций для корректного суммирования:


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

Обратите внимание на то, что вложенный запрос по НДФЛ предварительно группируется. Это критически важный момент, который мы разберем далее.

Проблема "Декартова произведения" (Задвоения)

Самая опасная ошибка при соединении двух табличных частей — возникновение дублей.

Представим ситуацию:

Если мы соединим эти таблицы напрямую по ФизическоеЛицо, СУБД сопоставит каждую строку из первой таблицы с каждой строкой из второй. В результате мы получим 2 * 2 = 4 строки. При суммировании итогов суммы увеличатся в 2 раза. Для визуального контроля и отладки сложных выборок удобно использовать такой инструмент, как Консоль запросов УФ, которая позволяет быстро анализировать промежуточные результаты.

Решение: Перед соединением необходимо свернуть (сгруппировать) каждую табличную часть до уникальных ключей (в нашем случае — до ФизЛица). То есть сначала мы делаем два вложенных запроса (или две временные таблицы), где группируем данные по ФизЛицу, и только потом соединяем полученные результаты.

Вариант 3: Объединение (UNION ALL) с последующей группировкой

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

Алгоритм действий следующий:

  1. Выбираем данные из ТЧ Зарплата, проставляя 0 или NULL в полях для НДФЛ.
  2. Выбираем данные из ТЧ НДФЛ, проставляя 0 или NULL в полях для Зарплаты.
  3. Объединяем эти выборки через ОБЪЕДИНИТЬ ВСЕ.
  4. Помещаем результат во временную таблицу или вложенный запрос.
  5. Группируем общий результат по ФизическоеЛицо, суммируя показатели.

ВЫБРАТЬ
    ОбщаяТаблица.ФизическоеЛицо КАК ФизическоеЛицо,
    МАКСИМУМ(ОбщаяТаблица.НомерЛицевогоСчета) КАК НомерЛицевогоСчета,
    СУММА(ОбщаяТаблица.КВыплате) КАК КВыплате,
    СУММА(ОбщаяТаблица.НДФЛ) КАК НДФЛ
ИЗ
    (ВЫБРАТЬ
        ТЧ_Зарплата.ФизическоеЛицо КАК ФизическоеЛицо,
        ТЧ_Зарплата.НомерЛицевогоСчета КАК НомерЛицевогоСчета,
        ТЧ_Зарплата.КВыплате КАК КВыплате,
        0 КАК НДФЛ
    ИЗ
        Документ.ВедомостьНаВыплатуЗарплатыВБанк.Зарплата КАК ТЧ_Зарплата
    ГДЕ
        ТЧ_Зарплата.Ссылка = &Ссылка
    
    ОБЪЕДИНИТЬ ВСЕ
    
    ВЫБРАТЬ
        ТЧ_НДФЛ.ФизическоеЛицо,
        NULL, // Номера счета тут нет
        0,    // Суммы к выплате тут нет
        ТЧ_НДФЛ.Сумма
    ИЗ
        Документ.ВедомостьНаВыплатуЗарплатыВБанк.НДФЛ КАК ТЧ_НДФЛ
    ГДЕ
        ТЧ_НДФЛ.Ссылка = &Ссылка) КАК ОбщаяТаблица

СГРУППИРОВАТЬ ПО
    ОбщаяТаблица.ФизическоеЛицо

В этом примере мы используем функцию МАКСИМУМ для поля НомерЛицевогоСчета, так как в строках из таблицы НДФЛ оно равно NULL. Если у человека один лицевой счет, функция вернет его корректно.

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

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

  1. Никогда не соединяйте табличные части напрямую, если отношение строк "многие ко многим" возможно. Всегда предварительно группируйте данные во временных таблицах или вложенных запросах.
  2. Используйте ПОЛНОЕ СОЕДИНЕНИЕ, если важно не потерять данные ни из одной таблицы.
  3. При использовании ПОЛНОЕ СОЕДИНЕНИЕ не забывайте про конструкцию ЕСТЬNULL для полей связи.
  4. Если вам не нужно жесткое сопоставление строк, метод с ОБЪЕДИНИТЬ ВСЕ и последующей группировкой является самым безопасным с точки зрения избежания дублей.

В современных конфигурациях 1С (например, ЗУП 3.1) в табличных частях часто присутствует поле ИдентификаторСтроки. Если оно есть в обеих табличных частях и заполнено корректно (то есть налог привязан к конкретной строке начисления), соединение следует выполнять именно по этому полю. Это даст наиболее точный результат без необходимости группировок.

← На главную