Язык запросов 1С 8.3: от первого ВЫБРАТЬ до пакетов и временных таблиц

· 9 мин

Содержание

Запрос в 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,
	Продажи.Количество
ИЗ
	ВТПродажи КАК Продажи

Что соблюдать:

  1. Число полей во всех частях одинаковое, порядок совпадает.
  2. Имена полей и псевдонимы берутся из первого запроса.
  3. ОБЪЕДИНИТЬ без ВСЕ удаляет полностью одинаковые строки. Две одинаковые продажи на 5 штук превратятся в одну. Если дубли не нужно схлопывать, пишите ОБЪЕДИНИТЬ ВСЕ — это ещё и быстрее.
  4. УПОРЯДОЧИТЬ ПО ставят один раз, в конце, и оно сортирует весь результат.

Чтобы получить итог «приход − расход» по каждому товару, объединение помещают во вложенный запрос или временную таблицу и группируют сверху.

Виртуальные таблицы регистров

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

ВЫБРАТЬ
	Остатки.Номенклатура,
	Остатки.КоличествоОстаток
ИЗ
	РегистрНакопления.ТоварыНаСкладах.Остатки(&Дата, Склад = &Склад) КАК Остатки
  • 📅 Первый параметр Остатки — момент времени. Если не указать, получите текущие остатки.
  • 🎯 Второй — условие. Отбор по измерениям ставьте сюда, а не в ГДЕ: тогда платформа отфильтрует данные до расчёта остатков, а не после.
  • 📈 У Обороты и ОстаткиИОбороты есть начало, конец периода и периодичность (День, Месяц и т. д.).

У СрезПоследних место условия меняет смысл. СрезПоследних(&Дата, Номенклатура = &Товар) вернёт последнюю цену этого товара. А тот же отбор в ГДЕ по полю ресурса, например ГДЕ Цена > 0, сначала возьмёт последние записи и только потом выкинет нулевые — товар, у которого последняя цена нулевая, пропадёт, хотя раньше у него была ненулевая.

Пакетные запросы и временные таблицы

Сложную выборку разбивают на шаги. Каждый шаг кладут во временную таблицу через ПОМЕСТИТЬ, запросы пакета разделяют точкой с запятой:

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

Запрос.УстановитьПараметр("Группа", Группа);
Запрос.УстановитьПараметр("Дата", ТекущаяДатаСеанса());

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

Как это работает:

  • 📦 Выполнить() возвращает результат последнего запроса пакета, ВыполнитьПакет() — массив результатов всех запросов, нумерация с нуля.
  • 🔗 МенеджерВременныхТаблиц держит таблицы живыми между вызовами. Передайте тот же менеджер другому запросу — и он увидит ВТТовары.
  • 🗑️ УНИЧТОЖИТЬ ВТТовары освобождает таблицу, когда она больше не нужна. Это полезно в длинных пакетах с крупными таблицами.
  • 📇 ИНДЕКСИРОВАТЬ ПО имеет смысл для полей, по которым временную таблицу потом соединяют или фильтруют, и когда строк много. На сотне строк индекс ничего не даст.
  • 📥 Временную таблицу можно заполнить и из таблицы значений: ВЫБРАТЬ … ПОМЕСТИТЬ ВТ ИЗ &ТаблицаЗначений КАК Т, передав таблицу параметром. Колонкам таблицы значений тогда нужно задать типы.

Как ускорить запрос

  1. Отбирайте как можно раньше: условия в параметрах виртуальных таблиц и в первом шаге пакета.
  2. Заменяйте подзапросы в соединениях временными таблицами — оптимизатор СУБД с ними работает предсказуемее.
  3. Не выбирайте лишние поля, особенно через точку.
  4. Ограничивайте выборку ПЕРВЫЕ N, когда нужен факт наличия: ВЫБРАТЬ ПЕРВЫЕ 1 ….
  5. Используйте индексированные поля в условиях: Ссылка, измерения регистров, реквизиты с признаком индексации.
Проверьте себя
Где поставить отбор по складу, чтобы платформа отфильтровала записи до расчёта остатков?
Что сделает ОБЪЕДИНИТЬ без слова ВСЕ с двумя одинаковыми строками?
Какое значение будет в поле правой таблицы при ЛЕВОМ СОЕДИНЕНИИ, если пары не нашлось?

Частые ошибки при написании запросов

  • ❌ Запрос в цикле. Перебор документов, и для каждого — отдельный запрос за ценой или остатком. На 1000 строк это 1000 обращений к СУБД. Соберите ключи в массив, передайте параметром и получите всё одним запросом с В (&Массив) или через временную таблицу.
  • ❌ Сравнение с NULL через знак равенства. Поле = NULL никогда не даёт ИСТИНА. Пишите Поле ЕСТЬ NULL.
  • ❌ Условие на правую таблицу в ГДЕ после левого соединения. ГДЕ Долги.Сумма > 0 отбросит строки с NULL, и левое соединение фактически станет внутренним. Если нужны все строки слева, переносите условие в ПО или используйте ЕСТЬNULL.
  • ❌ Группировка по строке неограниченной длины. Поля вроде «Комментарий» нельзя группировать и сравнивать. Приведите их: ВЫРАЗИТЬ(Документ.Комментарий КАК СТРОКА(300)).
  • ❌ Кавычки внутри текста запроса. Строковый литерал в запросе внутри строки модуля пишут удвоенными кавычками: ПОДОБНО ""%болт%"". Проще передать шаблон параметром.
  • ❌ Склейка значений в текст. "ГДЕ Дата > '" + Дата + "'" ломается на формате даты и мешает кешированию плана. Только параметры.
  • ❌ Отсутствие РАЗРЕШЕННЫЕ при ограничениях доступа. Если на таблицы настроены ограничения на уровне записей (RLS), запрос без РАЗРЕШЕННЫЕ у пользователя с урезанными правами упадёт с ошибкой нехватки прав, а не вернёт доступные строки.
Можно ли писать запросы в 1С на английском?
Да. Платформа понимает английский синтаксис: SELECT, FROM, WHERE, LEFT JOIN, GROUP BY. Имена объектов при этом остаются такими, как в конфигурации.
Чем вложенный запрос отличается от временной таблицы?
Вложенный запрос живёт внутри одного запроса и не индексируется. Временную таблицу можно проиндексировать, переиспользовать в нескольких запросах пакета и передать через менеджер временных таблиц.
Как выгрузить результат запроса в таблицу значений?
Вызовите Запрос.Выполнить().Выгрузить(). Чтобы сначала проверить, есть ли строки, используйте РезультатЗапроса.Пустой().
Почему запрос в консоли работает быстро, а в коде медленно?
Чаще всего в коде он вызывается в цикле или с другими параметрами, например без отбора по периоду. Вторая причина — права: под пользователем с ограничениями доступа к тексту добавляются условия RLS.
Нужно ли знать SQL, чтобы писать запросы в 1С?
Нет, но с ним проще. Логика соединений и группировок та же, отличаются объектная модель, виртуальные таблицы и разыменование ссылок через точку.