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

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

При разработке отчетов или обработок в 1С часто возникает задача собрать данные из разных источников: справочников, их табличных частей и связанных документов. Казалось бы, простой запрос с левым соединением должен вернуть корректный список, но на практике разработчики часто сталкиваются с неожиданным "размножением" строк. В этой статье мы подробно разберем, почему возникают дубли при выборке данных, проанализируем механизм работы соединений в языке запросов 1С и рассмотрим несколько способов решения этой проблемы.

Суть проблемы: Откуда берутся лишние строки?

Рассмотрим ситуацию, с которой столкнулся автор вопроса. Имеется запрос, который выбирает данные из трех источников:

  1. Табличная часть справочника ДоговорыКонтрагентов.Услуги (основная таблица).
  2. Сам справочник ДоговорыКонтрагентов (для получения реквизитов договора).
  3. Документ СчетНаОплатуПокупателю (для получения информации о выставленных счетах).

Код запроса выглядит примерно так:


ВЫБРАТЬ РАЗРЕШЕННЫЕ РАЗЛИЧНЫЕ
    СчетНаОплатуПокупателю.Ссылка КАК ДокументСчета,
    ДоговорыКонтрагентовУслуги.Номенклатура КАК Номенклатура,
    СУММА(ДоговорыКонтрагентовУслуги.Сумма) КАК КВыставлению
ИЗ
    Справочник.ДоговорыКонтрагентов.Услуги КАК ДоговорыКонтрагентовУслуги
        ЛЕВОЕ СОЕДИНЕНИЕ Справочник.ДоговорыКонтрагентов КАК ДоговорыКонтрагентов
        ПО ДоговорыКонтрагентовУслуги.Ссылка = ДоговорыКонтрагентов.Ссылка
        ЛЕВОЕ СОЕДИНЕНИЕ Документ.СчетНаОплатуПокупателю КАК СчетНаОплатуПокупателю
        ПО ДоговорыКонтрагентов.Ссылка = СчетНаОплатуПокупателю.ДоговорКонтрагента

После выгрузки результата запроса в Таблицу Значений мы видим, что одна и та же услуга по одному и тому же контрагенту повторяется несколько раз. При этом визуально строки могут казаться одинаковыми, если мы не выводим на форму все колонки.

Анализ причин: Декартово произведение

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

Представим структуру данных на примере одного договора:

Когда мы соединяем эти таблицы в одном запросе, SQL-сервер (и платформа 1С) выполняет перемножение записей. Для каждой строки из табличной части услуг он пытается найти соответствие в таблице счетов по Договору.

Результат:

Для "Услуги А" будет найдено 3 счета. Это создаст 3 строки в результате запроса.

Для "Услуги Б" также будет найдено 3 счета. Это создаст еще 3 строки.

Итог: Вместо 2 строк услуг мы получаем 6 строк (2 услуги * 3 счета). Если счетов будет 10, мы получим 20 строк.

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

Решение 1: Программное удаление дублей (Метод Свернуть)

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

Объект ТаблицаЗначений в 1С имеет встроенный метод Свернуть(). Он позволяет сгруппировать строки по указанным колонкам и просуммировать числовые показатели.

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

  1. Выполняем запрос и выгружаем результат в таблицу значений.
  2. Если колонка со ссылкой на счет (ДокументСчета) присутствует в таблице, но мешает уникальности (создает дубли), её нужно исключить из списка колонок группировки или удалить, если она не нужна.
  3. Применяем метод свертки.

Пример кода:


// Загружаем результат запроса в таблицу
ТаблицаДанных = Запрос.Выполнить().Выгрузить();

// Если нам не важен конкретный документ счета для вывода, а важны только суммы:
// Оставляем в первом параметре только те поля, которые должны быть уникальными (разрезы).
// Во втором параметре указываем поля, которые нужно сложить.
ТаблицаДанных.Свернуть("Контрагент, Договор, Номенклатура, Валюта", "КВыставлению");

// Теперь в таблице удалены дубли, а суммы по одинаковым услугам сложились.
ДанныеТФ.Загрузить(ТаблицаДанных);

Этот метод подходит, если дублирование вызвано наличием "лишних" детальных записей, которые не нужны пользователю в итоговом отчете.

Решение 2: Корректировка запроса (Агрегация счетов)

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

Для этого мы не можем соединять таблицу документов напрямую. Нам потребуется вложенный запрос или временная таблица.

Шаг 1. Сначала получим последний счет для каждого договора.


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

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

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

Решение 3: Правильная фильтрация в ЛЕВОМ СОЕДИНЕНИИ

В обсуждении проблемы упоминалась попытка отфильтровать счета по периоду. Важно помнить нюанс работы ЛЕВОЕ СОЕДИНЕНИЕ в связке с условием ГДЕ.

Ошибка:


...
ЛЕВОЕ СОЕДИНЕНИЕ Документ.СчетНаОплатуПокупателю ...
ГДЕ
    СчетНаОплатуПокупателю.Дата МЕЖДУ &Нач И &Кон

Такое условие превращает левое соединение во внутреннее. Если у договора нет счетов в этом периоде, то поле Дата будет NULL. Условие NULL МЕЖДУ... вернет ЛОЖЬ, и вся строка (включая договор и услугу) исчезнет из выборки.

Правильный подход:

Условия фильтрации присоединяемой таблицы (правой таблицы в левом соединении) нужно писать внутри оператора ПО.


...
ИЗ
    Справочник.ДоговорыКонтрагентов.Услуги КАК Услуги
ЛЕВОЕ СОЕДИНЕНИЕ Документ.СчетНаОплатуПокупателю КАК Счет
ПО Услуги.Ссылка = Счет.ДоговорКонтрагента
   И Счет.Проведен = ИСТИНА
   И Счет.Дата МЕЖДУ &НачалоПериода И &КонецПериода

Однако, даже этот способ не уберет дубли, если в выбранный период попадает более одного счета. Он лишь ограничит количество дублей.

Рекомендация по архитектуре

Важно отметить, что получение данных о задолженностях или "суммах к выставлению" путем анализа самих документов — не самая оптимальная практика в 1С. Документы могут перепроводиться, меняться, их может быть очень много, что замедляет работу.

Для хранения итоговых цифр в 1С существуют Регистры накопления (например, "Взаиморасчеты с контрагентами", "Продажи").

Правильный архитектурный подход:

  1. Обращаться к виртуальной таблице РегистрНакопления.ВзаиморасчетыСКонтрагентами.Остатки.
  2. Эта таблица автоматически выдает уже свернутые итоги без дублей.
  3. Если необходима детализация до номенклатуры (услуги), стоит проверить, есть ли соответствующее измерение в регистре, или использовать регистр "Продажи".

В текущей задаче автора, если целью является просто формирование печатной формы или реестра, использование метода Свернуть() (Решение 1) будет самым простым и эффективным способом исправить ситуацию "здесь и сейчас".

← На главную