Запросы в 1С: от текста запроса до обработки результата
Содержание
Запрос в 1С выполняется в три действия: создаёте объект Запрос, кладёте в его свойство Текст текст на языке запросов, вызываете метод Выполнить(). Результат забираете методом Выбрать() (построчный обход) или Выгрузить() (сразу таблица значений).
Минимальный рабочий пример выглядит так:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Номенклатура.Ссылка КАК Ссылка,
| Номенклатура.Наименование КАК Наименование
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
|ГДЕ
| Номенклатура.ПометкаУдаления = ЛОЖЬ";
Результат = Запрос.Выполнить();
Выборка = Результат.Выбрать();
Пока Выборка.Следующий() Цикл
Сообщить(Выборка.Наименование);
КонецЦикла;
Ниже разберём каждый элемент этой конструкции, параметры, временные таблицы, различия между режимами платформы и то, что чаще всего ломает запрос.
Из чего состоит запрос: объект, текст и параметры
Объект создаётся конструктором Новый Запрос. Текст запроса — это отдельный язык, похожий на SQL, но со своими правилами и кириллическими ключевыми словами.
Каркас текста почти всегда один и тот же:
ВЫБРАТЬ— перечисляете поля, которые нужны в результате.ИЗ— источник: таблица справочника, документа, регистра или их виртуальная таблица.ГДЕ— условие отбора.СГРУППИРОВАТЬ ПО,УПОРЯДОЧИТЬ ПО,ИТОГИ ПО— группировка, сортировка, итоги.
Язык запросов в 1С умеет только читать данные. Операций вроде UPDATE или INSERT в нём нет — запись в базу идёт через объектную модель (документы, наборы записей). Единственное исключение — ПОМЕСТИТЬ, но оно кладёт данные во временную таблицу, а не в базу.
Значения в текст не вклеивают строками — для этого есть параметры. Параметр пишется в тексте с амперсандом, а значение задаётся отдельно:
Запрос.Текст =
"ВЫБРАТЬ
| Продажи.Ссылка КАК Документ,
| Продажи.Сумма КАК Сумма
|ИЗ
| Документ.РеализацияТоваровУслуг КАК Продажи
|ГДЕ
| Продажи.Дата МЕЖДУ &НачалоПериода И &КонецПериода
| И Продажи.Проведен = ИСТИНА";
Запрос.УстановитьПараметр("НачалоПериода", НачалоДня(ДатаНач));
Запрос.УстановитьПараметр("КонецПериода", КонецДня(ДатаКон));
Параметры дают два плюса сразу: платформа переиспользует план запроса, и пропадает риск сломать синтаксис при подстановке значения вручную.
Многострочный текст набирают через символ | в начале каждой строки — так работает перенос строки внутри строкового литерала встроенного языка.
Отдельная тема — виртуальные таблицы. У регистров накопления и остатков нельзя просто взять строки: за остатками и оборотами обращаются к виртуальным таблицам Остатки, Обороты, ОстаткиИОбороты. Платформа сама разворачивает их в эффективный запрос:
Запрос.Текст =
"ВЫБРАТЬ
| Остатки.Номенклатура КАК Номенклатура,
| Остатки.КоличествоОстаток КАК Остаток
|ИЗ
| РегистрНакопления.ТоварыНаСкладах.Остатки(&Дата, ) КАК Остатки";
Запрос.УстановитьПараметр("Дата", КонецДня(ТекущаяДата()));
Первый параметр виртуальной таблицы — момент времени, второй (после запятой) — условие отбора. Пустое значение означает «без дополнительного отбора». Отбор лучше задавать прямо в скобках виртуальной таблицы, а не в ГДЕ — так платформа отсекает лишние данные ещё при расчёте остатков, а не после него.
Выполнить(): запуск запроса на стороне базы
Метод Выполнить() отправляет текст в СУБД, выполняет его и возвращает объект РезультатЗапроса. Сам по себе он данные в переменные не раскладывает — он только отдаёт результат, с которым вы работаете дальше.
Иногда результат вам не нужен. Типичный случай — запрос, который лишь наполняет временную таблицу: вы вызываете Выполнить() и возвращённый объект просто игнорируете.
Запрос.Выполнить(); // данные легли во временную таблицу, результат не используем
Перед обработкой удобно проверить, вернул ли запрос хоть одну строку:
Результат = Запрос.Выполнить();
Если Результат.Пустой() Тогда
Возврат;
КонецЕсли;
Метод Пустой() быстрее, чем выгружать результат и считать строки. Проверяйте им запросы, после которых идёт тяжёлая обработка, — так вы не запускаете цикл вхолостую.
Когда в одном тексте стоит несколько запросов через ;, их выполняют не методом Выполнить(), а методом ВыполнитьПакет(). Он возвращает массив результатов — по одному на каждый запрос пакета, в порядке их следования:
МассивРезультатов = Запрос.ВыполнитьПакет();
РезультатПоследнего = МассивРезультатов[МассивРезультатов.ВГраница()];
Пакет удобен, когда первые запросы наполняют временные таблицы, а последний собирает итог: весь обмен с базой проходит за один вызов.
Выбрать() и Выгрузить(): как забрать результат
У РезультатЗапроса два основных способа отдать данные.
Выбрать() возвращает выборку — курсор, по которому идут методом Следующий(). Поля читаются как свойства по псевдониму из ВЫБРАТЬ ... КАК:
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
Сумма = Сумма + Выборка.Сумма;
КонецЦикла;
Выгрузить() сразу отдаёт таблицу значений со всеми строками:
ТаблицаПродаж = Запрос.Выполнить().Выгрузить();
Количество = ТаблицаПродаж.Количество();
Когда из запроса нужно одно-единственное значение — сумма, количество, остаток, — его забирают из первой строки выгрузки:
Таблица = Запрос.Выполнить().Выгрузить();
Остаток = ?(Таблица.Количество() > 0, Таблица[0].Остаток, 0);
Проверка на пустую таблицу обязательна: обращение к строке [0] у пустого результата вызовет ошибку.
Что выбрать:
| Способ | Когда уместен |
|---|---|
Выбрать() | нужен построчный обход, суммирование, запись в документ |
Выгрузить() | нужна готовая таблица — для табличной части, обработки, отчёта |
Если в запросе есть ИТОГИ ПО, выборку можно обойти по группировкам: Результат.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам). Тогда на верхнем уровне вы получаете группы, а внутри каждой — свою детальную выборку.
Отдельный частый сценарий — загрузить результат сразу в табличную часть документа или обработки. Выгруженную таблицу значений принимает метод Загрузить:
Объект.Товары.Загрузить(Запрос.Выполнить().Выгрузить());
Имена колонок выгрузки должны совпадать с именами реквизитов табличной части — поэтому псевдонимы в ВЫБРАТЬ ... КАК называют заранее под целевую структуру.
Не вызывайте Выполнить() внутри цикла по другой выборке. Один запрос на тысячу строк всегда быстрее тысячи мелких запросов — соберите данные соединением или временной таблицей заранее.
Временные таблицы и менеджер временных таблиц
Временная таблица — это промежуточный результат, который живёт только во время работы запросов и доступен следующим запросам того же менеджера. Без неё сложную выборку приходится собирать одним громоздким запросом с множеством соединений.
Чтобы временные таблицы работали, объекту запроса нужно назначить менеджер:
МенеджерВТ = Новый МенеджерВременныхТаблиц;
Запрос = Новый Запрос;
Запрос.МенеджерВременныхТаблиц = МенеджерВТ;
Запрос.Текст =
"ВЫБРАТЬ
| Товары.Номенклатура КАК Номенклатура,
| Товары.Количество КАК Количество
|ПОМЕСТИТЬ ВТ_Товары
|ИЗ
| Документ.РеализацияТоваровУслуг.Товары КАК Товары
|ГДЕ
| Товары.Ссылка = &Документ";
Запрос.УстановитьПараметр("Документ", СсылкаНаДокумент);
Запрос.Выполнить();
Ключевое слово ПОМЕСТИТЬ и имя таблицы (ВТ_Товары) создают временную таблицу. Теперь следующий запрос с тем же менеджером может на неё ссылаться:
Запрос.Текст =
"ВЫБРАТЬ
| ВТ_Товары.Номенклатура КАК Номенклатура,
| СУММА(ВТ_Товары.Количество) КАК Количество
|ИЗ
| ВТ_Товары КАК ВТ_Товары
|СГРУППИРОВАТЬ ПО
| ВТ_Товары.Номенклатура";
Выборка = Запрос.Выполнить().Выбрать();
Несколько запросов можно объединить и в один текст, разделив символом ; — это пакетный запрос. Тогда отдельные Выполнить() не нужны, пакет выполняется методом ВыполнитьПакет().
Если по временной таблице потом идёт соединение или отбор по какому-то полю, таблицу стоит проиндексировать. Для этого при создании добавляют ИНДЕКСИРОВАТЬ ПО с нужными полями:
"ВЫБРАТЬ
| Товары.Номенклатура КАК Номенклатура
|ПОМЕСТИТЬ ВТ_Товары
|ИЗ
| Документ.РеализацияТоваровУслуг.Товары КАК Товары
|ИНДЕКСИРОВАТЬ ПО
| Номенклатура"
Без индекса соединение с большой временной таблицей идёт полным перебором и заметно тормозит. Индексируют то поле, по которому таблица соединяется с другими, — чаще всего ссылку или измерение.
Менеджер очищает свои временные таблицы, когда переменная МенеджерВТ перестаёт существовать. Если переиспользуете менеджер в рамках одной процедуры и имя таблицы повторяется, явно пишите УНИЧТОЖИТЬ ВТ_Товары в начале следующего запроса.
Запросы на тонком клиенте и в разных режимах платформы
Сам язык запросов одинаков в 8.2 и 8.3 — тексты переносятся между версиями без правок. Различается не синтаксис, а то, где запрос разрешено выполнять.
В управляемом приложении (режим по умолчанию для новых конфигураций на 8.3) обращение к базе есть только на сервере. Вызвать Новый Запрос с чтением данных из клиентского кода тонкого или веб-клиента нельзя — код должен стоять в серверной процедуре:
&НаСервере
Процедура ЗагрузитьДанныеНаСервере()
Запрос = Новый Запрос;
// ... текст, параметры, Выполнить
КонецПроцедуры
В обычном приложении (толстый клиент) запрос можно выполнять и в клиентском контексте — поэтому старый код из 8.2 иногда не переносится в тонкий клиент как есть.
Вторая зависимость от режима — блокировки. Конструкция ДЛЯ ИЗМЕНЕНИЯ в тексте запроса действует только в режиме автоматических блокировок. В управляемом режиме, который включён по умолчанию в новых конфигурациях на 8.3, она игнорируется — там блокировку ставят объектом БлокировкаДанных до выполнения запроса.
Если нужно узнать, под какую версию платформы писать: ориентируйтесь на номер релиза не ниже того, что установлен у базы (меню Справка → О программе в режиме 1С:Предприятие). Запросы это не ломает, но функции языка в очень старых релизах могут отсутствовать.
Ещё одно различие практического характера: в файловой базе запрос выполняется локально, встроенной СУБД, а в клиент-серверной — на сервере 1С и уже дальше в СУБД (PostgreSQL, MS SQL и др.). Текст запроса от этого не меняется, но на больших данных план и скорость заметно зависят от того, насколько запрос дружит с индексами СУБД. Поэтому отбор и индексирование временных таблиц особенно важны именно в серверном варианте.
Отладка запроса: конструктор и консоль
Три инструмента закрывают почти всё.
Конструктор запроса. В Конфигураторе встаньте курсором внутрь строки с текстом запроса, вызовите контекстное меню правой кнопкой и выберите пункт конструктора. Он собирает текст визуально: таблицы, поля, соединения, условия — и показывает ошибки структуры сразу.
Консоль запросов. Внешняя обработка, которая выполняет произвольный текст запроса на рабочей базе без изменения конфигурации. Удобна, чтобы прогнать запрос с реальными параметрами и посмотреть результат до того, как встраивать его в код. Пакетные запросы в консоли показывают и промежуточные временные таблицы.
Отладчик. Поставьте точку останова после Выполнить(), откройте окно Табло или воспользуйтесь командой вычисления выражения — так вы увидите содержимое результата и значения параметров прямо во время работы.
При подозрении на медленный запрос скопируйте его текст и параметры в консоль и замерьте время там. Это отделяет проблему запроса от проблем остального кода обработки.
Типичные ошибки при выполнении запроса
Ошибки запросов почти всегда повторяются. Сверьтесь с диагностикой ниже.
КАК.УстановитьПараметр для него не вызван или имя написано по-разному. Установите все параметры, имена должны совпадать посимвольно.= NULL используйте ЕСТЬ NULL, а для подстановки значения — функцию ЕСТЬNULL(Поле, 0).МенеджерВременныхТаблиц, либо таблица уже уничтожена. Назначьте менеджер и выполняйте связанные запросы через него или одним пакетом.Частые вопросы
Чем `Выбрать()` отличается от `Выгрузить()`?
Выбрать() даёт курсор для построчного обхода методом Следующий(), строки читаются по одной. Выгрузить() сразу возвращает таблицу значений целиком. Для суммирования и записи в документ удобнее первый, для готовой таблицы — второй.Можно ли запросом изменить или удалить данные?
Почему запрос не выполняется на тонком клиенте?
&НаСервере.Что быстрее — соединение или запрос в цикле?
Нужно ли явно удалять временные таблицы?
УНИЧТОЖИТЬ нужен, если в рамках одного менеджера вы повторно создаёте таблицу с тем же именем.Чем `ОБЪЕДИНИТЬ` отличается от соединения?
ОБЪЕДИНИТЬ и ОБЪЕДИНИТЬ ВСЕ складывают строки двух выборок друг под друга (нужна одинаковая структура полей). Соединение (ЛЕВОЕ СОЕДИНЕНИЕ, ВНУТРЕННЕЕ СОЕДИНЕНИЕ) добавляет колонки, сопоставляя строки по условию. Это разные операции: одно про строки, другое про колонки.