Как передать параметры пользователя в запрос при использовании внешнего набора данных (объекта) в СКД?

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

При разработке отчетов на схеме компоновки данных (СКД) часто возникает необходимость использовать внешний набор данных — Набор данных - объект. Обычно это требуется, когда данные невозможно получить одним простым запросом, или когда их нужно предварительно обработать на встроенном языке 1С (например, рассчитать сложные показатели, обратиться к программному интерфейсу других модулей или внешних систем).

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

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

---

Способ 1. Стандартный механизм СКД (Без создания собственной формы)

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

Рассмотрим по шагам, как это настроить:

  1. Откройте вашу схему компоновки данных и перейдите на вкладку «Параметры».
  2. Убедитесь, что в списке присутствуют нужные параметры. В нашем случае это ДатаНачала, ДатаОкончания и ВидУчетаВремени. Ограничьте типы данных для этих параметров (например, для дат установите тип Дата с составом даты "Дата", а для вида учета времени — ссылку на соответствующий справочник).
  3. Перейдите на вкладку «Настройки» (в правом верхнем углу конструктора СКД).
  4. В нижней части экрана найдите вкладку «Параметры». Здесь отображаются параметры, которые будут использоваться в отчете.
  5. Для каждого параметра, который должен заполнять пользователь, нажмите на кнопку со свойствами элемента пользовательских настроек (иконка в виде шестеренки или кнопка "Свойства элемента пользовательских настроек").
  6. Установите флаг «Включать в пользовательские настройки». В качестве режима отображения выберите «Быстрый доступ» (чтобы поле ввода отображалось прямо на панели отчета) или «Обычный».

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

---

Способ 2. Как правильно прочитать параметры пользователя в коде

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

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

Проанализируем правильный пример получения параметров:


// Получаем настройки с учетом всех изменений, внесенных пользователем на форме
Настройки = КомпоновщикНастроек.ПолучитьНастройки();

// Инициализируем переменные для параметров запроса
ДатаНачала = '00010101';
ДатаОкончания = '00010101';
ВидУчетаВремени = Справочники.ВидыИспользованияРабочегоВремени.ПустаяСсылка();

// Получаем значение параметра "ДатаНачала"
ПараметрДатаНачала = Настройки.ПараметрыДанных.НайтиЗначениеПараметра(Новый ПараметрКомпоновкиДанных("ДатаНачала"));
Если ПараметрДатаНачала <> Неопределено И ПараметрДатаНачала.Использование Тогда
    ДатаНачала = ПараметрДатаНачала.Значение;
КонецЕсли;

// Получаем значение параметра "ДатаОкончания"
ПараметрДатаОкончания = Настройки.ПараметрыДанных.НайтиЗначениеПараметра(Новый ПараметрКомпоновкиДанных("ДатаОкончания"));
Если ПараметрДатаОкончания <> Неопределено И ПараметрДатаОкончания.Использование Тогда
    ДатаОкончания = ПараметрДатаОкончания.Значение;
КонецЕсли;

// Получаем значение параметра "ВидУчетаВремени"
ПараметрВидУчета = Настройки.ПараметрыДанных.НайтиЗначениеПараметра(Новый ПараметрКомпоновкиДанных("ВидУчетаВремени"));
Если ПараметрВидУчета <> Неопределено И ПараметрВидУчета.Использование Тогда
    ВидУчетаВремени = ПараметрВидУчета.Значение;
КонецЕсли;

Важное примечание: Обратите внимание на опечатку в коде автора темы. В тексте его запроса параметры назывались &ДатаНачала и &ДатаОкончания, а в методе УстановитьПараметр он пытался использовать строки "НачалоПериода" и "КонецПериода". Имена параметров в тексте запроса и в методе УстановитьПараметр должны строго совпадать!

---

Способ 3. Оптимизация производительности (Устраняем критическую ошибку)

В коде автора из сообщения содержится классический антипаттерн разработки 1С — запрос в цикле. Посмотрим на этот фрагмент кода:


Для Каждого Строка Из ТЗ_ДанныеОтчета Цикл
    КадровыеДанные = КадровыйУчет.КадровыеДанныеСотрудников(Истина, ОбщегоНазначенияКлиентСервер.ЗначениеВМассиве(Строка.Сотрудник), "Подразделение", КонецДня(Строка.Период));
    Если КадровыеДанные.Количество() > 0 Тогда
        Строка.Подразделение = КадровыеДанные[0].Подразделение; 
    КонецЕсли;
КонецЦикла;

Метод КадровыеДанныеСотрудников внутри себя обращается к базе данных (делает запросы к виртуальным таблицам среза последних). Если в отчете будет 10 000 строк, система выполнит 10 000 отдельных запросов к СУБД! Это приведет к критическому зависанию отчета и колоссальной нагрузке на сервер.

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

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


// Шаг 1. Собираем уникальный массив сотрудников и массив дат для получения кадровых срезов
МассивСотрудников = Новый Массив;
Для Каждого Строка Из ТЗ_ДанныеОтчета Цикл
    Если МассивСотрудников.Найти(Строка.Сотрудник) = Неопределено Тогда
        МассивСотрудников.Добавить(Строка.Сотрудник);
    КонецЕсли;
КонецЦикла;

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

// Шаг 3. Индексируем таблицу кадровых данных для быстрого поиска
ТаблицаКадровыхДанных.Индексы.Добавить("Сотрудник");

// Шаг 4. Быстро заполняем подразделения в цикле без обращений к базе данных
Для Каждого Строка Из ТЗ_ДанныеОтчета Цикл
    СтруктураПоиска = Новый Структура("Сотрудник", Строка.Сотрудник);
    НайденныеСтроки = ТаблицаКадровыхДанных.НайтиСтроки(СтруктураПоиска);
    Если НайденныеСтроки.Количество() > 0 Тогда
        Строка.Подразделение = НайденныеСтроки[0].Подразделение;
    КонецЕсли;
КонецЦикла;

Такой подход сокращает количество обращений к базе данных с тысяч до одного-единственного запроса. Скорость формирования отчета увеличится в десятки и сотни раз.

---

Способ 4. Динамическое наложение пользовательских отборов

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

Мы можем программно проанализировать отборы, установленные пользователем в компоновщике настроек, и динамически внедрить их в текст нашего ручного запроса в процедуре ПриКомпоновкеРезультата.

Рассмотрим пример анализа элементов отбора:


ДополнительныеФильтры = "";
Для Каждого ЭлементОтбора Из Настройки.Отбор.Элементы Цикл
    Если ЭлементОтбора.Использование И ТипЗнч(ЭлементОтбора) = Тип("ЭлементОтбораКомпоновкиДанных") Тогда
        // Проверяем, настроен ли отбор по подразделению
        Если ЭлементОтбора.ЛевоеЗначение = Новый ПолеКомпоновкиДанных("Подразделение") Тогда
            ДополнительныеФильтры = ДополнительныеФильтры + " И ДанныеТабельногоУчета.Подразделение = &ОтборПодразделение";
            Запрос.УстановитьПараметр("ОтборПодразделение", ЭлементОтбора.ПравоеЗначение);
        КонецЕсли;
    КонецЕсли;
КонецЦикла;

// После этого мы можем динамически модифицировать текст запроса перед его выполнением:
Запрос.Текст = СтрЗаменить(Запрос.Текст, "//ГдеОтборы", ДополнительныеФильтры);

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

← На главную