Как динамически формировать запрос в 1С, подставляя имя типа документа в секцию "ИЗ"?

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

При разработке в 1С:Предприятии часто возникает задача создания универсальных функций или обработок, которые должны работать с различными типами документов. Одним из таких сценариев является формирование запроса, где источник данных (таблица документа) должен определяться динамически, исходя из переданной ссылки на документ. Например, нам может потребоваться выбрать номенклатуру из табличной части "Товары" документа, который может быть как "Поступлением товаров и услуг", так и "Перемещением товаров". В этом материале мы подробно разберем, как решить эту задачу, используя свойства метаданных и методы работы со строками.

Суть проблемы: динамический источник данных в запросе

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


ВЫБРАТЬ
    ДокументПоступление.Номенклатура КАК Номенклатура
ИЗ
    Документ.ПоступлениеТоваровУслуг.Товары КАК ДокументПоступление
ГДЕ
    ДокументПоступление.Ссылка = &СсылкаДокумента

Проблема заключается в том, что имя таблицы Документ.ПоступлениеТоваровУслуг.Товары жестко прописано. Если нам нужно, чтобы запрос работал и для Документ.ПеремещениеТоваров.Товары, и для Документ.ПриобретениеТоваровУслуг.Товары, мы не можем просто так подставить переменную вместо этого имени. Язык запросов 1С не поддерживает прямую параметризацию имен таблиц или полей.

Автор исходного запроса на форуме предложил такую структуру:


"ВЫБРАТЬ
| ЗаполняемыйДокумент.Номенклатура КАК Номенклатура
|ИЗ
| &МойТипДокумента.Товары КАК ЗаполняемыйДокумент
|ГДЕ
| ЗаполняемыйДокумент.Ссылка = &Документ";

Изначально предполагалось, что можно будет передать параметр &МойТипДокумента, который будет автоматически раскрываться в имя метаданных. Однако такой механизм напрямую не работает для имен таблиц в секции ИЗ.

Ключевое решение: свойство Метаданные.ПолноеИмя

Для того чтобы динамически подставлять имя типа документа в запрос, нам необходимо программно получить полное имя объекта метаданных. Для этого объект "Ссылка" (или любой другой ссылочный объект) предоставляет очень полезное свойство: Метаданные.ПолноеИмя.

Давайте рассмотрим подробнее, как это работает:

  1. У нас есть ссылка на документ, например, ДокументПриобретения, которая может указывать на объект типа "Документ.ПриобретениеТоваровУслуг".
  2. Если мы обратимся к свойству ДокументПриобретения.Метаданные.ПолноеИмя, система вернет строку: "Документ.ПриобретениеТоваровУслуг".
  3. Эта строка является точным именем источника данных, которое мы можем использовать в запросе.

Пример использования в коде:


&НаСервере
Функция ПолучитьДанныеИзДокумента(СсылкаНаДокумент)

    Если ТипЗнч(СсылкаНаДокумент) = Тип("Неопределено") Тогда
        Возврат Неопределено;
    КонецЕсли;

    // 1. Получаем полное имя объекта метаданных по ссылке.
    // Это вернет строку вида "Документ.ИмяДокумента"
    ПолноеИмяТипаДокумента = СсылкаНаДокумент.Метаданные.ПолноеИмя;

    // 2. Формируем текст запроса, используя полученную строку.
    // Здесь мы используем конкатенацию строк для динамического формирования запроса.
    // Обратите внимание, что если у документа есть табличная часть, например, "Товары",
    // то её имя также добавляется к полному имени типа документа.
    ТекстЗапроса = "ВЫБРАТЬ
                   |    Док.Ссылка КАК Ссылка,
                   |    Док.Номер КАК НомерДокумента,
                   |    Док.Дата КАК ДатаДокумента,
                   |    Док.Номенклатура КАК Номенклатура,
                   |    Док.Количество КАК Количество,
                   |    Док.Сумма КАК Сумма
                   |ИЗ
                   |    " + ПолноеИмяТипаДокумента + ".Товары КАК Док
                   |ГДЕ
                   |    Док.Ссылка = &Ссылка";

    Запрос = Новый Запрос(ТекстЗапроса);
    Запрос.УстановитьПараметр("Ссылка", СсылкаНаДокумент);

    Попытка
        Результат = Запрос.Выполнить();
        Возврат Результат.Выгрузить(); // Возвращаем результат в виде ТаблицыЗначений
    Исключение
        Сообщить("Ошибка выполнения запроса: " + ОписаниеОшибки(), СтатусСообщения.Важное);
        Возврат Неопределено;
    КонецПопытки;

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

// Пример вызова функции:
// Допустим, есть переменная МояСсылкаНаДокумент, содержащая ссылку на документ
// ТаблицаДанныхДокумента = ПолучитьДанныеИзДокумента(МояСсылкаНаДокумент);
// Если ТаблицаДанныхДокумента <> Неопределено Тогда
//     // Обработка полученных данных
// КонецЕсли;

Как мы видим, строка " + ПолноеИмяТипаДокумента + ".Товары КАК Док" позволяет нам динамически указать источник данных. Если СсылкаНаДокумент указывает на "ПоступлениеТоваровУслуг", то запрос будет работать с Документ.ПоступлениеТоваровУслуг.Товары. Если на "ПеремещениеТоваров", то с Документ.ПеремещениеТоваров.Товары.

Техники строковой подстановки

Для подстановки динамических частей в текст запроса вы можете использовать два основных подхода:

1. Конкатенация строк

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


// ... (получение ПолноеИмяТипаДокумента) ...
ТекстЗапроса = "ВЫБРАТЬ
               |    Док.Ссылка
               |ИЗ
               |    " + ПолноеИмяТипаДокумента + " КАК Док
               |ГДЕ
               |    Док.Ссылка = &Ссылка";

2. Использование функции СтрЗаменить

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


&НаСервере
Функция ПолучитьДанныеИзДокументаЧерезСтрЗаменить(СсылкаНаДокумент)

    Если ТипЗнч(СсылкаНаДокумент) = Тип("Неопределено") Тогда
        Возврат Неопределено;
    КонецЕсли;

    ПолноеИмяТипаДокумента = СсылкаНаДокумент.Метаданные.ПолноеИмя;

    // Определяем шаблон запроса с плейсхолдером для имени таблицы
    ШаблонЗапроса = "ВЫБРАТЬ
                     |    Док.Ссылка,
                     |    Док.Номер,
                     |    Док.Дата,
                     |    Док.Номенклатура,
                     |    Док.Количество,
                     |    Док.Сумма
                     |ИЗ
                     |    &ИМЯ_ТАБЛИЦЫ_ДОКУМЕНТА.Товары КАК Док
                     |ГДЕ
                     |    Док.Ссылка = &Ссылка";

    // Заменяем плейсхолдер на актуальное имя типа документа
    ТекстЗапроса = СтрЗаменить(ШаблонЗапроса, "&ИМЯ_ТАБЛИЦЫ_ДОКУМЕНТА", ПолноеИмяТипаДокумента);

    Запрос = Новый Запрос(ТекстЗапроса);
    Запрос.УстановитьПараметр("Ссылка", СсылкаНаДокумент);

    Попытка
        Результат = Запрос.Выполнить();
        Возврат Результат.Выгрузить();
    Исключение
        Сообщить("Ошибка выполнения запроса: " + ОписаниеОшибки(), СтатусСообщения.Важное);
        Возврат Неопределено;
    КонецПопытки;

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

Оба метода эффективны. Выбор между ними зависит от личных предпочтений и сложности формируемого запроса. СтрЗаменить может улучшить читаемость кода, если шаблон запроса большой, и динамических частей много.

Проверка наличия табличных частей и реквизитов

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

Для этого мы используем доступ к метаданным системы:

  1. Получаем объект метаданных для конкретного документа: МетаданныеДокумента = СсылкаНаДокумент.Метаданные();
  2. Проверяем наличие табличной части: ЕстьТабличнаяЧастьТовары = МетаданныеДокумента.ТабличныеЧасти.Найти("Товары") <> Неопределено;
  3. Если табличная часть существует, проверяем наличие реквизита в ней: Если ЕстьТабличнаяЧастьТовары Тогда МетаданныеТЧ = МетаданныеДокумента.ТабличныеЧасти.Товары; ЕстьРеквизитНоменклатура = МетаданныеТЧ.Реквизиты.Найти("Номенклатура") <> Неопределено; КонецЕсли;

Пример использования этой логики:


&НаСервере
Функция ПолучитьДанныеИзДокументаСПроверкой(СсылкаНаДокумент)

    Если ТипЗнч(СсылкаНаДокумент) = Тип("Неопределено") Тогда
        Возврат Неопределено;
    КонецЕсли;

    ПолноеИмяТипаДокумента = СсылкаНаДокумент.Метаданные.ПолноеИмя;
    ИмяОбъектаМетаданных = СсылкаНаДокумент.Метаданные().Имя; // Например, "ПоступлениеТоваровУслуг"

    МетаданныеДокумента = Метаданные.Документы.Найти(ИмяОбъектаМетаданных);
    Если МетаданныеДокумента = Неопределено Тогда
        Сообщить("Не удалось найти метаданные для документа типа: " + ИмяОбъектаМетаданных, СтатусСообщения.Важное);
        Возврат Неопределено;
    КонецЕсли;

    // Проверяем наличие табличной части "Товары"
    МетаданныеТЧТовары = МетаданныеДокумента.ТабличныеЧасти.Найти("Товары");
    Если МетаданныеТЧТовары = Неопределено Тогда
        Сообщить("У документа " + ПолноеИмяТипаДокумента + " отсутствует табличная часть 'Товары'.", СтатусСообщения.Важное);
        Возврат Неопределено;
    КонецЕсли;

    // Проверяем наличие реквизитов "Номенклатура", "Количество", "Сумма" в табличной части "Товары"
    Если МетаданныеТЧТовары.Реквизиты.Найти("Номенклатура") = Неопределено
        ИЛИ МетаданныеТЧТовары.Реквизиты.Найти("Количество") = Неопределено
        ИЛИ МетаданныеТЧТовары.Реквизиты.Найти("Сумма") = Неопределено Тогда
        Сообщить("В табличной части 'Товары' документа " + ПолноеИмяТипаДокумента + " отсутствуют необходимые реквизиты.", СтатусСообщения.Важное);
        Возврат Неопределено;
    КонецЕсли;

    // Если все проверки пройдены, формируем запрос
    ТекстЗапроса = "ВЫБРАТЬ
                   |    Док.Ссылка КАК Ссылка,
                   |    Док.Номер КАК НомерДокумента,
                   |    Док.Дата КАК ДатаДокумента,
                   |    Док.Номенклатура КАК Номенклатура,
                   |    Док.Количество КАК Количество,
                   |    Док.Сумма КАК Сумма
                   |ИЗ
                   |    " + ПолноеИмяТипаДокумента + ".Товары КАК Док
                   |ГДЕ
                   |    Док.Ссылка = &Ссылка";

    Запрос = Новый Запрос(ТекстЗапроса);
    Запрос.УстановитьПараметр("Ссылка", СсылкаНаДокумент);

    Попытка
        Результат = Запрос.Выполнить();
        Возврат Результат.Выгрузить();
    Исключение
        Сообщить("Ошибка выполнения запроса: " + ОписаниеОшибки(), СтатусСообщения.Важное);
        Возврат Неопределено;
    КонецПопытки;

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

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

Различие между динамическим запросом к таблице и получением значений реквизитов

Важно четко понимать, что задача, которую мы разбираем — это динамическая подстановка имени таблицы в секцию ИЗ запроса. Это отличается от другой, также распространенной задачи: получения значений конкретных реквизитов объекта по его ссылке.

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

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

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

Рекомендации и лучшие практики

  1. Проверяйте входные параметры: Всегда проверяйте, что переданная ссылка действительна и не является Неопределено, прежде чем пытаться получить ее метаданные.
  2. Обрабатывайте исключения: Динамическое формирование запросов может быть источником ошибок, особенно если метаданные системы меняются. Используйте блоки Попытка...Исключение...КонецПопытки для обработки возможных ошибок выполнения запроса.
  3. Будьте внимательны к синтаксису: При формировании запроса с помощью строковых операций очень легко допустить синтаксическую ошибку (пропустить пробел, запятую, опечатку в имени поля). Тщательно проверяйте формируемый текст запроса, возможно, выводя его в Сообщения для отладки.
  4. Оптимизация: Хотя динамические запросы и удобны, они могут быть немного медленнее, чем жестко закодированные запросы, так как требуется дополнительное время на парсинг строки запроса. Для критически важных по производительности участков кода, где типы документов заранее известны и не меняются, возможно, стоит рассмотреть создание отдельных, оптимизированных запросов. Однако для большинства универсальных функций разница в производительности будет незначительной.
  5. Читаемость кода: Старайтесь поддерживать читаемость текста запроса, даже если он формируется динамически. Используйте переносы строк, отступы и комментарии, чтобы ваш код был понятен другим разработчикам.

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

← На главную