Язык запросов 1С 8.3: от первого ВЫБРАТЬ до пакетов и временных таблиц
Содержание
Запрос в 1С делают так: создают объект Новый Запрос, кладут в свойство Текст строку на языке запросов (ВЫБРАТЬ … ИЗ … ГДЕ …), передают параметры через УстановитьПараметр() и вызывают Выполнить(). Текст удобнее всего собирать конструктором запроса: в модуле щёлкните правой кнопкой мыши и выберите «Конструктор запроса».
Язык запросов 1С похож на SQL, но работает не с таблицами СУБД, а с объектами конфигурации: справочниками, документами, регистрами. Отсюда его сильные стороны (разыменование ссылок через точку, виртуальные таблицы регистров) и ловушки, из-за которых запрос, написанный «как в SQL», работает медленно или возвращает не то. Имена объектов в примерах ниже условные — подставьте свои из дерева конфигурации.
Как устроен запрос в 1С и где его писать
Минимальный рабочий пример в модуле на сервере:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Номенклатура.Ссылка КАК Номенклатура,
| Номенклатура.Артикул КАК Артикул
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
|ГДЕ
| НЕ Номенклатура.ПометкаУдаления
| И НЕ Номенклатура.ЭтоГруппа";
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
Сообщить(Строка(Выборка.Номенклатура) + " — " + Выборка.Артикул);
КонецЦикла;
Символ | в начале строки — продолжение многострочного литерала во встроенном языке, к самому языку запросов он отношения не имеет.
Порядок предложений
Предложения идут строго в таком порядке, лишние опускаются:
| Предложение | Что делает |
|---|---|
ВЫБРАТЬ [РАЗРЕШЕННЫЕ] [РАЗЛИЧНЫЕ] [ПЕРВЫЕ N] | список полей |
ПОМЕСТИТЬ ИмяВТ | сохранить результат во временную таблицу |
ИЗ | источники и соединения |
ГДЕ | отбор записей до группировки |
СГРУППИРОВАТЬ ПО | группировка |
ИМЕЮЩИЕ | отбор после группировки |
ОБЪЕДИНИТЬ [ВСЕ] | следующий запрос объединения |
УПОРЯДОЧИТЬ ПО | сортировка |
ИТОГИ … ПО | промежуточные итоги для иерархической выборки |
ИНДЕКСИРОВАТЬ ПО | индекс временной таблицы |
Ключевые слова не зависят от регистра. Есть и англоязычный синтаксис (SELECT, FROM, WHERE) — платформа понимает оба, но в одном проекте лучше держаться одного.
Конструктор и консоль
- 🧩 Конструктор запроса собирает текст по вкладкам «Таблицы и поля», «Связи», «Группировка», «Условия», «Объединения/Псевдонимы», «Порядок». Если поставить курсор внутрь уже готового текста и снова открыть конструктор, он разберёт запрос обратно.
- 🧪 Консоль запросов — внешняя обработка, которую подключают к базе через
Файл → Открыть. В ней запрос выполняют без кода и сразу видят результат. Пишите и отлаживайте текст там, а в модуль переносите готовый.
Выполняйте запросы на сервере (&НаСервере или в общем модуле). Объект Запрос на клиенте в тонком клиенте недоступен.
Отбор данных: условия и параметры запроса
Условия пишут в предложении ГДЕ. Значения никогда не склеивают в текст строкой — их передают параметрами с амперсандом:
Запрос.Текст =
"ВЫБРАТЬ
| Реализация.Ссылка,
| Реализация.СуммаДокумента
|ИЗ
| Документ.РеализацияТоваровУслуг КАК Реализация
|ГДЕ
| Реализация.Дата МЕЖДУ &НачалоПериода И &КонецПериода
| И Реализация.Контрагент = &Контрагент
| И Реализация.Проведен";
Запрос.УстановитьПараметр("НачалоПериода", НачалоМесяца(ТекущаяДатаСеанса()));
Запрос.УстановитьПараметр("КонецПериода", КонецМесяца(ТекущаяДатаСеанса()));
Запрос.УстановитьПараметр("Контрагент", Контрагент);
Параметры защищают от ошибок с форматом дат и чисел, а СУБД может переиспользовать план запроса.
Операторы, которые нужны каждый день
- 🔹
=,<>,<,>,<=,>=— сравнение; - 🔹
МЕЖДУ … И …— диапазон, границы включаются; - 🔹
В (&СписокЗначений)— вхождение в массив или список значений, переданный параметром;В (ВЫБРАТЬ …)— вхождение в результат подзапроса; - 🔹
В ИЕРАРХИИ (&Группа)— элемент лежит в группе справочника или в её подгруппах; - 🔹
ПОДОБНО "%болт%"— поиск по шаблону:%— любые символы,_— один символ; - 🔹
ЕСТЬ NULL/НЕ ЕСТЬ NULL— проверка на NULL; - 🔹
ССЫЛКА Документ.ПоступлениеТоваровУслуг— проверка типа поля составного типа.
Предопределённые значения и перечисления подставляют функцией ЗНАЧЕНИЕ():
ГДЕ
Договоры.ВидДоговора = ЗНАЧЕНИЕ(Перечисление.ВидыДоговоровКонтрагентов.СПокупателем)
И Договоры.Владелец <> ЗНАЧЕНИЕ(Справочник.Контрагенты.ПустаяСсылка)
Для вычисляемых полей есть ВЫБОР КОГДА … ТОГДА … ИНАЧЕ … КОНЕЦ, для дат — НАЧАЛОПЕРИОДА(Дата, МЕСЯЦ), ДОБАВИТЬКДАТЕ(), РАЗНОСТЬДАТ(), ГОД(), МЕСЯЦ().
Соединения таблиц в запросах 1С
Соединение задают в предложении ИЗ и условие связи пишут после ПО:
ВЫБРАТЬ
Контрагенты.Ссылка КАК Контрагент,
ЕСТЬNULL(Долги.Сумма, 0) КАК Долг
ИЗ
Справочник.Контрагенты КАК Контрагенты
ЛЕВОЕ СОЕДИНЕНИЕ ВТДолги КАК Долги
ПО Контрагенты.Ссылка = Долги.Контрагент
| Вид соединения | Что вернёт | Когда брать |
|---|---|---|
ВНУТРЕННЕЕ СОЕДИНЕНИЕ | только пары, где совпало условие | нужны записи, у которых есть пара |
ЛЕВОЕ СОЕДИНЕНИЕ | все строки левой таблицы, справа NULL, если пары нет | справочник + данные, которых может не быть |
ПРАВОЕ СОЕДИНЕНИЕ | зеркально левому | редко, проще переставить таблицы |
ПОЛНОЕ СОЕДИНЕНИЕ | все строки обеих таблиц | сравнение двух наборов: план и факт, остатки по двум источникам |
После левого и полного соединения поля «пустой» стороны равны NULL. Оборачивайте их в ЕСТЬNULL(Поле, 0) — иначе суммы и сравнения дадут NULL вместо числа.
Разыменование через точку
Поле ссылочного типа можно «раскрыть» прямо в запросе: Реализация.Контрагент.ИНН. Платформа сама добавит соединение со справочником. Это удобно, но каждая точка — ещё одно соединение. Для полей составного типа (например, Регистратор) платформа соединяет со всеми возможными таблицами типа, и запрос раздувается в десятки соединений. Если тип известен, сузьте его: ВЫРАЗИТЬ(Движения.Регистратор КАК Документ.РеализацияТоваровУслуг).Дата.
Группировка и агрегатные функции
Агрегатные функции сворачивают несколько строк в одну:
- ➕
СУММА()— сумма; - 🔢
КОЛИЧЕСТВО()иКОЛИЧЕСТВО(РАЗЛИЧНЫЕ Поле)— число строк или уникальных значений; - ⬆️
МАКСИМУМ(), ⬇️МИНИМУМ(); - ⚖️
СРЕДНЕЕ().
Правило одно: каждое поле в ВЫБРАТЬ либо стоит под агрегатной функцией, либо перечислено в СГРУППИРОВАТЬ ПО. Иначе платформа выдаст ошибку о том, что поле не входит в группу.
ВЫБРАТЬ
Товары.Номенклатура КАК Номенклатура,
СУММА(Товары.Количество) КАК Количество,
СУММА(Товары.Сумма) КАК Сумма
ИЗ
Документ.РеализацияТоваровУслуг.Товары КАК Товары
ГДЕ
Товары.Ссылка.Проведен
И Товары.Ссылка.Дата МЕЖДУ &НачалоПериода И &КонецПериода
СГРУППИРОВАТЬ ПО
Товары.Номенклатура
ИМЕЮЩИЕ
СУММА(Товары.Сумма) > 100000
УПОРЯДОЧИТЬ ПО
Сумма УБЫВ
Обратите внимание на источник Документ.РеализацияТоваровУслуг.Товары — так в запросе обращаются к табличной части. Поле Ссылка табличной части ведёт на сам документ.
ГДЕ отсекает строки до группировки, ИМЕЮЩИЕ — готовые группы. Условие на агрегат (сумма больше 100 000) ставят только в ИМЕЮЩИЕ.
ИТОГИ ПО
Предложение ИТОГИ СУММА(Сумма) ПО Номенклатура ИЕРАРХИЯ добавляет к результату строки промежуточных итогов, в том числе по группам справочника. Обходят такой результат через Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам). Для отчёта на СКД итоги в тексте не нужны: их считает сама схема.
Объединение результатов нескольких запросов
ОБЪЕДИНИТЬ ставит результаты двух и более запросов друг под другом. Типичный случай — собрать поступления и выбытия в одну таблицу:
ВЫБРАТЬ
Поступление.Номенклатура КАК Номенклатура,
Поступление.Количество КАК Приход,
0 КАК Расход
ИЗ
ВТПоступления КАК Поступление
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ
Продажи.Номенклатура,
0,
Продажи.Количество
ИЗ
ВТПродажи КАК Продажи
Что соблюдать:
- Число полей во всех частях одинаковое, порядок совпадает.
- Имена полей и псевдонимы берутся из первого запроса.
ОБЪЕДИНИТЬбезВСЕудаляет полностью одинаковые строки. Две одинаковые продажи на 5 штук превратятся в одну. Если дубли не нужно схлопывать, пишитеОБЪЕДИНИТЬ ВСЕ— это ещё и быстрее.УПОРЯДОЧИТЬ ПОставят один раз, в конце, и оно сортирует весь результат.
Чтобы получить итог «приход − расход» по каждому товару, объединение помещают во вложенный запрос или временную таблицу и группируют сверху.
Виртуальные таблицы регистров
У регистров есть виртуальные таблицы, которые платформа считает сама: Остатки, Обороты, ОстаткиИОбороты у регистров накопления, СрезПоследних и СрезПервых у периодических регистров сведений. Их параметры пишут в скобках после имени:
ВЫБРАТЬ
Остатки.Номенклатура,
Остатки.КоличествоОстаток
ИЗ
РегистрНакопления.ТоварыНаСкладах.Остатки(&Дата, Склад = &Склад) КАК Остатки
- 📅 Первый параметр
Остатки— момент времени. Если не указать, получите текущие остатки. - 🎯 Второй — условие. Отбор по измерениям ставьте сюда, а не в
ГДЕ: тогда платформа отфильтрует данные до расчёта остатков, а не после. - 📈 У
ОборотыиОстаткиИОборотыесть начало, конец периода и периодичность (День,Месяци т. д.).
У СрезПоследних место условия меняет смысл. СрезПоследних(&Дата, Номенклатура = &Товар) вернёт последнюю цену этого товара. А тот же отбор в ГДЕ по полю ресурса, например ГДЕ Цена > 0, сначала возьмёт последние записи и только потом выкинет нулевые — товар, у которого последняя цена нулевая, пропадёт, хотя раньше у него была ненулевая.
Пакетные запросы и временные таблицы
Сложную выборку разбивают на шаги. Каждый шаг кладут во временную таблицу через ПОМЕСТИТЬ, запросы пакета разделяют точкой с запятой:
Запрос = Новый Запрос;
Запрос.МенеджерВременныхТаблиц = Новый МенеджерВременныхТаблиц;
Запрос.Текст =
"ВЫБРАТЬ
| Товары.Ссылка КАК Номенклатура
|ПОМЕСТИТЬ ВТТовары
|ИЗ
| Справочник.Номенклатура КАК Товары
|ГДЕ
| Товары.Ссылка В ИЕРАРХИИ (&Группа)
|ИНДЕКСИРОВАТЬ ПО
| Номенклатура
|;
|
|////////////////////////////////////////
|ВЫБРАТЬ
| Остатки.Номенклатура,
| Остатки.КоличествоОстаток
|ИЗ
| РегистрНакопления.ТоварыНаСкладах.Остатки(
| &Дата,
| Номенклатура В
| (ВЫБРАТЬ ВТТовары.Номенклатура ИЗ ВТТовары)) КАК Остатки";
Запрос.УстановитьПараметр("Группа", Группа);
Запрос.УстановитьПараметр("Дата", ТекущаяДатаСеанса());
Результаты = Запрос.ВыполнитьПакет();
Выборка = Результаты[1].Выбрать();
Как это работает:
- 📦
Выполнить()возвращает результат последнего запроса пакета,ВыполнитьПакет()— массив результатов всех запросов, нумерация с нуля. - 🔗
МенеджерВременныхТаблицдержит таблицы живыми между вызовами. Передайте тот же менеджер другому запросу — и он увидитВТТовары. - 🗑️
УНИЧТОЖИТЬ ВТТоварыосвобождает таблицу, когда она больше не нужна. Это полезно в длинных пакетах с крупными таблицами. - 📇
ИНДЕКСИРОВАТЬ ПОимеет смысл для полей, по которым временную таблицу потом соединяют или фильтруют, и когда строк много. На сотне строк индекс ничего не даст. - 📥 Временную таблицу можно заполнить и из таблицы значений:
ВЫБРАТЬ … ПОМЕСТИТЬ ВТ ИЗ &ТаблицаЗначений КАК Т, передав таблицу параметром. Колонкам таблицы значений тогда нужно задать типы.
Как ускорить запрос
- Отбирайте как можно раньше: условия в параметрах виртуальных таблиц и в первом шаге пакета.
- Заменяйте подзапросы в соединениях временными таблицами — оптимизатор СУБД с ними работает предсказуемее.
- Не выбирайте лишние поля, особенно через точку.
- Ограничивайте выборку
ПЕРВЫЕ N, когда нужен факт наличия:ВЫБРАТЬ ПЕРВЫЕ 1 …. - Используйте индексированные поля в условиях:
Ссылка, измерения регистров, реквизиты с признаком индексации.
Частые ошибки при написании запросов
- ❌ Запрос в цикле. Перебор документов, и для каждого — отдельный запрос за ценой или остатком. На 1000 строк это 1000 обращений к СУБД. Соберите ключи в массив, передайте параметром и получите всё одним запросом с
В (&Массив)или через временную таблицу. - ❌ Сравнение с NULL через знак равенства.
Поле = NULLникогда не даёт ИСТИНА. ПишитеПоле ЕСТЬ NULL. - ❌ Условие на правую таблицу в ГДЕ после левого соединения.
ГДЕ Долги.Сумма > 0отбросит строки с NULL, и левое соединение фактически станет внутренним. Если нужны все строки слева, переносите условие вПОили используйтеЕСТЬNULL. - ❌ Группировка по строке неограниченной длины. Поля вроде «Комментарий» нельзя группировать и сравнивать. Приведите их:
ВЫРАЗИТЬ(Документ.Комментарий КАК СТРОКА(300)). - ❌ Кавычки внутри текста запроса. Строковый литерал в запросе внутри строки модуля пишут удвоенными кавычками:
ПОДОБНО ""%болт%"". Проще передать шаблон параметром. - ❌ Склейка значений в текст.
"ГДЕ Дата > '" + Дата + "'"ломается на формате даты и мешает кешированию плана. Только параметры. - ❌ Отсутствие РАЗРЕШЕННЫЕ при ограничениях доступа. Если на таблицы настроены ограничения на уровне записей (RLS), запрос без
РАЗРЕШЕННЫЕу пользователя с урезанными правами упадёт с ошибкой нехватки прав, а не вернёт доступные строки.
Можно ли писать запросы в 1С на английском?
Чем вложенный запрос отличается от временной таблицы?
Как выгрузить результат запроса в таблицу значений?
Запрос.Выполнить().Выгрузить(). Чтобы сначала проверить, есть ли строки, используйте РезультатЗапроса.Пустой().