В процессе разработки на платформе 1С:Предприятие 8 программисты часто сталкиваются с ситуацией, когда необходимо получить данные из разных типов документов, где одни и те же по смыслу реквизиты называются по-разному. Например, в конфигурации «Зарплата и управление персоналом» (ЗУП) период начисления в одних документах может храниться в поле ПериодРегистрации, а в других — МесяцНачисления. Естественным желанием разработчика является создание универсальной функции прямо в тексте запроса, чтобы избежать громоздких конструкций. Разберем подробно, как решить эту задачу, учитывая ограничения платформы.
Первое, что необходимо уяснить: язык запросов 1С не поддерживает создание пользовательских функций (UDF — User Defined Functions), как это реализовано в «чистом» SQL. Платформа предоставляет строго определенный набор функций (таких как ПРЕДСТАВЛЕНИЕ, ТИПЗНАЧЕНИЯ, РАЗНОСТЬДАТ и др.), и расширить этот список программно внутри самого тела запроса невозможно. Поэтому на вопрос о прямой реализации &МояФункция в запросе ответ всегда будет отрицательным.
Однако существует несколько проверенных способов обойти это ограничение и добиться чистоты кода. Проанализируем их по порядку.
Если документов-регистраторов много, писать условие под каждый конкретный тип — трудоемкая задача. Оптимальным подходом будет предварительная группировка типов документов в массивы на стороне встроенного языка 1С, а затем использование этих массивов в качестве параметров запроса. Рассмотрим пример реализации:
// В коде на встроенном языке подготовим массивы типов
МассивВидовПериод = Новый Массив;
МассивВидовПериод.Добавить(Тип("ДокументСсылка.НачислениеЗарплаты"));
// ... заполняем остальные типы
МассивВидовМесяцНачисления = Новый Массив;
МассивВидовМесяцНачисления.Добавить(Тип("ДокументСсылка.Премия"));
// ... заполняем остальные типы
Запрос.УстановитьПараметр("МассивВидовПериод", МассивВидовПериод);
Запрос.УстановитьПараметр("МассивВидовМесяцНачисления", МассивВидовМесяцНачисления);
Затем в самом тексте запроса мы используем функцию ТИПЗНАЧЕНИЯ:
ВЫБРАТЬ
ВЫБОР
КОГДА ТИПЗНАЧЕНИЯ(Регистр.Регистратор) В (&МассивВидовПериод)
ТОГДА Регистр.Регистратор.ПериодРегистрации
КОГДА ТИПЗНАЧЕНИЯ(Регистр.Регистратор) В (&МассивВидовМесяцНачисления)
ТОГДА Регистр.Регистратор.МесяцНачисления
ИНАЧЕ Регистр.Период
КОНЕЦ КАК ПериодДокумента
ИЗ
РегистрНакопления.НачисленияУдержанияПоСотрудникам КАК Регистр
Важный нюанс: Обращение через точку от поля составного типа (каким является Регистратор) к реквизитам документов заставляет платформу выполнять LEFT JOIN ко всем таблицам документов, входящим в этот тип. Если типов очень много, производительность может существенно снизиться.
В некоторых случаях, когда список возможных полей ограничен двумя-тремя вариантами, можно применить функцию ЕСТЬNULL. Платформа попытается обратиться к указанным полям, и если реквизит у данного типа документа отсутствует, запрос вернет NULL, после чего сработает подстановка следующего значения.
ВЫБРАТЬ
ЕСТЬNULL(Регистратор.МесяцНачисления, Регистратор.ПериодРегистрации) КАК Период
ИЗ
РегистрНакопления.ДанныеОЗаработнойПлате
Несмотря на кажущуюся простоту, этот метод также страдает от проблемы множественных соединений. Мы рекомендуем использовать его только в небольших запросах или когда количество типов документов в регистраторе жестко ограничено.
Для задач, связанных с зарплатой, самым правильным и производительным решением будет получение периода не из документа-регистратора, а напрямую из Регистра Расчета. В ЗУП регистры расчета (например, Начисления) уже содержат унифицированное поле ПериодРегистрации. Это избавляет от необходимости «прыгать» в таблицы документов.
Разберем ситуацию: если вы строите отчет по данным регистра накопления, попробуйте соединить его с соответствующим регистром расчета по полю Регистратор и НомерСтроки (или Активность), чтобы забрать уже готовый период.
Если ваш запрос является частью схемы компоновки данных (СКД), вы действительно можете использовать свои функции. Это максимально приближено к тому, что хотел автор вопроса. Рассмотрим алгоритм действий:
РаботаСЗапросамиПолныеПрава).
Функция ПолучитьПериодДокумента(Ссылка) Экспорт
// Здесь может быть любая сложная логика
Возврат Ссылка.ПериодРегистрации;
КонецФункции
РаботаСЗапросамиПолныеПрава.ПолучитьПериодДокумента(Регистратор).Платформа сама вызовет эту функцию для каждой строки результата. Помните: это может замедлить формирование отчета, так как происходит переключение контекста между сервером БД и сервером 1С для каждой записи.
Вместо того чтобы писать универсальную «функцию» внутри запроса, мы можем использовать «функцию-генератор» на встроенном языке, которая сама сформирует правильный текст ВЫБОР КОГДА на основе метаданных. Это наиболее профессиональный подход для сложных систем.
Проанализируем пример логики:
ТекстВыбора = "ВЫБОР ";
Для Каждого ТипДокумента Из Метаданные.РегистрыНакопления.Начисления.Регистраторы Цикл
Имя = ТипДокумента.Имя;
Если Метаданные.Документы[Имя].Реквизиты.Найти("МесяцНачисления") <> Неопределено Тогда
ТекстВыбора = ТекстВыбора + "КОГДА Регистратор ССЫЛКА Документ." + Имя + " ТОГДА Регистратор.МесяцНачисления ";
ИначеЕсли Метаданные.Документы[Имя].Реквизиты.Найти("ПериодРегистрации") <> Неопределено Тогда
ТекстВыбора = ТекстВыбора + "КОГДА Регистратор ССЫЛКА Документ." + Имя + " ТОГДА Регистратор.ПериодРегистрации ";
КонецЕсли;
КонецЦикла;
ТекстВыбора = ТекстВыбора + " ИНАЧЕ Период КОНЕЦ";
ТекстЗапроса = СтрЗаменить(ИсходныйТекст, "&МояФункция", ТекстВыбора);
Таким образом, мы динамически подставляем в запрос идеально подходящую под текущую конфигурацию конструкцию ВЫБОР, не загромождая основной код ручным перечислением типов.
Хотя напрямую создать свою функцию в запросе 1С нельзя, мы выяснили, что существует как минимум пять альтернативных путей. Для простых задач лучше всего подходит Способ 1 (массивы типов), для сложных отчетов — Способ 4 (функции СКД), а для обеспечения максимальной производительности в ЗУП — Способ 3 (использование полей регистров расчета).