Язык запросов 1С: от первого ВЫБРАТЬ до оптимизации
Содержание
Запрос в 1С начинается с ключевого слова ВЫБРАТЬ, перечисления полей, источника после ИЗ и отбора после ГДЕ. Это минимальный рабочий скелет: выбрать такие-то колонки из такой-то таблицы по такому-то условию. Всё остальное — соединения, группировки, временные таблицы — наращивается поверх этих трёх блоков.
Писать запросы правильно — значит получать нужные данные одним обращением к базе, без лишних циклов в коде и без тормозов на больших объёмах. Разберём язык запросов 1С по частям: синтаксис выборки, условия с параметрами, соединения таблиц, агрегаты, пакеты с временными таблицами и приёмы, которые отличают быстрый запрос от медленного.
Все примеры — на языке запросов платформы 8.3 (интерфейс «Такси»). Запросы в 1С нужны почти везде: отчёты на системе компоновки данных, обработки, автозаполнение документов, проверки при проведении, регламентные задания. Принципы одни и те же, меняется только то, куда уходит результат — в таблицу значений, в макет отчёта или в код.
Писать запрос руками с нуля необязательно: в конфигураторе и в расширении есть конструктор запросов (правый клик в модуле → Конструктор запроса), который собирает текст по визуальной схеме. Но понимать синтаксис всё равно нужно — конструктор не подскажет, где вы теряете производительность.
Из чего состоит запрос: ВЫБРАТЬ, ИЗ, ГДЕ
Базовая форма читается почти как фраза на русском. Берём справочник номенклатуры, два поля, отсекаем помеченные на удаление и сортируем по алфавиту:
ВЫБРАТЬ
Номенклатура.Наименование КАК Наименование,
Номенклатура.Артикул КАК Артикул
ИЗ
Справочник.Номенклатура КАК Номенклатура
ГДЕ
НЕ Номенклатура.ПометкаУдаления
УПОРЯДОЧИТЬ ПО
Наименование
Разберём по строкам:
- 📋
ВЫБРАТЬ— список полей результата. Каждому полю черезКАКдаём псевдоним — по нему потом обращаются и в коде, и в других секциях запроса. - 🗂
ИЗ ... КАК— источник данных и его короткое имя. Справочник, документ, регистр, табличная часть — всё адресуется через точку:Документ.РеализацияТоваровУслуг,Справочник.Контрагенты. - 🔎
ГДЕ— условие отбора строк. Условия соединяются черезИ,ИЛИ,НЕ. - ↕️
УПОРЯДОЧИТЬ ПО— сортировка. Указывают псевдоним поля и при желанииУБЫВилиВОЗР.
Полезные варианты самого ВЫБРАТЬ:
ВЫБРАТЬ РАЗЛИЧНЫЕ— убирает полные дубли строк. Удобнее группировки, когда агрегаты не нужны, а дубли мешают.ВЫБРАТЬ ПЕРВЫЕ 10— ограничивает выборку первыми N строками. Вместе с сортировкой даёт «топ-10».- Обращение через точку —
Товары.Номенклатура.ЕдиницаИзмерения— разыменовывает ссылку и достаёт реквизит связанного объекта. Удобно, но на условиях отбора это неявное соединение, о чём ниже.
Псевдоним через КАК задавайте всегда и осмысленно. Это не формальность: по псевдониму вы ссылаетесь на поле в ГДЕ, СГРУППИРОВАТЬ ПО, УПОРЯДОЧИТЬ ПО и при чтении выборки в коде.
Условия отбора и параметры запроса
Жёстко вписывать значения в текст запроса нельзя — ни конкретную дату, ни ссылку, ни строку. Для этого есть параметры: имя с амперсандом в тексте, значение — в коде.
ВЫБРАТЬ
Заказы.Ссылка КАК Заказ,
Заказы.Контрагент КАК Контрагент,
Заказы.СуммаДокумента КАК Сумма
ИЗ
Документ.ЗаказПокупателя КАК Заказы
ГДЕ
Заказы.Дата МЕЖДУ &НачалоПериода И &КонецПериода
И Заказы.Контрагент = &Контрагент
И Заказы.Проведен
Значения передаём перед выполнением:
Запрос = Новый Запрос(ТекстЗапроса);
Запрос.УстановитьПараметр("НачалоПериода", НачалоДня(ДатаНачала));
Запрос.УстановитьПараметр("КонецПериода", КонецДня(ДатаКонца));
Запрос.УстановитьПараметр("Контрагент", ВыбранныйКонтрагент);
Результат = Запрос.Выполнить();
Выборка = Результат.Выбрать();
Пока Выборка.Следующий() Цикл
Сообщить(Выборка.Контрагент);
КонецЦикла;
Операторы, которые чаще всего нужны в ГДЕ:
| Оператор | Для чего | Пример |
|---|---|---|
=, <>, >, < | сравнение | Сумма > 0 |
МЕЖДУ ... И ... | диапазон (включая границы) | Дата МЕЖДУ &Н И &К |
В (...) | вхождение в список значений | Статус В (&Список) |
В ИЕРАРХИИ (...) | элемент и вся его ветка группы | Группа В ИЕРАРХИИ (&Папка) |
ПОДОБНО | поиск по шаблону, % — любой текст | Наименование ПОДОБНО "%болт%" |
ЕСТЬ NULL | проверка на отсутствие значения | Цена ЕСТЬ NULL |
Границы дат в документах — частая ловушка. Если передать &КонецПериода как просто дату без времени, записи за последний день с ненулевым временем не попадут в выборку. Оборачивайте границу в КонецДня() в коде.
Для даты прямо в тексте запроса (например, точка отсчёта «с начала года») есть функция ДАТАВРЕМЯ(2026, 1, 1) — год, месяц, день, при необходимости часы-минуты-секунды.
Соединение таблиц: ЛЕВОЕ, ВНУТРЕННЕЕ, ПОЛНОЕ
Когда данные лежат в разных таблицах, их соединяют по совпадению полей. Тип соединения определяет, что делать со строками без пары.
- ВНУТРЕННЕЕ СОЕДИНЕНИЕ — в результат попадут только строки, у которых есть совпадение в обеих таблицах. Нет пары — строка выпадает.
- ЛЕВОЕ СОЕДИНЕНИЕ — берём все строки левой таблицы, к ним подтягиваем совпадения из правой. Нет пары — поля правой таблицы будут
NULL. - ПРАВОЕ СОЕДИНЕНИЕ — зеркально левому. На практике почти не пишут: достаточно поменять таблицы местами.
- ПОЛНОЕ СОЕДИНЕНИЕ — все строки обеих таблиц, непарные с обеих сторон дают
NULL.
Типичный случай — строки документа и регистр цен к ним:
ВЫБРАТЬ
Товары.Номенклатура КАК Номенклатура,
Товары.Количество КАК Количество,
ЕСТЬNULL(Цены.Цена, 0) КАК Цена
ИЗ
Документ.РеализацияТоваровУслуг.Товары КАК Товары
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныНоменклатуры КАК Цены
ПО Товары.Номенклатура = Цены.Номенклатура
ГДЕ
Товары.Ссылка = &Документ
Здесь важен приём, без которого левое соединение подводит: после левого соединения непарные поля равны NULL, а не нулю. Если такое поле попадёт в арифметику (Цена * Количество), результат тоже станет NULL. Поэтому оборачиваем в ЕСТЬNULL(Поле, 0) — функция подставляет второй аргумент вместо NULL.
Условие соединения пишется после ПО. Не путайте его с ГДЕ: условие в ПО отбирает пары при соединении, а ГДЕ фильтрует уже готовый результат. Для левого соединения это разные вещи — отбор правой таблицы в ГДЕ может «схлопнуть» левое соединение до внутреннего.
Группировка и агрегатные функции
Чтобы свернуть строки в итоги — суммы по контрагентам, количество документов, максимумы — нужны агрегатные функции и секция СГРУППИРОВАТЬ ПО.
ВЫБРАТЬ
Продажи.Контрагент КАК Контрагент,
СУММА(Продажи.СуммаДокумента) КАК СуммаПродаж,
КОЛИЧЕСТВО(РАЗЛИЧНЫЕ Продажи.Ссылка) КАК ЧислоДокументов,
МАКСИМУМ(Продажи.Дата) КАК ПоследняяПродажа
ИЗ
Документ.РеализацияТоваровУслуг КАК Продажи
ГДЕ
Продажи.Проведен
СГРУППИРОВАТЬ ПО
Продажи.Контрагент
ИМЕЮЩИЕ
СУММА(Продажи.СуммаДокумента) > 100000
УПОРЯДОЧИТЬ ПО
СуммаПродаж УБЫВ
Основные агрегаты: СУММА, КОЛИЧЕСТВО, МАКСИМУМ, МИНИМУМ, СРЕДНЕЕ. Правила, которые легко нарушить:
- Все поля, которые не завёрнуты в агрегатную функцию, обязаны попасть в
СГРУППИРОВАТЬ ПО. Иначе платформа вернёт ошибку. КОЛИЧЕСТВО(РАЗЛИЧНЫЕ Поле)считает число уникальных значений, аКОЛИЧЕСТВО(*)— число строк.- Фильтр по результату агрегата — это
ИМЕЮЩИЕ, а неГДЕ.ГДЕотбирает строки до свёртки,ИМЕЮЩИЕ— группы после.
Отдельно стоит ИТОГИ ... ПО — многоуровневые итоги прямо в выборке (аналог «подвала» в отчётах). Их обходят методом ВыбратьПоГруппировкам(). Для табличного результата в коде это удобно, но учтите: такой результат уже не плоская таблица, а дерево.
Объединение запросов и временные таблицы
Два разных набора строк с одинаковой структурой колонок склеиваются через ОБЪЕДИНИТЬ:
ОБЪЕДИНИТЬ— склеивает и попутно убирает полные дубли.ОБЪЕДИНИТЬ ВСЕ— склеивает как есть, дубли остаются. Работает быстрее, потому что не чистит дубли; если их заведомо нет — берите этот вариант.
ВЫБРАТЬ
"Приход" КАК Операция,
Поступление.Номенклатура КАК Номенклатура,
Поступление.Количество КАК Количество
ИЗ
Документ.ПоступлениеТоваровУслуг.Товары КАК Поступление
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ
"Расход",
Реализация.Номенклатура,
Реализация.Количество
ИЗ
Документ.РеализацияТоваровУслуг.Товары КАК Реализация
Количество и порядок полей в объединяемых запросах должны совпадать. Псевдонимы берутся из первого запроса.
Временные таблицы — ключевой инструмент для сложных выборок. Результат одного запроса кладём во временную таблицу через ПОМЕСТИТЬ, а в следующем запросе пакета используем её как обычный источник. Запросы в пакете разделяются точкой с запятой:
ВЫБРАТЬ
Ном.Ссылка КАК Номенклатура
ПОМЕСТИТЬ Вт_Товары
ИЗ
Справочник.Номенклатура КАК Ном
ГДЕ
Ном.Родитель = &Группа
ИНДЕКСИРОВАТЬ ПО
Номенклатура
;
ВЫБРАТЬ
Вт_Товары.Номенклатура КАК Номенклатура,
ЕСТЬNULL(Остатки.КоличествоОстаток, 0) КАК Остаток
ИЗ
Вт_Товары КАК Вт_Товары
ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ТоварыНаСкладах.Остатки КАК Остатки
ПО Вт_Товары.Номенклатура = Остатки.Номенклатура
Чтобы пакет с временными таблицами отработал, запросу нужно отдать менеджер временных таблиц:
Запрос = Новый Запрос(ТекстПакета);
Запрос.МенеджерВременныхТаблиц = Новый МенеджерВременныхТаблиц;
Запрос.УстановитьПараметр("Группа", ГруппаТоваров);
Результат = Запрос.ВыполнитьПакет()[1];
Зачем это нужно: временная таблица разбивает тяжёлый запрос на понятные шаги и, что важнее, позволяет проиндексировать промежуточные данные перед соединением. Соединять большую таблицу регистра с проиндексированной временной таблицей из полусотни строк СУБД умеет гораздо быстрее, чем с подзапросом.
Как ускорить запрос: приёмы и типичные ошибки
Медленный запрос почти всегда медленный по одной из нескольких предсказуемых причин. Пройдитесь по чек-листу — он закрывает большинство случаев.
Разберём главные приёмы.
Виртуальные таблицы вместо физических. У регистров есть виртуальные таблицы .Остатки(...), .Обороты(...), .ОстаткиИОбороты(...). Они считают итоги по специальным таблицам итогов и работают в разы быстрее, чем ручной подсчёт по таблице движений. Условия отбора задавайте в параметрах виртуальной таблицы, а не в ГДЕ:
ВЫБРАТЬ
Остатки.Номенклатура КАК Номенклатура,
Остатки.КоличествоОстаток КАК Остаток
ИЗ
РегистрНакопления.ТоварыНаСкладах.Остатки(
&НаДату,
Склад = &Склад) КАК Остатки
Отбор по складу внутри скобок виртуальной таблицы заставит платформу отфильтровать данные до расчёта остатков. Тот же отбор в ГДЕ сработает уже после — медленнее и на большем объёме.
Индексация временных таблиц. Если временную таблицу вы потом соединяете по какому-то полю — добавьте ИНДЕКСИРОВАТЬ ПО этому полю при её создании (как в примере выше). Без индекса соединение пойдёт перебором.
Никаких подзапросов в соединениях. Конструкция ЛЕВОЕ СОЕДИНЕНИЕ (ВЫБРАТЬ ... ИЗ ...) КАК Т ПО ... — классический тормоз: по подзапросу нельзя построить индекс. Вынесите подзапрос в отдельную временную таблицу пакета и проиндексируйте её.
Осторожно с точкой у составных типов. Обращение ДокументСсылка.Реквизит, где поле имеет составной тип (может ссылаться на разные документы), разворачивается в соединение со всеми возможными таблицами типа. В условии отбора это особенно дорого. Лучше заранее привести тип через ВЫРАЗИТЬ(Поле КАК Документ.РеализацияТоваровУслуг).Реквизит.
Главная ошибка — запрос внутри цикла. Если по коллекции из тысячи элементов вы на каждом шаге выполняете новый запрос, базе уходит тысяча обращений вместо одного. Разворачивайте логику наоборот: загрузите всю нужную номенклатуру во временную таблицу, выполните один запрос с соединением и обойдите готовую выборку циклом. Правило простое — данные получаем запросом, цикл только читает результат. Это самый частый источник тормозов в типовых доработках.
Не тащите лишние поля. Каждое поле в ВЫБРАТЬ — это чтение колонки, а реквизит через точку — ещё и соединение. Если поле в отчёте не показывается и в коде не используется, уберите его. Особенно это касается реквизитов справочников, которые разыменовывают «на всякий случай».
Условную логику в полях удобно описывать через ВЫБОР:
ВЫБОР
КОГДА Остатки.КоличествоОстаток > 0 ТОГДА "В наличии"
КОГДА Остатки.КоличествоОстаток = 0 ТОГДА "Закончился"
ИНАЧЕ "Нет данных"
КОНЕЦ КАК СтатусНаличия
Для отладки держите под рукой консоль запросов — внешнюю обработку , которая позволяет выполнить текст запроса без встраивания в код, посмотреть результат таблицей и замерить время. На ней же проверяют, что отбор и соединения дают ровно те строки, что ожидались.
Частые вопросы
Чем язык запросов 1С отличается от SQL?
В ИЕРАРХИИ, обращение к реквизитам через точку. Ключевые слова — русские.Можно ли менять данные запросом, как в SQL?
UPDATE, DELETE, INSERT в нём нет. Данные меняют через объектную модель: читают объект, правят реквизиты, записывают. Это сделано намеренно, чтобы не обходить бизнес-логику проведения и прав.Почему после левого соединения в поле NULL, а не ноль?
NULL — «данных нет». Чтобы в арифметике и выводе вместо NULL был ноль или пустая строка, оборачивайте поле в ЕСТЬNULL(Поле, 0).Когда брать временную таблицу, а когда подзапрос?
ИЗ допустим для разовой несложной выборки, но соединять с подзапросом не стоит: по нему нет индекса, и запрос замедлится.Как отобрать элемент группы вместе со всей вложенной веткой?
В ИЕРАРХИИ: условие Номенклатура В ИЕРАРХИИ (&Группа) вернёт и саму группу, и все элементы её подгрупп на любую глубину. Обычное = сравнивает только с самим элементом.Собирайте запрос в конструкторе, когда структура сложная, но итог всегда перечитывайте глазами: правильный порядок соединений, отбор в параметрах виртуальных таблиц и индексы на временных таблицах — то, что конструктор за вас не оптимизирует.