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

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

При разработке печатных форм в системе «1С:Предприятие» программисты часто сталкиваются с необходимостью дополнить данные табличной части документа кадровыми или аналитическими сведениями из регистров. Типичный пример — вывод должности и категории сотрудника в печатной форме табеля учета рабочего времени, когда этих реквизитов нет в самом документе, но они хранятся в периодическом регистре сведений РаботникиОрганизаций.

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

Выясняем причины ошибок: почему дублируются строки и ломается макет

Проанализируем исходный запрос, который привел к некорректному выводу данных на форуме:


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

В этой конструкции кроются сразу три критические ошибки, из-за которых печатная форма работает некорректно:

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

Давайте исправим эти ошибки и построим правильный алгоритм работы с данными.

Шаг 1. Разработка корректного запроса с использованием левого соединения и среза последних

Чтобы избежать вложенности и дублирования строк, мы должны обратиться напрямую к физической таблице табличной части документа (она называется в формате Документ.[ИмяДокумента].[ИмяТабличнойЧасти]) и связать ее через левое соединение с виртуальной таблицей СрезПоследних кадрового регистра.

Рассмотрим структуру правильного запроса:


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

Проанализируем особенности этого решения:

Шаг 2. Оптимизация запроса через временные таблицы (стандарты «1С:Совместимо»)

Если в табличной части вашего документа содержится большое количество сотрудников, прямое левое соединение физической таблицы с виртуальной таблицей СрезПоследних может выполняться неоптимально. СУБД будет вынуждена рассчитывать срез последних по всему регистру сведений.

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

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

Посмотрим на пример реализации такого пакета запросов:


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

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    КадрыСрез.Сотрудник КАК Сотрудник,
    КадрыСрез.Должность КАК Должность,
    КадрыСрез.Категория КАК Категория
ПОМЕСТИТЬ ВТ_КадровыеДанные
ИЗ
    РегистрСведений.РаботникиОрганизаций.СрезПоследних(
            &Период, 
            Сотрудник В (ВЫБРАТЬ ВТ_Сотрудники.Сотрудник ИЗ ВТ_Сотрудники)) КАК КадрыСрез
;

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

Этот подход полностью соответствует стандартам разработки фирмы «1С». Фильтрация сотрудников на уровне виртуальной таблицы среза последних снижает объем обрабатываемых сервером данных в сотни раз.

Шаг 3. Вывод полученных данных в макет печатной формы

Теперь разберем вторую проблему автора темы: как связать результат выполнения запроса с параметром Должность в табличном макете печатной формы.

Для начала откроем макет в конфигураторе и настроим ячейку вывода должности:

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

После настройки макета напишем программный код вывода печатной формы в модуле менеджера документа или модуле команды печати. Рассмотрим пример функции формирования печатной формы:


Функция СформироватьПечатнуюФормуТабеля(СсылкаНаДокумент, АдресМакета) Экспорт

    ТабДок = Новый ТабличныйДокумент;
    ТабДок.КлючПараметровПечати = "ПараметрыПечати_ТабельУчетаРабочегоВремени";
    
    Макет = ПолучитьМакет("ПФ_MXL_Табель"); // Укажите имя вашего макета
    
    // Получаем области макета
    ОбластьШапка  = Макет.ПолучитьОбласть("Шапка");
    ОбластьСтрока = Макет.ПолучитьОбласть("Строка");
    ОбластьПодвал = Макет.ПолучитьОбласть("Подвал");
    
    // Заполняем и выводим шапку
    ОбластьШапка.Параметры.НомерДокумента = СсылкаНаДокумент.Номер;
    ОбластьШапка.Параметры.ДатаДокумента  = Формат(СсылкаНаДокумент.Дата, "ДФ=dd.MM.yyyy");
    ТабДок.Вывести(ОбластьШапка);
    
    // Подготовка запроса
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |   ТабельНачисления.НомерСтроки КАК НомерСтроки,
        |   ТабельНачисления.Сотрудник КАК Сотрудник,
        |   ТабельНачисления.Ставка КАК Ставка,
        |   ТабельНачисления.День1 КАК День1,
        |   // ... перечисление всех необходимых дней ...
        |   ЕСТЬNULL(КадрыСрез.Должность, ЗНАЧЕНИЕ(Справочник.Должности.ПустаяСсылка)) КАК Должность,
        |   ЕСТЬNULL(КадрыСрез.Категория, ЗНАЧЕНИЕ(Справочник.Категории.ПустаяСсылка)) КАК Категория
        |ИЗ
        |   Документ.ТабельУчетаРабочегоВремени.Начисления КАК ТабельНачисления
        |   ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.РаботникиОрганизаций.СрезПоследних(&Период, ) КАК КадрыСрез
        |   ПО ТабельНачисления.Сотрудник = КадрыСрез.Сотрудник
        |ГДЕ
        |   ТабельНачисления.Ссылка = &Ссылка";
        
    Запрос.УстановитьПараметр("Ссылка", СсылкаНаДокумент);
    // В качестве даты среза передаем дату документа
    Запрос.УстановитьПараметр("Период", СсылкаНаДокумент.Date); 
    
    Выборка = Запрос.Выполнить().Выбрать();
    
    Пока Выборка.Следующий() Цикл
        
        // Метод Заполнить() сопоставляет имена полей выборки запроса с параметрами области макета
        ОбластьСтрока.Параметры.Заполнить(Выборка);
        
        // Если бы имена полей в запросе и макете не совпадали, мы бы заполняли их вручную:
        // ОбластьСтрока.Параметры.Должность = Выборка.Должность;
        // ОбластьСтрока.Параметры.Категория = Выборка.Категория;
        
        ТабДок.Вывести(ОбластьСтрока);
        
    КонецЦикла;
    
    // Выводим подвал печатной формы
    ТабДок.Вывести(ОбластьПодвал);
    
    Возврат ТабДок;

КонецФункции

Использование метода Параметры.Заполнить() существенно сокращает объем исходного кода. Главное требование для его корректной работы — полное совпадение имен полей выборки запроса (псевдонимов после ключевого слова КАК) с именами параметров в ячейках макета.

Важное замечание для современных конфигураций (ЗУП 3.1, ERP 2.5, КА 2.5)

Если вы ведете разработку в современных типовых решениях от фирмы «1С» на базе конфигурации «Зарплата и Управление Персоналом», то прямое обращение к регистру сведений РаботникиОрганизаций в запросе может не сработать. В современных системах используется концепция интервальных регистров, а кадровая история сотрудников хранится в специализированных периодических регистрах сведений со сложной структурой (например, КадроваяИсторияСотрудников).

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

Рассмотрим, как это можно реализовать программно:


// 1. Сначала выбираем данные табличной части документа в таблицу значений
Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ РАЗЛИЧНЫЕ
    |   ТабельНачисления.Сотрудник КАК Сотрудник
    |ИЗ
    |   Документ.ТабельУчетаРабочегоВремени.Начисления КАК ТабельНачисления
    |ГДЕ
    |   ТабельНачисления.Ссылка = &Ссылка";
Запрос.УстановитьПараметр("Ссылка", СсылкаНаДокумент);
ТаблицаСотрудников = Запрос.Выполнить().Выгрузить();

МассивСотрудников = ТаблицаСотрудников.ВыгрузитьКолонку("Сотрудник");

// 2. Используем типовой программный интерфейс кадрового учета
// Передаем массив сотрудников, список нужных полей и дату среза данных
ОписательКадровыхДанных = "Должность, Категория";
КадровыеДанные = КадровыйУчет.КадровыеДанныеСотрудников(Истина, МассивСотрудников, ОписательКадровыхДанных, СсылкаНаДокумент.Дата);

// На выходе получаем заполненную таблицу значений с колонками "Сотрудник", "Должность", "Категория"
// Теперь мы можем связать полученную таблицу с ТЧ документа на уровне встроенного языка или передать в запрос

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

← На главную