Язык запросов 1С 8.3: разбираемся на реальных примерах
Содержание
Запрос в 1С 8.3 начинается всегда одинаково: создаёте объект Новый Запрос, кладёте в его свойство Текст строку на языке запросов, вызываете Выполнить() и обходите результат. Сам текст строится из блоков ВЫБРАТЬ … ИЗ … ГДЕ … — это и есть минимальный рабочий запрос.
Язык похож на SQL, но с русскими ключевыми словами и своими объектами — виртуальными таблицами, срезами регистров, параметрами через &. Работает одинаково в Бухгалтерии 3.0, ЗУП 3.1, УТ 11 и любой другой конфигурации на платформе 8.3 — язык принадлежит платформе, а не прикладному решению.
Писать текст вручную с нуля не обязательно: в модуле щёлкните правой кнопкой и выберите Конструктор запроса, он соберёт синтаксис за вас. Но читать и править результат всё равно придётся руками, поэтому разберём по порядку.
Базовый синтаксис: из чего состоит текст запроса
Любой запрос — это последовательность предложений в строго заданном порядке. Пропускать можно почти все, кроме ВЫБРАТЬ и ИЗ.
ВЫБРАТЬ
Номенклатура.Код КАК Код,
Номенклатура.Наименование КАК Наименование
ИЗ
Справочник.Номенклатура КАК Номенклатура
ГДЕ
НЕ Номенклатура.ПометкаУдаления
УПОРЯДОЧИТЬ ПО
Наименование
Разберём элементы:
- 🔹
ВЫБРАТЬ— перечень полей результата. Поле пишется какТаблица.Поле, аКАК Псевдонимзадаёт имя колонки. - 🔹
ИЗ— источник данных: таблица справочника, документа или регистра. Псевдоним черезКАКобязателен, если дальше вы к таблице обращаетесь. - 🔹
ГДЕ— отбор строк. Условия соединяются черезИ,ИЛИ,НЕ. - 🔹
УПОРЯДОЧИТЬ ПО— сортировка;УБЫВпосле поля даёт обратный порядок.
Параметры в текст подставляют через амперсанд, а значение задают в коде:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ Ссылка ИЗ Справочник.Контрагенты
|ГДЕ ИНН = &ИНН";
Запрос.УстановитьПараметр("ИНН", "7701234567");
Результат = Запрос.Выполнить();
Выборка = Результат.Выбрать();
Пока Выборка.Следующий() Цикл
Сообщить(Выборка.Ссылка);
КонецЦикла;
Вертикальная черта | в начале строк — это перенос внутри строкового литерала 1С, а не часть запроса. ВЫБРАТЬ РАЗЛИЧНЫЕ убирает дубли строк, ВЫБРАТЬ ПЕРВЫЕ 10 ограничивает выборку первыми десятью строками.
Результат читают двумя способами. Выборка = Результат.Выбрать() даёт построчный обход в цикле Пока … Цикл, как в примере выше. Если строки нужны сразу все в таблице значений, удобнее Результат.Выгрузить() — вернёт готовую таблицу, по которой можно пройти как по обычной коллекции или отдать в табличный документ.
К реквизитам ссылочных полей можно обращаться через точку: Номенклатура.ЕдиницаИзмерения.Наименование. Платформа сама подставит нужное соединение, но каждая такая точка — это скрытое левое соединение, и злоупотреблять ими не стоит (об этом ниже).
Виртуальные таблицы и срезы регистров
Главное отличие языка запросов 1С от обычного SQL — виртуальные таблицы. Их нет в базе физически: платформа рассчитывает их на лету из таблиц движений регистра. Именно через них получают остатки и обороты, а не складывают движения вручную.
У регистра накопления есть такие виртуальные таблицы:
| Таблица | Что возвращает |
|---|---|
.Остатки(Период, Условие) | остатки на момент времени |
.Обороты(Начало, Конец, Период, Условие) | приход и расход за интервал |
.ОстаткиИОбороты(…) | начальный остаток, обороты и конечный остаток |
Пример — остатки товаров на складе на дату:
ВЫБРАТЬ
ТоварыОстатки.Номенклатура КАК Номенклатура,
ТоварыОстатки.Склад КАК Склад,
ТоварыОстатки.КоличествоОстаток КАК Количество
ИЗ
РегистрНакопления.ТоварыНаСкладах.Остатки(&НаДату, Склад = &Склад) КАК ТоварыОстатки
Для регистров сведений используют срезы — СрезПоследних и СрезПервых. Классика — курс валюты на дату:
ВЫБРАТЬ
КурсыСрез.Валюта КАК Валюта,
КурсыСрез.Курс КАК Курс
ИЗ
РегистрСведений.КурсыВалют.СрезПоследних(&НаДату, ) КАК КурсыСрез
Условие отбора ставьте внутрь скобок виртуальной таблицы, а не в предложение ГДЕ. В первом случае платформа отфильтрует данные ещё при расчёте таблицы и обработает меньше строк. Если вынести то же условие в ГДЕ, система сначала рассчитает остатки по всем складам, а потом отбросит лишнее — медленнее в разы на большой базе.
У регистра бухгалтерии свой набор: .Остатки, .Обороты, .ОстаткиИОбороты, .ДвиженияССубконто. У них есть параметры по счетам и субконто — через них удобно собирать обороты по конкретному счёту без ручного разбора проводок.
Соединения таблиц: ЛЕВОЕ, ВНУТРЕННЕЕ и остальные
Когда данные лежат в двух источниках, их соединяют по общему полю. В 1С четыре вида соединений:
ВНУТРЕННЕЕ СОЕДИНЕНИЕ— только строки, где совпадение есть в обеих таблицах.ЛЕВОЕ СОЕДИНЕНИЕ— все строки левой таблицы, из правой подтягивается совпадение илиNULL.ПРАВОЕ СОЕДИНЕНИЕ— зеркально левому.ПОЛНОЕ СОЕДИНЕНИЕ— все строки обеих таблиц.
На практике в 95% случаев используется левое. Типовая задача — вывести весь список номенклатуры и подставить к нему остаток, даже если остатка нет:
ВЫБРАТЬ
Товары.Ссылка КАК Номенклатура,
ЕСТЬNULL(Остатки.КоличествоОстаток, 0) КАК Остаток
ИЗ
Справочник.Номенклатура КАК Товары
ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ТоварыНаСкладах.Остатки(&НаДату, ) КАК Остатки
ПО Остатки.Номенклатура = Товары.Ссылка
ГДЕ
НЕ Товары.ЭтоГруппа
Условие соединения задаётся после ПО. Обратите внимание на ЕСТЬNULL(Остатки.КоличествоОстаток, 0): по товарам без движений остаток придёт как NULL, и без этой обёртки в арифметике получится NULL, а не ноль.
Нельзя соединять таблицу с подзапросом или виртуальной таблицей «в лоб» на больших данных — платформа не может эффективно построить план. Сложные источники сначала выгружают во временную таблицу, индексируют и только потом соединяют.
Группировка и агрегатные функции
Чтобы свернуть строки и посчитать итоги
, применяют агрегатные функции и СГРУППИРОВАТЬ ПО. Доступны СУММА, КОЛИЧЕСТВО, МАКСИМУМ, МИНИМУМ, СРЕДНЕЕ.
Правило простое: каждое поле в ВЫБРАТЬ либо завёрнуто в агрегатную функцию, либо перечислено в СГРУППИРОВАТЬ ПО. Иначе запрос не выполнится.
ВЫБРАТЬ
Продажи.Номенклатура КАК Номенклатура,
СУММА(Продажи.Количество) КАК Количество,
СУММА(Продажи.ВыручкаОборот) КАК Выручка
ИЗ
РегистрНакопления.Продажи.Обороты(&Начало, &Конец, , ) КАК Продажи
СГРУППИРОВАТЬ ПО
Продажи.Номенклатура
ИМЕЮЩИЕ
СУММА(Продажи.ВыручкаОборот) > 10000
УПОРЯДОЧИТЬ ПО
Выручка УБЫВ
ИМЕЮЩИЕ — это отбор по уже посчитанному итогу (аналог HAVING). Условие на исходные строки ставят в ГДЕ, условие на сумму или количество — в ИМЕЮЩИЕ.
Отдельно стоит КОЛИЧЕСТВО(РАЗЛИЧНЫЕ Поле) — считает число уникальных значений, например сколько разных контрагентов было в продажах. А КОЛИЧЕСТВО(*) просто считает строки, не глядя на их содержимое.
Когда суммируемое поле имеет составной тип (например, «Регистратор»), перед агрегатом его приводят к нужному типу через ВЫРАЗИТЬ(Поле КАК Число(15,2)). Без приведения платформа не поймёт, как складывать значения, и выдаст ошибку типа.
Если нужны не плоские итоги, а иерархия с промежуточными суммами, используют предложение ИТОГИ … ПО:
ИТОГИ
СУММА(Выручка)
ПО
Номенклатура
Тогда результат обходят по группировкам: Результат.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам).
Оптимизация: как не посадить базу тяжёлым запросом
Медленный запрос — главная причина тормозов в 1С. Несколько приёмов, которые реально ускоряют выборки:
- ✅ Отбор — в параметры виртуальной таблицы, а не в
ГДЕ. Это самое частое и самое дорогое упущение. - ✅ Временные таблицы с индексом. Сложную выборку разбивают на пакет: промежуточный результат кладут в
ПОМЕСТИТЬ ВТ_Имяи добавляютИНДЕКСИРОВАТЬ ПОполям, по которым дальше будет соединение. - ✅ Перечисляйте поля явно.
ВЫБРАТЬ *тянет лишние колонки и мешает оптимизатору. - ✅ Не обращайтесь через точку на несколько уровней (
Документ.Контрагент.ЮрЛицо.Наименование) — каждая точка добавляет левое соединение. Лучше явное соединение с нужным справочником. - ✅ Никогда не выполняйте запрос в цикле. Если данные нужны по списку объектов, передайте список целиком параметром и получите всё одним запросом.
Пример пакетного запроса с временной таблицей:
ВЫБРАТЬ
Заказы.Контрагент КАК Контрагент,
СУММА(Заказы.Сумма) КАК Сумма
ПОМЕСТИТЬ ВТ_Заказы
ИЗ
Документ.ЗаказКлиента КАК Заказы
ГДЕ
Заказы.Дата МЕЖДУ &Начало И &Конец
СГРУППИРОВАТЬ ПО
Заказы.Контрагент
ИНДЕКСИРОВАТЬ ПО
Контрагент
;
ВЫБРАТЬ
ВТ_Заказы.Контрагент,
ВТ_Заказы.Сумма
ИЗ
ВТ_Заказы КАК ВТ_Заказы
Точка с запятой ; разделяет пакет. Для работы временных таблиц между частями запросу назначают менеджер: Запрос.МенеджерВременныхТаблиц = Новый МенеджерВременныхТаблиц.
Чтобы понять, где именно тормозит, есть штатные инструменты. В конфигураторе — Отладка → Замер производительности: покажет, сколько времени съел каждый вызов Выполнить(). Для серверной базы смотрят технологический журнал и план запроса на стороне СУБД. Оптимизируют по фактам из замера, а не на глаз — интуиция здесь ошибается чаще, чем кажется.
Частые ошибки и как их распознать
Большинство проблем с запросами укладываются в несколько повторяющихся сценариев. Вот как они выглядят и что с ними делать:
NULL = NULL даёт не истину, а неопределённость. Проверяйте через ЕСТЬ NULL и ЕСТЬ НЕ NULL, а в выборках заворачивайте поля из левых соединений в ЕСТЬNULL(Поле, 0)..ОстаткиИОбороты.ГДЕ вместо параметров виртуальной таблицы, либо запрос вызывается внутри цикла. Перенесите отбор в скобки виртуальной таблицы, а список объектов передайте одним параметром.ВЫБРАТЬ не обёрнуто агрегатной функцией и не указано в СГРУППИРОВАТЬ ПО. Добавьте его в группировку либо примените СУММА/МАКСИМУМ.| при переносе строкового литерала или перепутаны кавычки. Соберите текст через Конструктор запроса и сверьте.Ещё одна тонкость: функция ЗНАЧЕНИЕ() нужна, когда в условии участвует предопределённый элемент, перечисление или пустая ссылка — например ГДЕ Статус = ЗНАЧЕНИЕ(Перечисление.СтатусыЗаказов.Выполнен). Подставлять такие значения строкой нельзя.
Практические примеры сложных выборок
Соберём пару запросов, которые встречаются в реальных отчётах.
Условная логика через ВЫБОР. Нужно пометить товары словами «в наличии» / «нет на складе»:
ВЫБРАТЬ
Товары.Ссылка КАК Номенклатура,
ВЫБОР
КОГДА ЕСТЬNULL(Остатки.КоличествоОстаток, 0) > 0
ТОГДА "В наличии"
ИНАЧЕ "Нет на складе"
КОНЕЦ КАК Статус
ИЗ
Справочник.Номенклатура КАК Товары
ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ТоварыНаСкладах.Остатки(&НаДату, ) КАК Остатки
ПО Остатки.Номенклатура = Товары.Ссылка
Объединение разнородных данных через ОБЪЕДИНИТЬ ВСЕ. Собрать в одну таблицу приходы и расходы денег:
ВЫБРАТЬ "Поступление" КАК Операция, СУММА(Т.Сумма) КАК Сумма
ИЗ Документ.ПоступлениеНаРасчетныйСчет КАК Т
ГДЕ Т.Дата МЕЖДУ &Начало И &Конец
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ "Списание", СУММА(Т.Сумма)
ИЗ Документ.СписаниеСРасчетногоСчета КАК Т
ГДЕ Т.Дата МЕЖДУ &Начало И &Конец
ОБЪЕДИНИТЬ убирает дубли строк, ОБЪЕДИНИТЬ ВСЕ — оставляет всё как есть и работает быстрее. Число и типы полей в объединяемых запросах должны совпадать.
Проверьте, насколько уверенно вы читаете синтаксис:
Отлаживать сложные запросы удобно в консоли запросов (обработка из стандартных инструментов разработчика) — там видно результат без запуска кода, и текст можно править на лету.
Частые вопросы
Чем язык запросов 1С отличается от SQL?
Зачем нужны временные таблицы, если есть вложенные запросы?
ПОМЕСТИТЬ.Почему в результате появляются NULL и как от них избавиться?
NULL приходит из левых и полных соединений, когда в правой таблице нет совпадения. Заворачивайте такие поля в ЕСТЬNULL(Поле, ЗначениеПоУмолчанию), а в условиях сравнивайте через ЕСТЬ NULL.Можно ли одним запросом изменить данные в базе?
Как быстро собрать текст запроса, не помня синтаксис?
Конструктор запроса. Он соберёт ВЫБРАТЬ, соединения и группировки по выбранным полям. Результат всё равно стоит перечитать и при необходимости оптимизировать вручную.