При разработке отчетов и обработок в 1С:Предприятии, особенно при работе с расширениями, мы часто сталкиваемся с необходимостью создания сложных запросов. Нередко возникают вопросы, связанные с синтаксисом, производительностью и корректным использованием различных объектов платформы. В этой статье мы с вами разберем типовые проблемы при написании запросов, выясним, в чем разница между объектами Запрос и ПостроительЗапроса, и научимся оптимизировать запросы для более эффективной работы.
Прежде всего, давайте проясним фундаментальное различие между двумя основными объектами для работы с запросами в 1С: Запрос и ПостроительЗапроса. Понимание их назначения критически важно для корректного выполнения ваших задач.
Объект Запрос
Мы используем объект Запрос, когда нам требуется непосредственно выполнить запрос к базе данных и получить результат. Текст запроса задается при создании объекта или через его свойство Текст. Параметры запроса устанавливаются методом УстановитьПараметр().
Посмотрим на пример правильного использования объекта Запрос:
Запрос = Новый Запрос;
Запрос.Текст = "ВЫБРАТЬ
| Справочник.Контрагенты.Наименование КАК НаименованиеКонтрагента
|ИЗ
| Справочник.Контрагенты КАК СправочникКонтрагенты
|ГДЕ
| СправочникКонтрагенты.ЭтоГруппа = &ЭтоГруппа";
Запрос.УстановитьПараметр("ЭтоГруппа", Ложь);
РезультатЗапроса = Запрос.Выполнить();
Выборка = РезультатЗапроса.Выбрать();
Пока Выборка.Следующий() Цикл
Сообщить(Выборка.НаименованиеКонтрагента);
КонецЦикла;
Метод Выполнить() объекта Запрос возвращает объект РезультатЗапроса, с которым мы затем можем работать.
Объект ПостроительЗапроса
ПостроительЗапроса служит для динамического формирования текста запроса. Он предоставляет более гибкие возможности для программного конструирования запросов, например, когда нам нужно добавлять отборы или изменять поля в зависимости от условий. Мы можем думать о нем как о более низкоуровневом аналоге ПостроителяОтчета, но без оформительских свойств.
Ключевое отличие: в отличие от Запрос.Выполнить(), метод ПостроительЗапроса.Выполнить() не возвращает результат запроса напрямую. Вместо этого он подготавливает запрос, который затем можно получить методом ПолучитьЗапрос(). Только после этого полученный объект Запрос можно выполнить.
Давайте проанализируем распространенную ошибку, которая могла возникнуть в вашей ситуации:
// Предположим, что "Запрос" здесь на самом деле объект ПостроительЗапроса
Запрос.ЗаполнитьНастройки();
Запрос.Параметры.Вставить("Организация", Организация);
Запрос.Параметры.Вставить("ДатаКон", ДатаКон);
// !!! Ошибка: ПостроительЗапроса.Выполнить() ничего не возвращает !!!
Запрос.Выполнить();
Результат = Запрос.Результат.Выгрузить(); // Здесь будет ошибка!
В этом примере мы пытаемся получить свойство Результат у объекта ПостроительЗапроса после вызова его метода Выполнить(), что некорректно. Правильный подход будет выглядеть так:
Построитель = Новый ПостроительЗапроса;
Построитель.Текст = "ВЫБРАТЬ
| Портфели.Владелец КАК Договор,
| Портфели.Владелец.Владелец КАК Клиент
|ИЗ
| Справочник.Портфели КАК Портфели
|ГДЕ
| Портфели.Владелец.Организация = &Организация";
Построитель.Параметры.Вставить("Организация", Организация);
// Выполняем построитель для подготовки запроса
Построитель.Выполнить();
// Получаем объект Запрос из ПостроителяЗапроса
ЗапросДляВыполнения = Построитель.ПолучитьЗапрос();
// Выполняем полученный Запрос и работаем с результатом
РезультатЗапроса = ЗапросДляВыполнения.Выполнить();
Результат = РезультатЗапроса.Выгрузить();
Важный момент: для установки параметров в ПостроительЗапроса мы используем коллекцию Параметры, вызывая метод Вставить(), а для объекта Запрос — метод УстановитьПараметр().
Синтаксические ошибки — это первое, с чем мы сталкиваемся при некорректном написании запроса. Всегда проверяйте ваш запрос в Конструкторе запросов. Он поможет выявить опечатки в ключевых словах, неправильное написание полей или некорректное использование операторов.
Давайте рассмотрим ваш исходный запрос:
"ВЫБРАТЬ
| Портфели.Владелец.Ссылка КАК Договор,
| ИСТИНА КАК Включать,
| Портфели.Владелец.Владелец КАК Клиент,
| Портфели.Ссылка КАК Портфель,
| ИсторияСтратегийСрезПоследних.Стратегия КАК Стратегия,
| Портфели.Владелец.СтратегияПоДоговору КАК СтратегияПоДоговору
|ИЗ
| Справочник.Портфели КАК Портфели
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ИсторияСтратегий.СрезПоследних(&ДатаКон, ) КАК ИсторияСтратегийСрезПоследних
| ПО Портфели.Ссылка = ИсторияСтратегийСрезПоследних.Объект
|ГДЕ
| Портфели.Владелец.ДоговорДУ = ИСТИНА
| И Портфели.Владелец.Организация = &Организация"
В этом запросе мы видим несколько разыменований ссылочных полей через точку, например, Портфели.Владелец.Ссылка и Портфели.Владелец.Владелец. Такие глубокие цепочки разыменований могут серьезно сказаться на производительности, особенно на больших объемах данных. Оптимизатор запросов СУБД может строить неэффективные планы выполнения, выбирая избыточные данные.
Наш совет: всегда старайтесь "развернуть" такие вложенные поля в явные соединения. Используйте ЛЕВОЕ СОЕДИНЕНИЕ или ВНУТРЕННЕЕ СОЕДИНЕНИЕ, чтобы связать таблицы по полям-ссылкам. Это позволяет СУБД работать более эффективно.
Например, вместо Портфели.Владелец.Владелец, мы можем поступить следующим образом:
"ВЫБРАТЬ
| Договоры.Ссылка КАК Договор,
| ИСТИНА КАК Включать,
| Договоры.Владелец КАК Клиент, // Теперь Владелец - это поле из таблицы Договоры
| Портфели.Ссылка КАК Портфель,
| ИсторияСтратегийСрезПоследних.Стратегия КАК Стратегия,
| Договоры.СтратегияПоДоговору КАК СтратегияПоДоговору
|ИЗ
| Справочник.Портфели КАК Портфели
| ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Договоры КАК Договоры
| ПО Портфели.Владелец = Договоры.Ссылка
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ИсторияСтратегий.СрезПоследних(&ДатаКон, ) КАК ИсторияСтратегийСрезПоследних
| ПО Портфели.Ссылка = ИсторияСтратегийСрезПоследних.Объект
|ГДЕ
| Договоры.ДоговорДУ = ИСТИНА
| И Договоры.Организация = &Организация"
Здесь мы явно присоединили таблицу Справочник.Договоры, что делает запрос более прозрачным и, как правило, более производительным. Если поле Владелец имеет составной тип, и вы хотите обращаться к конкретному типу, можете использовать функции ТИПЗНАЧЕНИЯ и ТИП в условиях или ССЫЛКА для связи.
В вашем запросе используется виртуальная таблица РегистрСведений.ИсторияСтратегий.СрезПоследних(&ДатаКон, ). Давайте разберем ее специфику:
Что такое СрезПоследних? Это механизм платформы 1С для получения самой последней записи регистра сведений на заданную дату для каждой уникальной комбинации измерений. Это очень удобно, но имеет свои особенности.
Параметры виртуальных таблиц: Обратите внимание на то, как передаются параметры. В вашем примере &ДатаКон передается в качестве параметра даты среза. Важно, что мы можем передать только одно значение даты.
Оптимизация: Всегда старайтесь использовать отборы внутри виртуальных таблиц, если это возможно, чтобы сократить объем выбираемых данных. Например, если бы вам нужен был срез последних только по определенным объектам, вы могли бы написать РегистрСведений.ИсторияСтратегий.СрезПоследних(&ДатаКон, Объект В (&СписокОбъектов)).
Альтернативы: Для получения среза последних на каждую дату для списка документов может потребоваться более сложный запрос с использованием временных таблиц и соединений. Также, если не требуется сложная логика запроса, можно использовать методы менеджера регистра сведений, такие как РегистрыСведений.ИсторияСтратегий.СрезПоследних().
Важным аспектом, который упоминался в обсуждении, является работа с расширениями и конструктором запросов. В платформе 1С:Предприятие, начиная с версии 8.3.23, были внесены значительные улучшения, которые существенно упрощают разработку запросов в расширениях:
Доступность объектов основной конфигурации: В конструктор запросов, который выполняется в расширении, добавлена специальная кнопка-переключатель "Отображать таблицы и поля расширяемой конфигурации". Это позволяет нам видеть и использовать объекты основной конфигурации, даже если они не были явно добавлены в расширение.
Автоматическое добавление объектов: При сохранении запроса, который использует объекты из основной конфигурации (например, справочники, документы, регистры), не добавленные в расширение, платформа теперь предлагает автоматически добавить эти объекты в расширение. Это избавляет от ручной работы и значительно ускоряет процесс разработки.
Если вы работаете в более старых версиях платформы, возможно, вам придется вручную добавлять используемые объекты основной конфигурации в расширение, чтобы они были доступны в запросах.
Чтобы наши запросы работали быстро и эффективно, давайте запомним несколько общих правил:
Избегайте глубокого разыменования: Как мы уже обсудили, цепочки типа Объект.Ссылка.Поле лучше заменять явными соединениями.
Фильтруйте данные как можно раньше: Накладывайте условия отбора в секции ГДЕ или, еще лучше, внутри виртуальных таблиц, чтобы СУБД сразу отбирала только нужные данные.
Используйте временные таблицы: Для сложных запросов с несколькими уровнями вложенности или для обработки промежуточных результатов, временные таблицы (ПОМЕСТИТЬ ...) часто более производительны, чем вложенные запросы в условиях соединения.
Осторожно с ОБЪЕДИНИТЬ: Оператор ОБЪЕДИНИТЬ позволяет объединять результаты нескольких запросов. Если вам нужны только уникальные значения, используйте ОБЪЕДИНИТЬ без ключевого слова ВСЕ. Однако помните, что каждый запрос выполняется отдельно, что может быть не всегда оптимально.
ВЫБОР КОГДА ТОГДА: Эта конструкция позволяет реализовать условную логику прямо в запросе, что очень удобно для формирования разных значений в зависимости от условий. Например: ВЫБОР КОГДА Условие1 ТОГДА Значение1 ИНАЧЕ Значение2 КОНЕЦ.
Анализируйте план запроса: В клиент-серверном варианте с СУБД (особенно PostgreSQL) производительность запросов может сильно зависеть от плана выполнения. Используйте средства СУБД для анализа плана, чтобы выявить "узкие места".
Принимая во внимание все вышесказанное, давайте посмотрим на возможный вариант оптимизированного запроса, который решает вашу задачу, избегая глубокого разыменования и используя объект Запрос для прямого выполнения:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ИСТИНА КАК Включать,
| Договоры.Ссылка КАК Договор,
| Договоры.Владелец КАК Клиент,
| Портфели.Ссылка КАК Портфель,
| ИсторияСтратегийСрезПоследних.Стратегия КАК Стратегия,
| Договоры.СтратегияПоДоговору КАК СтратегияПоДоговору
|ИЗ
| Справочник.Портфели КАК Портфели
| ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Договоры КАК Договоры
| ПО Портфели.Владелец = Договоры.Ссылка
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ИсторияСтратегий.СрезПоследних(&ДатаКон, ) КАК ИсторияСтратегийСрезПоследних
| ПО Портфели.Ссылка = ИсторияСтратегийСрезПоследних.Объект
|ГДЕ
| Договоры.ДоговорДУ = ИСТИНА
| И Договоры.Организация = &Организация";
Запрос.УстановитьПараметр("Организация", Организация);
Запрос.УстановитьПараметр("ДатаКон", ДатаКон);
РезультатЗапроса = Запрос.Выполнить();
Результат = РезультатЗапроса.Выгрузить();
// Если требуется свертка данных, выполняем ее после выгрузки
Результат.Свернуть("Включать, Договор, Клиент, СтратегияПоДоговору");
СписокКлиентов.Загрузить(Результат);
В этом примере мы явно присоединяем таблицу Справочник.Договоры, что делает запрос более понятным и производительным. Если поле Владелец в справочнике Портфели может ссылаться на другие типы объектов, а не только на договоры, нам может потребоваться дополнительная проверка типа или более сложное соединение.
Надеемся, что этот подробный разбор поможет вам создавать эффективные и безошибочные запросы в 1С:Предприятии!
← К списку