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

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

В процессе разработки на платформе 1С:Предприятие 8 программисты часто сталкиваются с ситуацией, когда необходимо получить данные из разных типов документов, где одни и те же по смыслу реквизиты называются по-разному. Например, в конфигурации «Зарплата и управление персоналом» (ЗУП) период начисления в одних документах может храниться в поле ПериодРегистрации, а в других — МесяцНачисления. Естественным желанием разработчика является создание универсальной функции прямо в тексте запроса, чтобы избежать громоздких конструкций. Разберем подробно, как решить эту задачу, учитывая ограничения платформы.

Ограничение платформы: почему нельзя просто написать функцию?

Первое, что необходимо уяснить: язык запросов 1С не поддерживает создание пользовательских функций (UDF — User Defined Functions), как это реализовано в «чистом» SQL. Платформа предоставляет строго определенный набор функций (таких как ПРЕДСТАВЛЕНИЕ, ТИПЗНАЧЕНИЯ, РАЗНОСТЬДАТ и др.), и расширить этот список программно внутри самого тела запроса невозможно. Поэтому на вопрос о прямой реализации &МояФункция в запросе ответ всегда будет отрицательным.

Однако существует несколько проверенных способов обойти это ограничение и добиться чистоты кода. Проанализируем их по порядку.

Способ 1. Использование типизации и конструкции ВЫБОР КОГДА

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


// В коде на встроенном языке подготовим массивы типов
МассивВидовПериод = Новый Массив;
МассивВидовПериод.Добавить(Тип("ДокументСсылка.НачислениеЗарплаты"));
// ... заполняем остальные типы

МассивВидовМесяцНачисления = Новый Массив;
МассивВидовМесяцНачисления.Добавить(Тип("ДокументСсылка.Премия"));
// ... заполняем остальные типы

Запрос.УстановитьПараметр("МассивВидовПериод", МассивВидовПериод);
Запрос.УстановитьПараметр("МассивВидовМесяцНачисления", МассивВидовМесяцНачисления);

Затем в самом тексте запроса мы используем функцию ТИПЗНАЧЕНИЯ:


ВЫБРАТЬ
    ВЫБОР
        КОГДА ТИПЗНАЧЕНИЯ(Регистр.Регистратор) В (&МассивВидовПериод)
            ТОГДА Регистр.Регистратор.ПериодРегистрации
        КОГДА ТИПЗНАЧЕНИЯ(Регистр.Регистратор) В (&МассивВидовМесяцНачисления)
            ТОГДА Регистр.Регистратор.МесяцНачисления
        ИНАЧЕ Регистр.Период
    КОНЕЦ КАК ПериодДокумента
ИЗ
    РегистрНакопления.НачисленияУдержанияПоСотрудникам КАК Регистр

Важный нюанс: Обращение через точку от поля составного типа (каким является Регистратор) к реквизитам документов заставляет платформу выполнять LEFT JOIN ко всем таблицам документов, входящим в этот тип. Если типов очень много, производительность может существенно снизиться.

Способ 2. Использование функции ЕСТЬNULL для упрощения

В некоторых случаях, когда список возможных полей ограничен двумя-тремя вариантами, можно применить функцию ЕСТЬNULL. Платформа попытается обратиться к указанным полям, и если реквизит у данного типа документа отсутствует, запрос вернет NULL, после чего сработает подстановка следующего значения.


ВЫБРАТЬ
    ЕСТЬNULL(Регистратор.МесяцНачисления, Регистратор.ПериодРегистрации) КАК Период
ИЗ
    РегистрНакопления.ДанныеОЗаработнойПлате

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

Способ 3. Получение данных из Регистров Расчета (специфика ЗУП)

Для задач, связанных с зарплатой, самым правильным и производительным решением будет получение периода не из документа-регистратора, а напрямую из Регистра Расчета. В ЗУП регистры расчета (например, Начисления) уже содержат унифицированное поле ПериодРегистрации. Это избавляет от необходимости «прыгать» в таблицы документов.

Разберем ситуацию: если вы строите отчет по данным регистра накопления, попробуйте соединить его с соответствующим регистром расчета по полю Регистратор и НомерСтроки (или Активность), чтобы забрать уже готовый период.

Способ 4. Использование функций общих модулей в СКД

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

  1. Создайте в конфигураторе Общий модуль (например, РаботаСЗапросамиПолныеПрава).
  2. Установите модулю флаги Сервер и Вызов сервера.
  3. Напишите экспортируемую функцию:
    
    Функция ПолучитьПериодДокумента(Ссылка) Экспорт
        // Здесь может быть любая сложная логика
        Возврат Ссылка.ПериодРегистрации; 
    КонецФункции
    
  4. В СКД на закладке Вычисляемые поля добавьте новое поле и в выражении укажите: РаботаСЗапросамиПолныеПрава.ПолучитьПериодДокумента(Регистратор).

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

Способ 5. Программная генерация текста запроса

Вместо того чтобы писать универсальную «функцию» внутри запроса, мы можем использовать «функцию-генератор» на встроенном языке, которая сама сформирует правильный текст ВЫБОР КОГДА на основе метаданных. Это наиболее профессиональный подход для сложных систем.

Проанализируем пример логики:


ТекстВыбора = "ВЫБОР ";
Для Каждого ТипДокумента Из Метаданные.РегистрыНакопления.Начисления.Регистраторы Цикл
    Имя = ТипДокумента.Имя;
    Если Метаданные.Документы[Имя].Реквизиты.Найти("МесяцНачисления") <> Неопределено Тогда
        ТекстВыбора = ТекстВыбора + "КОГДА Регистратор ССЫЛКА Документ." + Имя + " ТОГДА Регистратор.МесяцНачисления ";
    ИначеЕсли Метаданные.Документы[Имя].Реквизиты.Найти("ПериодРегистрации") <> Неопределено Тогда
        ТекстВыбора = ТекстВыбора + "КОГДА Регистратор ССЫЛКА Документ." + Имя + " ТОГДА Регистратор.ПериодРегистрации ";
    КонецЕсли;
КонецЦикла;
ТекстВыбора = ТекстВыбора + " ИНАЧЕ Период КОНЕЦ";

ТекстЗапроса = СтрЗаменить(ИсходныйТекст, "&МояФункция", ТекстВыбора);

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

Резюме

Хотя напрямую создать свою функцию в запросе 1С нельзя, мы выяснили, что существует как минимум пять альтернативных путей. Для простых задач лучше всего подходит Способ 1 (массивы типов), для сложных отчетов — Способ 4 (функции СКД), а для обеспечения максимальной производительности в ЗУП — Способ 3 (использование полей регистров расчета).

← На главную