Запросы в 1С: от текста запроса до обработки результата

· 9 мин

Содержание

Запрос в 1С выполняется в три действия: создаёте объект Запрос, кладёте в его свойство Текст текст на языке запросов, вызываете метод Выполнить(). Результат забираете методом Выбрать() (построчный обход) или Выгрузить() (сразу таблица значений).

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

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

Результат = Запрос.Выполнить();
Выборка = Результат.Выбрать();

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

Ниже разберём каждый элемент этой конструкции, параметры, временные таблицы, различия между режимами платформы и то, что чаще всего ломает запрос.

Из чего состоит запрос: объект, текст и параметры

Объект создаётся конструктором Новый Запрос. Текст запроса — это отдельный язык, похожий на SQL, но со своими правилами и кириллическими ключевыми словами.

Каркас текста почти всегда один и тот же:

  • ВЫБРАТЬ — перечисляете поля, которые нужны в результате.
  • ИЗ — источник: таблица справочника, документа, регистра или их виртуальная таблица.
  • ГДЕ — условие отбора.
  • СГРУППИРОВАТЬ ПО, УПОРЯДОЧИТЬ ПО, ИТОГИ ПО — группировка, сортировка, итоги.

Язык запросов в 1С умеет только читать данные. Операций вроде UPDATE или INSERT в нём нет — запись в базу идёт через объектную модель (документы, наборы записей). Единственное исключение — ПОМЕСТИТЬ, но оно кладёт данные во временную таблицу, а не в базу.

Значения в текст не вклеивают строками — для этого есть параметры. Параметр пишется в тексте с амперсандом, а значение задаётся отдельно:

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

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

Параметры дают два плюса сразу: платформа переиспользует план запроса, и пропадает риск сломать синтаксис при подстановке значения вручную.

Многострочный текст набирают через символ | в начале каждой строки — так работает перенос строки внутри строкового литерала встроенного языка.

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

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

Первый параметр виртуальной таблицы — момент времени, второй (после запятой) — условие отбора. Пустое значение означает «без дополнительного отбора». Отбор лучше задавать прямо в скобках виртуальной таблицы, а не в ГДЕ — так платформа отсекает лишние данные ещё при расчёте остатков, а не после него.

Выполнить(): запуск запроса на стороне базы

Метод Выполнить() отправляет текст в СУБД, выполняет его и возвращает объект РезультатЗапроса. Сам по себе он данные в переменные не раскладывает — он только отдаёт результат, с которым вы работаете дальше.

Иногда результат вам не нужен. Типичный случай — запрос, который лишь наполняет временную таблицу: вы вызываете Выполнить() и возвращённый объект просто игнорируете.

Запрос.Выполнить(); // данные легли во временную таблицу, результат не используем

Перед обработкой удобно проверить, вернул ли запрос хоть одну строку:

Результат = Запрос.Выполнить();
Если Результат.Пустой() Тогда
    Возврат;
КонецЕсли;

Метод Пустой() быстрее, чем выгружать результат и считать строки. Проверяйте им запросы, после которых идёт тяжёлая обработка, — так вы не запускаете цикл вхолостую.

Когда в одном тексте стоит несколько запросов через ;, их выполняют не методом Выполнить(), а методом ВыполнитьПакет(). Он возвращает массив результатов — по одному на каждый запрос пакета, в порядке их следования:

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

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

Выбрать() и Выгрузить(): как забрать результат

У РезультатЗапроса два основных способа отдать данные.

Выбрать() возвращает выборку — курсор, по которому идут методом Следующий(). Поля читаются как свойства по псевдониму из ВЫБРАТЬ ... КАК:

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

Выгрузить() сразу отдаёт таблицу значений со всеми строками:

ТаблицаПродаж = Запрос.Выполнить().Выгрузить();
Количество = ТаблицаПродаж.Количество();

Когда из запроса нужно одно-единственное значение — сумма, количество, остаток, — его забирают из первой строки выгрузки:

Таблица = Запрос.Выполнить().Выгрузить();
Остаток = ?(Таблица.Количество() > 0, Таблица[0].Остаток, 0);

Проверка на пустую таблицу обязательна: обращение к строке [0] у пустого результата вызовет ошибку.

Что выбрать:

СпособКогда уместен
Выбрать()нужен построчный обход, суммирование, запись в документ
Выгрузить()нужна готовая таблица — для табличной части, обработки, отчёта

Если в запросе есть ИТОГИ ПО, выборку можно обойти по группировкам: Результат.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам). Тогда на верхнем уровне вы получаете группы, а внутри каждой — свою детальную выборку.

Отдельный частый сценарий — загрузить результат сразу в табличную часть документа или обработки. Выгруженную таблицу значений принимает метод Загрузить:

Объект.Товары.Загрузить(Запрос.Выполнить().Выгрузить());

Имена колонок выгрузки должны совпадать с именами реквизитов табличной части — поэтому псевдонимы в ВЫБРАТЬ ... КАК называют заранее под целевую структуру.

Не вызывайте Выполнить() внутри цикла по другой выборке. Один запрос на тысячу строк всегда быстрее тысячи мелких запросов — соберите данные соединением или временной таблицей заранее.

Временные таблицы и менеджер временных таблиц

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

Чтобы временные таблицы работали, объекту запроса нужно назначить менеджер:

МенеджерВТ = Новый МенеджерВременныхТаблиц;

Запрос = Новый Запрос;
Запрос.МенеджерВременныхТаблиц = МенеджерВТ;

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

Ключевое слово ПОМЕСТИТЬ и имя таблицы (ВТ_Товары) создают временную таблицу. Теперь следующий запрос с тем же менеджером может на неё ссылаться:

Запрос.Текст =
"ВЫБРАТЬ
|    ВТ_Товары.Номенклатура КАК Номенклатура,
|    СУММА(ВТ_Товары.Количество) КАК Количество
|ИЗ
|    ВТ_Товары КАК ВТ_Товары
|СГРУППИРОВАТЬ ПО
|    ВТ_Товары.Номенклатура";
Выборка = Запрос.Выполнить().Выбрать();

Несколько запросов можно объединить и в один текст, разделив символом ; — это пакетный запрос. Тогда отдельные Выполнить() не нужны, пакет выполняется методом ВыполнитьПакет().

Если по временной таблице потом идёт соединение или отбор по какому-то полю, таблицу стоит проиндексировать. Для этого при создании добавляют ИНДЕКСИРОВАТЬ ПО с нужными полями:

"ВЫБРАТЬ
|    Товары.Номенклатура КАК Номенклатура
|ПОМЕСТИТЬ ВТ_Товары
|ИЗ
|    Документ.РеализацияТоваровУслуг.Товары КАК Товары
|ИНДЕКСИРОВАТЬ ПО
|    Номенклатура"

Без индекса соединение с большой временной таблицей идёт полным перебором и заметно тормозит. Индексируют то поле, по которому таблица соединяется с другими, — чаще всего ссылку или измерение.

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

Запросы на тонком клиенте и в разных режимах платформы

Сам язык запросов одинаков в 8.2 и 8.3 — тексты переносятся между версиями без правок. Различается не синтаксис, а то, где запрос разрешено выполнять.

В управляемом приложении (режим по умолчанию для новых конфигураций на 8.3) обращение к базе есть только на сервере. Вызвать Новый Запрос с чтением данных из клиентского кода тонкого или веб-клиента нельзя — код должен стоять в серверной процедуре:

&НаСервере
Процедура ЗагрузитьДанныеНаСервере()
    Запрос = Новый Запрос;
    // ... текст, параметры, Выполнить
КонецПроцедуры

В обычном приложении (толстый клиент) запрос можно выполнять и в клиентском контексте — поэтому старый код из 8.2 иногда не переносится в тонкий клиент как есть.

Вторая зависимость от режима — блокировки. Конструкция ДЛЯ ИЗМЕНЕНИЯ в тексте запроса действует только в режиме автоматических блокировок. В управляемом режиме, который включён по умолчанию в новых конфигурациях на 8.3, она игнорируется — там блокировку ставят объектом БлокировкаДанных до выполнения запроса.

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

Ещё одно различие практического характера: в файловой базе запрос выполняется локально, встроенной СУБД, а в клиент-серверной — на сервере 1С и уже дальше в СУБД (PostgreSQL, MS SQL и др.). Текст запроса от этого не меняется, но на больших данных план и скорость заметно зависят от того, насколько запрос дружит с индексами СУБД. Поэтому отбор и индексирование временных таблиц особенно важны именно в серверном варианте.

Отладка запроса: конструктор и консоль

Три инструмента закрывают почти всё.

Конструктор запроса. В Конфигураторе встаньте курсором внутрь строки с текстом запроса, вызовите контекстное меню правой кнопкой и выберите пункт конструктора. Он собирает текст визуально: таблицы, поля, соединения, условия — и показывает ошибки структуры сразу.

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

Отладчик. Поставьте точку останова после Выполнить(), откройте окно Табло или воспользуйтесь командой вычисления выражения — так вы увидите содержимое результата и значения параметров прямо во время работы.

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

Типичные ошибки при выполнении запроса

Ошибки запросов почти всегда повторяются. Сверьтесь с диагностикой ниже.

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

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

Чем `Выбрать()` отличается от `Выгрузить()`?
Выбрать() даёт курсор для построчного обхода методом Следующий(), строки читаются по одной. Выгрузить() сразу возвращает таблицу значений целиком. Для суммирования и записи в документ удобнее первый, для готовой таблицы — второй.
Можно ли запросом изменить или удалить данные?
Нет. Язык запросов в 1С только читает. Запись, проведение и удаление выполняются объектной моделью: документами, наборами записей регистров, менеджерами объектов.
Почему запрос не выполняется на тонком клиенте?
В управляемом приложении доступ к базе есть только на сервере. Вынесите создание и выполнение запроса в процедуру с директивой &НаСервере.
Что быстрее — соединение или запрос в цикле?
Соединение. Один запрос на всю выборку почти всегда обгоняет множество мелких запросов внутри цикла, потому что обращение к базе дорогое само по себе.
Нужно ли явно удалять временные таблицы?
Менеджер очищает их при разрушении автоматически. Явный УНИЧТОЖИТЬ нужен, если в рамках одного менеджера вы повторно создаёте таблицу с тем же именем.
Чем `ОБЪЕДИНИТЬ` отличается от соединения?
ОБЪЕДИНИТЬ и ОБЪЕДИНИТЬ ВСЕ складывают строки двух выборок друг под друга (нужна одинаковая структура полей). Соединение (ЛЕВОЕ СОЕДИНЕНИЕ, ВНУТРЕННЕЕ СОЕДИНЕНИЕ) добавляет колонки, сопоставляя строки по условию. Это разные операции: одно про строки, другое про колонки.