Объект «Запрос» в 1С 8.3: от текста ВЫБРАТЬ до готовой выборки в модуле

· 9 мин

Содержание

Чтобы отправить запрос в 1С, создайте на сервере объект Новый Запрос, запишите текст на языке запросов в свойство Текст, задайте параметры через УстановитьПараметр() и вызовите Выполнить(). Метод вернёт результат, который обходят через Выбрать() или выгружают в таблицу значений через Выгрузить().

Дальше — рабочие примеры для модуля формы и модуля объекта, параметры, временные таблицы, пакетные запросы, ускорение медленных выборок и ошибки, на которых спотыкаются чаще всего. Всё относится к платформе 8.3 и управляемому приложению, конкретная конфигурация роли не играет.

Из чего состоит запрос к данным 1С

Запрос в 1С — это обращение к базе на собственном языке, похожем на SQL. Платформа сама переводит его в команды СУБД: файловой, MS SQL или PostgreSQL. Код при этом одинаковый.

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

ВЫБРАТЬ
	Номенклатура.Ссылка КАК Ссылка,
	Номенклатура.Наименование КАК Наименование
ИЗ
	Справочник.Номенклатура КАК Номенклатура
ГДЕ
	НЕ Номенклатура.ПометкаУдаления
УПОРЯДОЧИТЬ ПО
	Наименование

Основные части текста:

  • 📌 ВЫБРАТЬ — список полей. Сюда же пишут ПЕРВЫЕ 10, РАЗЛИЧНЫЕ, РАЗРЕШЕННЫЕ.
  • 📦 ИЗ — источник: справочник, документ, табличная часть, регистр или его виртуальная таблица.
  • 🔗 СОЕДИНЕНИЕ ... ПО — связь с другими таблицами.
  • 🔍 ГДЕ — отбор строк.
  • 📊 СГРУППИРОВАТЬ ПО и ИМЕЮЩИЕ — группировка и отбор по итогам.
  • ↕️ УПОРЯДОЧИТЬ ПО — сортировка.
  • 🧮 ИТОГИ ПО — промежуточные итоги для иерархического обхода.

Ключевые слова пишут на русском или английском (SELECT, FROM). Регистр букв не важен, но по стандарту разработки слова языка пишут заглавными.

Набирать текст руками не обязательно. В модуле щёлкните правой кнопкой мыши и выберите Конструктор запроса или Конструктор запроса с обработкой результата. Второй вариант сразу допишет цикл обхода выборки.

Как выполнить запрос программно в модуле формы и объекта

Объект Запрос доступен только на сервере (и в толстом клиенте обычного приложения). В тонком и веб-клиенте его нет. Поэтому в форме код запроса выносят в процедуру с директивой &НаСервере или в серверный общий модуль.

Шаг 1. Создайте процедуру на сервере

&НаКлиенте
Процедура Заполнить(Команда)
	ЗаполнитьНаСервере();
КонецПроцедуры

&НаСервере
Процедура ЗаполнитьНаСервере()

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

	Выборка = Запрос.Выполнить().Выбрать();
	Пока Выборка.Следующий() Цикл
		Сообщить(Выборка.Наименование);
	КонецЦикла;

КонецПроцедуры

Каждая строка многострочного текста начинается с вертикальной черты |. Кавычки внутри текста удваивают: ""ООО Ромашка"".

Шаг 2. Выберите способ получить результат

Выполнить() возвращает объект РезультатЗапроса. С ним работают тремя способами:

МетодЧто даётКогда брать
Выбрать()Последовательный курсор по строкамОбход больших выборок, обработка строки за строкой
Выгрузить()ТаблицаЗначений в памятиНужен поиск, сортировка, передача в другую процедуру
Пустой()Истина, если строк нетПроверить наличие данных без обхода

Проверка «есть ли хоть одна запись» делается так:

Запрос.Текст =
"ВЫБРАТЬ ПЕРВЫЕ 1
|	1 КАК Поле
|ИЗ
|	Документ.РеализацияТоваровУслуг КАК Док
|ГДЕ
|	Док.Контрагент = &Контрагент
|	И Док.Проведен";
Запрос.УстановитьПараметр("Контрагент", Контрагент);
ЕстьПродажи = НЕ Запрос.Выполнить().Пустой();

Имя документа в примере — из типовых конфигураций. В своей базе подставьте имя объекта из дерева метаданных.

Шаг 3. Запрос в модуле объекта

В модуле документа или справочника код уже выполняется на сервере. Запрос можно писать в обработчиках ПередЗаписью, ОбработкаПроведения, ОбработкаПроверкиЗаполнения. Ссылку на сам объект передают параметром:

Процедура ОбработкаПроверкиЗаполнения(Отказ, ПроверяемыеРеквизиты)

	Запрос = Новый Запрос;
	Запрос.Текст =
	"ВЫБРАТЬ ПЕРВЫЕ 1
	|	Док.Ссылка КАК Ссылка
	|ИЗ
	|	Документ.ЗаказКлиента КАК Док
	|ГДЕ
	|	Док.НомерВходящий = &Номер
	|	И Док.Ссылка <> &Ссылка
	|	И НЕ Док.ПометкаУдаления";
	Запрос.УстановитьПараметр("Номер", НомерВходящий);
	Запрос.УстановитьПараметр("Ссылка", Ссылка);

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

КонецПроцедуры

Для нового, ещё не записанного объекта Ссылка пустая, условие <> &Ссылка при этом работает корректно.

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

Значения отборов никогда не склеивают со строкой текста. Пишут параметр с амперсандом &ИмяПараметра и задают его значение до вызова Выполнить().

Почему параметры, а не конкатенация:

  • ✅ ссылки, даты и числа передаются как есть, без преобразования в строку;
  • ✅ платформа кеширует план запроса, повторные вызовы быстрее;
  • ✅ текст запроса не ломается из-за кавычек в наименованиях.

Отбор по списку значений

Для условия В в параметр передают Массив, СписокЗначений или колонку таблицы значений:

Склады = Новый Массив;
Склады.Добавить(ОсновнойСклад);
Склады.Добавить(СкладРозницы);

Запрос.Текст =
"ВЫБРАТЬ
|	Товары.Номенклатура КАК Номенклатура,
|	Товары.Склад КАК Склад
|ИЗ
|	РегистрСведений.ЦеныНоменклатуры КАК Товары
|ГДЕ
|	Товары.Склад В (&Склады)";
Запрос.УстановитьПараметр("Склады", Склады);

Регистр в примере условный: замените его и поля на свои.

Параметры виртуальных таблиц

У регистров есть виртуальные таблицы: Остатки, Обороты, ОстаткиИОбороты, СрезПоследних. Период и отбор в них задают прямо в скобках:

"ВЫБРАТЬ
|	Остатки.Номенклатура КАК Номенклатура,
|	Остатки.КоличествоОстаток КАК Количество
|ИЗ
|	РегистрНакопления.ТоварыНаСкладах.Остатки(&НаДату, Склад = &Склад) КАК Остатки"

Если передать в &НаДату обычную дату, остаток считается на начало этого момента, без движений в эту секунду. Чтобы включить их, передайте границу:

Запрос.УстановитьПараметр("НаДату", Новый Граница(ДатаОстатка, ВидГраницы.Включая));

Отбор по измерениям регистра пишите в параметрах виртуальной таблицы, а не в ГДЕ. Условие в ГДЕ накладывается уже после расчёта остатков по всем складам, и запрос работает в разы медленнее.

Пустые значения и NULL

Поля из левого соединения, где пары не нашлось, возвращают NULL. Сравнение с ним через = не работает , нужно ЕСТЬ NULL или функция ЕСТЬNULL(Поле, 0). Пустую ссылку передают параметром: Справочники.Склады.ПустаяСсылка().

Временные таблицы, пакеты и объединения

Сложную выборку удобнее собирать по частям. Для этого запросы объединяют в пакет: тексты разделяют точкой с запятой, а промежуточные результаты складывают во временные таблицы через ПОМЕСТИТЬ.

Пакетный запрос с временной таблицей

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

Результаты = Запрос.ВыполнитьПакет();
Таблица = Результаты[1].Выгрузить();

ВыполнитьПакет() возвращает массив результатов по порядку запросов. Индексы начинаются с нуля: здесь Результаты[0] — служебный результат ПОМЕСТИТЬ, Результаты[1] — нужная выборка. Если интересен только последний запрос, достаточно обычного Выполнить(): он вернёт результат последнего запроса пакета.

Зачем нужен менеджер временных таблиц

Без менеджера временные таблицы живут только внутри одного пакета. С менеджером они доступны следующим запросам, которым присвоен тот же МенеджерВременныхТаблиц. Так данные передают между процедурами, не гоняя таблицы значений через память. Ненужную таблицу удаляют командой УНИЧТОЖИТЬ ВТ_Товары или закрывают менеджер методом Закрыть().

Объединение и соединение — не одно и то же

КонструкцияЧто делаетПример задачи
ОБЪЕДИНИТЬ ВСЕСклеивает строки двух запросов друг под другомПродажи и возвраты одним списком
ОБЪЕДИНИТЬТо же, но убирает дублиУникальный список контрагентов из двух журналов
ЛЕВОЕ СОЕДИНЕНИЕДобавляет колонки из второй таблицы, все строки первой сохраняютсяТовары заказа + остатки
ВНУТРЕННЕЕ СОЕДИНЕНИЕОставляет только строки, у которых есть параТовары, по которым есть цена

В запросах, которые объединяются, должно совпадать число полей. Имена и типы колонок берутся из первого запроса. ОБЪЕДИНИТЬ без ВСЕ медленнее: платформе приходится искать дубликаты.

Как ускорить запрос и где смотреть план выполнения

Медленный запрос почти всегда тормозит из-за одной из пяти причин. Проверяйте их по порядку.

  1. Запрос в цикле. Сто документов — сто обращений к базе. Соберите ссылки в массив и выполните один запрос с условием В (&Список) или соединением с временной таблицей.
  2. Отбор в ГДЕ вместо параметров виртуальной таблицы. Перенесите условия по измерениям в скобки Остатки(...).
  3. Разыменование составных полей через точку. Запись Регистратор.Контрагент при составном типе превращается в соединение со всеми документами-регистраторами. Уточните тип: ВЫРАЗИТЬ(Регистратор КАК Документ.РеализацияТоваровУслуг).Контрагент.
  4. Условия с ИЛИ по разным полям. СУБД часто не может применить индекс. Помогает разбивка на два запроса с ОБЪЕДИНИТЬ ВСЕ.
  5. Соединение с подзапросом. Оптимизатор плохо оценивает число строк подзапроса. Поместите его во временную таблицу с ИНДЕКСИРОВАТЬ ПО.

Инструменты замера

  • ⏱ Замер производительности в Конфигураторе: Отладка → Замер производительности. Показывает, какая строка кода сколько времени заняла, в том числе вызов Выполнить().
  • 🔎 Консоль запросов — внешняя обработка, где запрос пишут, задают параметры и смотрят результат без программирования. В режиме отладки текст запроса копируют из переменной Запрос.Текст прямо в консоль.
  • 📄 Технологический журнал на сервере. В файле logcfg.xml включают события DBMSSQL или DBPOSTGRS с порогом длительности, а элемент <plansql/> добавляет планы запросов для MS SQL.
  • 🗄 Средства СУБД: SQL Server Profiler и планы в Management Studio, для PostgreSQL — EXPLAIN ANALYZE по перехваченному тексту.

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

Права и РАЗРЕШЕННЫЕ

Если в базе настроено ограничение доступа на уровне записей, запрос без слова РАЗРЕШЕННЫЕ у пользователя с ограниченными правами упадёт с ошибкой доступа. С ВЫБРАТЬ РАЗРЕШЕННЫЕ недоступные строки просто не попадут в результат. В пакетном запросе слово ставят в каждый запрос, который читает защищённые таблицы.

Ошибки при выполнении запроса и как их убрать

Текст ошибки запроса содержит позицию в фигурных скобках, например {(12, 5)}: строка и колонка внутри текста запроса, а не модуля. Скопируйте текст в конструктор или консоль — курсор встанет на проблемное место.

Диагностика: выберите симптом

Ещё две ловушки, которые не дают ошибки, но дают неверный результат:

  • ⚡ Агрегат по левому соединению. СУММА по полю из правой таблицы при нескольких совпадениях умножает значения. Сначала сгруппируйте правую таблицу во временную, потом соединяйте.
  • ⚡ РАЗЛИЧНЫЕ вместо группировки. Слово убирает одинаковые строки целиком. Если нужно свернуть количество по товару, нужен СГРУППИРОВАТЬ ПО с СУММА.

Частые вопросы

Можно ли выполнить запрос прямо на клиенте?
В тонком и веб-клиенте — нет, объекта Запрос там не существует. Вызовите серверную процедуру формы или общего модуля и верните на клиент уже готовые данные.
Чем отличаются Выполнить() и ВыполнитьПакет()?
Выполнить() возвращает результат последнего запроса пакета. ВыполнитьПакет() возвращает массив результатов всех запросов, индексы с нуля.
Как получить результат в табличную часть или список формы?
Выгрузите результат в таблицу значений через Выгрузить() и загрузите её методом Загрузить() табличной части. Колонки нужно назвать в запросе так же, как реквизиты табличной части.
Как выбрать актуальную цену на дату?
Используйте виртуальную таблицу СрезПоследних периодического регистра сведений с датой в первом параметре и отбором по виду цены во втором.
Как вывести иерархию с итогами по группам?
Добавьте в конец текста ИТОГИ СУММА(Количество) ПО Номенклатура ИЕРАРХИЯ и обходите результат через Выбрать(ОбходРезультатаЗапроса.ПоГруппировкамСИерархией).