При разработке отчетов или обработок в 1С часто возникает задача собрать данные из разных источников: справочников, их табличных частей и связанных документов. Казалось бы, простой запрос с левым соединением должен вернуть корректный список, но на практике разработчики часто сталкиваются с неожиданным "размножением" строк. В этой статье мы подробно разберем, почему возникают дубли при выборке данных, проанализируем механизм работы соединений в языке запросов 1С и рассмотрим несколько способов решения этой проблемы.
Рассмотрим ситуацию, с которой столкнулся автор вопроса. Имеется запрос, который выбирает данные из трех источников:
ДоговорыКонтрагентов.Услуги (основная таблица).ДоговорыКонтрагентов (для получения реквизитов договора).СчетНаОплатуПокупателю (для получения информации о выставленных счетах).Код запроса выглядит примерно так:
ВЫБРАТЬ РАЗРЕШЕННЫЕ РАЗЛИЧНЫЕ
СчетНаОплатуПокупателю.Ссылка КАК ДокументСчета,
ДоговорыКонтрагентовУслуги.Номенклатура КАК Номенклатура,
СУММА(ДоговорыКонтрагентовУслуги.Сумма) КАК КВыставлению
ИЗ
Справочник.ДоговорыКонтрагентов.Услуги КАК ДоговорыКонтрагентовУслуги
ЛЕВОЕ СОЕДИНЕНИЕ Справочник.ДоговорыКонтрагентов КАК ДоговорыКонтрагентов
ПО ДоговорыКонтрагентовУслуги.Ссылка = ДоговорыКонтрагентов.Ссылка
ЛЕВОЕ СОЕДИНЕНИЕ Документ.СчетНаОплатуПокупателю КАК СчетНаОплатуПокупателю
ПО ДоговорыКонтрагентов.Ссылка = СчетНаОплатуПокупателю.ДоговорКонтрагента
После выгрузки результата запроса в Таблицу Значений мы видим, что одна и та же услуга по одному и тому же контрагенту повторяется несколько раз. При этом визуально строки могут казаться одинаковыми, если мы не выводим на форму все колонки.
Давайте разберем механику работы ЛЕВОЕ СОЕДИНЕНИЕ в данном контексте. Проблема кроется в структуре данных "один-ко-многим" (One-to-Many), которая здесь применяется дважды.
Представим структуру данных на примере одного договора:
Когда мы соединяем эти таблицы в одном запросе, SQL-сервер (и платформа 1С) выполняет перемножение записей. Для каждой строки из табличной части услуг он пытается найти соответствие в таблице счетов по Договору.
Результат:
Для "Услуги А" будет найдено 3 счета. Это создаст 3 строки в результате запроса.
Для "Услуги Б" также будет найдено 3 счета. Это создаст еще 3 строки.
Итог: Вместо 2 строк услуг мы получаем 6 строк (2 услуги * 3 счета). Если счетов будет 10, мы получим 20 строк.
Использование ключевого слова РАЗЛИЧНЫЕ в данном случае не помогает, потому что в выборку попадает поле СчетНаОплатуПокупателю.Ссылка. Поскольку ссылки на документы разные, строки уникальны с точки зрения базы данных, и они не сворачиваются.
Если вам нужно просто вывести список услуг и сумм к выставлению, и при этом детальная информация о каждом конкретном счете в этой же строке не является критичной (или вам не важно, какой именно счет подтянется), самым быстрым способом решения будет обработка результата уже после выполнения запроса.
Объект ТаблицаЗначений в 1С имеет встроенный метод Свернуть(). Он позволяет сгруппировать строки по указанным колонкам и просуммировать числовые показатели.
Алгоритм действий:
ДокументСчета) присутствует в таблице, но мешает уникальности (создает дубли), её нужно исключить из списка колонок группировки или удалить, если она не нужна.Пример кода:
// Загружаем результат запроса в таблицу
ТаблицаДанных = Запрос.Выполнить().Выгрузить();
// Если нам не важен конкретный документ счета для вывода, а важны только суммы:
// Оставляем в первом параметре только те поля, которые должны быть уникальными (разрезы).
// Во втором параметре указываем поля, которые нужно сложить.
ТаблицаДанных.Свернуть("Контрагент, Договор, Номенклатура, Валюта", "КВыставлению");
// Теперь в таблице удалены дубли, а суммы по одинаковым услугам сложились.
ДанныеТФ.Загрузить(ТаблицаДанных);
Этот метод подходит, если дублирование вызвано наличием "лишних" детальных записей, которые не нужны пользователю в итоговом отчете.
Если вам необходимо вывести информацию о счете прямо в запросе, но вы хотите избежать дублирования строк услуг, нужно ограничить выборку счетов. Логика такая: "Для каждого договора присоединить только ОДИН счет (например, последний)".
Для этого мы не можем соединять таблицу документов напрямую. Нам потребуется вложенный запрос или временная таблица.
Шаг 1. Сначала получим последний счет для каждого договора.
ВЫБРАТЬ
Счет.ДоговорКонтрагента КАК Договор,
МАКСИМУМ(Счет.Дата) КАК ПоследняяДатаСчета
ПОМЕСТИТЬ ВТ_ПоследниеДатыСчетов
ИЗ
Документ.СчетНаОплатуПокупателю КАК Счет
ГДЕ
Счет.Проведен = ИСТИНА
СГРУППИРОВАТЬ ПО
Счет.ДоговорКонтрагента
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
Счет.Ссылка КАК СсылкаНаСчет,
Счет.ДоговорКонтрагента
ПОМЕСТИТЬ ВТ_ПоследниеСчета
ИЗ
ВТ_ПоследниеДатыСчетов КАК Даты
ВНУТРЕННЕЕ СОЕДИНЕНИЕ Документ.СчетНаОплатуПокупателю КАК Счет
ПО Даты.Договор = Счет.ДоговорКонтрагента
И Даты.ПоследняяДатаСчета = Счет.Дата
Шаг 2. Соединяем основную таблицу услуг с полученной таблицей уникальных счетов.
Теперь связь будет "один-к-одному" (или "многие-к-одному" со стороны услуг), и дублирования не произойдет, так как на один договор приходится строго один счет из нашей временной таблицы.
В обсуждении проблемы упоминалась попытка отфильтровать счета по периоду. Важно помнить нюанс работы ЛЕВОЕ СОЕДИНЕНИЕ в связке с условием ГДЕ.
Ошибка:
...
ЛЕВОЕ СОЕДИНЕНИЕ Документ.СчетНаОплатуПокупателю ...
ГДЕ
СчетНаОплатуПокупателю.Дата МЕЖДУ &Нач И &Кон
Такое условие превращает левое соединение во внутреннее. Если у договора нет счетов в этом периоде, то поле Дата будет NULL. Условие NULL МЕЖДУ... вернет ЛОЖЬ, и вся строка (включая договор и услугу) исчезнет из выборки.
Правильный подход:
Условия фильтрации присоединяемой таблицы (правой таблицы в левом соединении) нужно писать внутри оператора ПО.
...
ИЗ
Справочник.ДоговорыКонтрагентов.Услуги КАК Услуги
ЛЕВОЕ СОЕДИНЕНИЕ Документ.СчетНаОплатуПокупателю КАК Счет
ПО Услуги.Ссылка = Счет.ДоговорКонтрагента
И Счет.Проведен = ИСТИНА
И Счет.Дата МЕЖДУ &НачалоПериода И &КонецПериода
Однако, даже этот способ не уберет дубли, если в выбранный период попадает более одного счета. Он лишь ограничит количество дублей.
Важно отметить, что получение данных о задолженностях или "суммах к выставлению" путем анализа самих документов — не самая оптимальная практика в 1С. Документы могут перепроводиться, меняться, их может быть очень много, что замедляет работу.
Для хранения итоговых цифр в 1С существуют Регистры накопления (например, "Взаиморасчеты с контрагентами", "Продажи").
Правильный архитектурный подход:
РегистрНакопления.ВзаиморасчетыСКонтрагентами.Остатки.В текущей задаче автора, если целью является просто формирование печатной формы или реестра, использование метода Свернуть() (Решение 1) будет самым простым и эффективным способом исправить ситуацию "здесь и сейчас".