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

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

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

В чем заключается проблема использования вложенных таблиц в запросе

Разработчики часто пытаются применить вложенную выборку табличной части в секции ВЫБРАТЬ:


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

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

Если же переписать запрос напрямую к табличной части Документ.РеализацияТоваровУслуг.Товары, плоская выборка возвращает по одной строке на каждую позицию документа. В результате стандартный двухцикловый обход выборки (сначала по документу, а затем по его ТЧ) перестает работать корректно без дополнительных настроек группировки.

Способ 1. Запрос к табличной части документа и секция ИТОГИ ПО

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


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

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


ВыборкаДокументов = РезультатЗапроса.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);
Пока ВыборкаДокументов.Следующий() Цикл
    // Заполняем шапку и подвал макета
    ОбластьШапка.Параметры.Заполнить(ВыборкаДокументов);
    ТабДок.Вывести(ОбластьШапка);
    
    ВыборкаСтрок = ВыборкаДокументов.Выбрать();
    Пока ВыборкаСтрок.Следующий() Цикл
        // Заполняем строки табличной части
        ОбластьСтрока.Параметры.Заполнить(ВыборкаСтрок);
        ТабДок.Вывести(ОбластьСтрока);
    КонецЦикла;
КонецЦикла;

Способ 2. Использование временных таблиц для корректного соединения с регистрами

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

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

Посмотрим на пример построения такого запроса:


ВЫБРАТЬ РАЗЛИЧНЫЕ
    ДокументТЧ.Номенклатура КАК Номенклатура
ПОМЕСТИТЬ ВТ_Номенклатура
ИЗ
    Документ.РеализацияТоваровУслуг.Товары КАК ДокументТЧ
ГДЕ
    ДокументТЧ.Ссылка В(&МассивОбъектов);

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

Причины дублирования строк при соединении с регистрами и способы их устранения

Выясним причину, почему при соединении табличной части документа с регистром могут возникать лишние строки и дублирование данных:

1. Соединение с физической таблицей регистра вместо виртуальной.
Если обращаться напрямую к таблице РегистрСведений.ЦеныНоменклатурыПоставщиков или РегистрНакопления.СебестоимостьТоваров, запрос выберет все исторические записи регистраций. Чтобы получить актуальное значение на дату, необходимо использовать виртуальную таблицу СрезПоследних или Остатки.

2. Использование ПРАВОГО или ПОЛНОГО соединения.
При ПРАВОМ СОЕДИНЕНИИ к результату добавляются строки из регистра, которых нет в документе. Для печатных форм документов всегда следует использовать ЛЕВОЕ СОЕДИНЕНИЕ, при котором главной таблицей выступает табличная часть документа.

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

4. Безопасность при расчете средней цены.
Если стоимость за единицу рассчитывается путем деления общей суммы на количество (из регистра накопления), во избежание ошибки деления на ноль необходимо использовать условное выражение ВЫБОР КОГДА:


ВЫБОР
    КОГДА ЕСТЬNULL(Остатки.КоличествоОстаток, 0) = 0 ТОГДА 0
    ИНАЧЕ Остатки.СтоимостьОстаток / Остатки.КоличествоОстаток
КОНЕЦ КАК ЦенаЗаЕдиницу

Применение секции ИТОГИ ПО, использование виртуальных таблиц и ЛЕВОГО СОЕДИНЕНИЯ гарантируют правильное объединение данных шапки, табличной части и регистров в одном запросе без потери строк и дублирования информации.

← На главную