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

· 10 мин

Содержание

Чтобы создать запрос в 1С, нужны три строки кода: Запрос = Новый Запрос, текст запроса в свойстве Запрос.Текст и вызов Запрос.Выполнить(). Текст можно набрать вручную или собрать в конструкторе запроса: в Конфигураторе щёлкните правой кнопкой в модуле и выберите Конструктор запроса.... Язык один для всех типовых конфигураций на платформе 8.3: БП 3.0, ЗУП 3.1, УТ 11, ERP.

Типичная ситуация: бухгалтер просит отчёт «продажи по номенклатуре за квартал, только проведённые документы, по убыванию суммы». Типовые отчёты так не группируют, а СКД пугает. Такой отчёт — это один запрос на 15 строк. Ниже разобрано, как его написать, проверить и ускорить, если база большая.

Из чего состоит запрос в 1С: синтаксис и порядок секций

Язык запросов 1С похож на SQL, но работает только на чтение. Изменить, удалить или вставить данные запросом нельзя: для этого есть объектная модель (ПолучитьОбъект(), Записать()).

Секции идут строго в таком порядке:

СекцияЧто делаетОбязательна
ВЫБРАТЬсписок полей, можно с РАЗЛИЧНЫЕ, ПЕРВЫЕ N, РАЗРЕШЕННЫЕда
ПОМЕСТИТЬсохраняет результат во временную таблицунет
ИЗисточники: справочники, документы, регистры, их табличные частида
ГДЕотбор строк до группировкинет
СГРУППИРОВАТЬ ПОсвёртка строк для агрегатных функцийнет
ИМЕЮЩИЕотбор уже сгруппированных строкнет
ОБЪЕДИНИТЬ / ОБЪЕДИНИТЬ ВСЕсклейка результатов нескольких запросовнет
УПОРЯДОЧИТЬ ПОсортировканет
ИТОГИ ПОпромежуточные и общие итогинет

Каждой таблице и вычисляемому полю давайте псевдоним через КАК. Без псевдонимов поля в результате получат имена вроде Поле1, и обращаться к ним из кода будет неудобно.

Минимальный рабочий пример, который закрывает задачу из вступления (БП 3.0):

ВЫБРАТЬ
	РеализацияТовары.Номенклатура КАК Номенклатура,
	СУММА(РеализацияТовары.Количество) КАК Количество,
	СУММА(РеализацияТовары.Сумма) КАК Сумма
ИЗ
	Документ.РеализацияТоваровУслуг.Товары КАК РеализацияТовары
ГДЕ
	РеализацияТовары.Ссылка.Проведен
	И РеализацияТовары.Ссылка.Дата МЕЖДУ &НачалоПериода И &КонецПериода
СГРУППИРОВАТЬ ПО
	РеализацияТовары.Номенклатура
УПОРЯДОЧИТЬ ПО
	Сумма УБЫВ

Полезные конструкции, которые понадобятся почти сразу:

  • 🔹 ВЫБОР КОГДА ... ТОГДА ... ИНАЧЕ ... КОНЕЦ — условное поле, аналог CASE.
  • 🔹 ЕСТЬNULL(Поле, 0) — подставляет значение вместо NULL после левого соединения.
  • 🔹 ЗНАЧЕНИЕ(Перечисление.ВидыОпераций.Продажа) или ЗНАЧЕНИЕ(Справочник.Номенклатура.ПустаяСсылка) — предопределённые значения прямо в тексте.
  • 🔹 В ИЕРАРХИИ (&Группа) — отбор по группе справочника со всеми вложенными.
  • 🔹 ПОДОБНО "%болт%" — поиск по части строки.
  • 🔹 НАЧАЛОПЕРИОДА(Дата, МЕСЯЦ) — свёртка дат до месяца, удобно для помесячных отчётов.

Где и как создать запрос: объект Запрос в коде

Запрос живёт в любом серверном коде: модуле объекта, модуле менеджера, общем модуле, модуле внешней обработки. Последний вариант удобен, когда конфигурацию трогать нельзя: создайте внешнюю обработку через Файл → Новый... → Внешняя обработка, добавьте форму и пишите код там.

Полный цикл «создать — заполнить параметры — выполнить — обойти» выглядит так:

&НаСервере
Процедура СформироватьНаСервере()

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

	Запрос.УстановитьПараметр("НачалоПериода", НачалоДня(Объект.ДатаНачала));
	Запрос.УстановитьПараметр("КонецПериода", КонецДня(Объект.ДатаОкончания));

	РезультатЗапроса = Запрос.Выполнить();
	Если РезультатЗапроса.Пустой() Тогда
		Сообщить("За период продаж нет");
		Возврат;
	КонецЕсли;

	Выборка = РезультатЗапроса.Выбрать();
	Пока Выборка.Следующий() Цикл
		Сообщить(СтрШаблон("%1: %2", Выборка.Номенклатура, Формат(Выборка.Сумма, "ЧДЦ=2")));
	КонецЦикла;

КонецПроцедуры

Многострочный текст в 1С пишется так: строка открывается кавычкой, каждая следующая строка начинается с |. Конструктор формирует этот формат сам.

Результат можно получить тремя способами:

МетодЧто возвращаетКогда брать
Выбрать()выборку, строки читаются по одной в циклеобход большого результата без лишней памяти
Выгрузить()таблицу значенийнужно сортировать, искать, передать в табличную часть или на форму
Пустой()Истина/Ложьпроверить, есть ли хоть одна строка

Если запрос нужен только чтобы узнать «есть ли хоть что-то», добавьте ВЫБРАТЬ ПЕРВЫЕ 1 и проверяйте Пустой(). Платформа не будет тянуть всю таблицу.

Для прав пользователей есть слово РАЗРЕШЕННЫЕ: ВЫБРАТЬ РАЗРЕШЕННЫЕ .... С ним записи, закрытые ограничением доступа на уровне записей, просто не попадут в результат. Без него пользователь с ограниченными правами получит ошибку чтения вместо отчёта.

Конструктор запросов: собираем текст без ручного набора

Конструктор открывается в Конфигураторе из контекстного меню модуля: Конструктор запроса... или Конструктор запроса с обработкой результата.... Второй вариант сразу допишет код с выборкой и циклом. Если курсор стоит внутри уже готового текста запроса, конструктор откроет его для правки.

Порядок работы на вкладках:

  1. Таблицы и поля. Слева — дерево метаданных. Перетащите нужную таблицу в центр, затем поля в правую колонку. Табличная часть документа лежит внутри узла документа.
  2. Связи. Появится, если таблиц две и больше. Укажите условие связи и отметьте флажки «Все» — от них зависит тип соединения.
  3. Группировка. Перенесите поля группировки налево, суммируемые поля — в «Суммируемые поля» и выберите функцию.
  4. Условия. Перетащите поле, конструктор сам подставит параметр вида &Номенклатура. Галочка «Произвольное» позволяет написать условие вручную.
  5. Дополнительно. Здесь включаются ПЕРВЫЕ, РАЗЛИЧНЫЕ, РАЗРЕШЕННЫЕ, а также переключатель «Создание временной таблицы».
  6. Объединения/Псевдонимы. Переименуйте поля и добавьте второй запрос для ОБЪЕДИНИТЬ.
  7. Порядок и Итоги — сортировка и итоги по группировкам.
  8. Пакет запросов. Если запросов несколько, они видны здесь списком.

Кнопка «Запрос» внизу показывает получившийся текст. Подглядывать туда полезно: так язык запоминается быстрее, чем по справочнику.

Конструктор переписывает текст целиком по своим правилам. Комментарии // внутри текста запроса и ручное форматирование после его открытия пропадут.

Если доступа к Конфигуратору нет, запросы пишут и проверяют во внешней обработке «Консоль запросов». Её открывают в пользовательском режиме через Файл → Открыть. Внутри консоли обычно есть тот же конструктор.

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

Значения в текст запроса не вклеивают строкой. Вместо этого пишут параметр с амперсандом &ИмяПараметра и передают значение через УстановитьПараметр(). Плюсы: ссылки, даты и списки передаются как есть, без преобразования в строку, а платформа может переиспользовать план запроса.

В параметр можно передать массив или список значений и проверять вхождение: ГДЕ Товары.Номенклатура В (&СписокТоваров).

Конец периода передавайте как КонецДня(Дата). Если передать просто дату из поля ввода, у неё время 00:00:00, и документы последнего дня в отбор МЕЖДУ не попадут.

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

Временная таблица — промежуточный результат, к которому следующие запросы обращаются как к обычной таблице. Создаётся словом ПОМЕСТИТЬ, запросы пакета разделяются точкой с запятой.

Пример для БП 3.0: сначала собираем продажи, затем подтягиваем к ним цену по типу цен из регистра.

ВЫБРАТЬ
	Товары.Номенклатура КАК Номенклатура,
	СУММА(Товары.Количество) КАК Количество
ПОМЕСТИТЬ ВТПродажи
ИЗ
	Документ.РеализацияТоваровУслуг.Товары КАК Товары
ГДЕ
	Товары.Ссылка.Проведен
	И Товары.Ссылка.Дата МЕЖДУ &НачалоПериода И &КонецПериода
СГРУППИРОВАТЬ ПО
	Товары.Номенклатура
ИНДЕКСИРОВАТЬ ПО
	Номенклатура
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
	ВТПродажи.Номенклатура КАК Номенклатура,
	ВТПродажи.Количество КАК Количество,
	ЕСТЬNULL(Цены.Цена, 0) КАК Цена
ИЗ
	ВТПродажи КАК ВТПродажи
		ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныНоменклатуры.СрезПоследних(
				&КонецПериода,
				ТипЦен = &ТипЦен
					И Номенклатура В
						(ВЫБРАТЬ ВТ.Номенклатура ИЗ ВТПродажи КАК ВТ)) КАК Цены
		ПО ВТПродажи.Номенклатура = Цены.Номенклатура

Как это выполнить в коде:

  • Запрос.Выполнить() вернёт результат последнего запроса пакета.
  • Запрос.ВыполнитьПакет() вернёт массив результатов всех запросов. Индексы начинаются с нуля.
  • Чтобы временные таблицы пережили выполнение и были доступны другим запросам, назначьте Запрос.МенеджерВременныхТаблиц = Новый МенеджерВременныхТаблиц и передавайте этот менеджер дальше.

Ненужную таблицу внутри пакета удаляют командой УНИЧТОЖИТЬ ВТПродажи. Без менеджера временные таблицы исчезают сами после выполнения пакета.

Соединения таблиц: какое выбрать

Соединение нужно, когда данные лежат в разных таблицах: продажи — в документе, цены — в регистре, остатки — в другом регистре. Тип соединения решает, какие строки попадут в результат, если пары не нашлось.

СоединениеЧто попадёт в результатТипичная задача
ВНУТРЕННЕЕ СОЕДИНЕНИЕтолько строки, у которых есть пара в обеих таблицахтовары, у которых есть и продажи, и цена
ЛЕВОЕ СОЕДИНЕНИЕвсе строки левой таблицы, справа NULL, если пары нетвсе продажи, цена — где задана
ПРАВОЕ СОЕДИНЕНИЕто же зеркальнона практике заменяют левым, переставив таблицы
ПОЛНОЕ СОЕДИНЕНИЕвсе строки обеих таблицсверка двух источников: что есть только здесь, что только там

В конструкторе тип задают флажки «Все» на вкладке «Связи»: «Все» у левой таблицы — левое соединение, у обеих — полное, ни у одной — внутреннее.

Что знать про NULL после внешнего соединения:

  • ❗ Сравнение Цены.Цена = 0 для строки без пары вернёт не Истину, а ложь: там NULL. Проверяйте Цены.Цена ЕСТЬ NULL или оборачивайте в ЕСТЬNULL().
  • ❗ Условие на правую таблицу в ГДЕ превращает левое соединение во внутреннее: строки с NULL отсекаются. Такое условие переносят в ПО или в параметры виртуальной таблицы.
  • ❗ Связь по неуникальному полю размножает строки. Если у товара две цены, сумма продаж удвоится. Сначала сверните правую таблицу до одной строки на ключ.

Отдельно от соединений существует ОБЪЕДИНИТЬ ВСЕ: оно не ставит таблицы рядом, а дописывает строки одной под другой. Так склеивают, например, поступления и реализации в один список движений. Обычный ОБЪЕДИНИТЬ без ВСЕ ещё и убирает одинаковые строки, поэтому работает медленнее; если дублей быть не может, пишите ОБЪЕДИНИТЬ ВСЕ.

Группировка, агрегатные функции и итоги

Агрегатные функции: СУММА, КОЛИЧЕСТВО, МАКСИМУМ, МИНИМУМ, СРЕДНЕЕ. Для подсчёта уникальных значений — КОЛИЧЕСТВО(РАЗЛИЧНЫЕ Поле), например число разных покупателей за месяц.

Правило, на котором спотыкаются новички: каждое поле в ВЫБРАТЬ либо стоит в СГРУППИРОВАТЬ ПО, либо обёрнуто в агрегатную функцию. Иначе запрос не выполнится с ошибкой о поле, которое не входит в группу.

Разница между ГДЕ и ИМЕЮЩИЕ:

  • ГДЕ отбирает строки до свёртки: «только проведённые документы».
  • ИМЕЮЩИЕ отбирает группы после свёртки: «только товары, продажи по которым больше 100 000».
СГРУППИРОВАТЬ ПО
	РеализацияТовары.Номенклатура
ИМЕЮЩИЕ
	СУММА(РеализацияТовары.Сумма) > 100000

Итоги по группировкам

ИТОГИ ПО добавляет в результат строки промежуточных итогов, как в отчёте с группировками. Например, ИТОГИ СУММА(Сумма) ПО ОБЩИЕ, Контрагент даст общую строку, строку по каждому контрагенту и детальные строки под ним.

Такой результат обходят вложенными выборками: Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам) на верхнем уровне и такой же вызов у строки выборки для уровня ниже. Для табличного документа или дерева значений это удобнее, чем считать итоги в коде.

Проверьте себя
Нужно оставить только тех покупателей, у которых больше трёх документов за месяц. Куда писать условие?
После левого соединения с ценами у части товаров цена пустая. Чем подставить ноль?
Нужно склеить строки поступлений и реализаций, дублей между ними быть не может. Что быстрее?

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

Запрос, который на демо-базе отрабатывает мгновенно, на рабочей базе в несколько миллионов строк может висеть минутами. Причины почти всегда одни и те же.

Что тормозит и как исправить

ПроблемаПочему медленноЧто сделать
Запрос внутри циклана 1000 строк — 1000 обращений к СУБДодин запрос с параметром-массивом или временной таблицей
Отбор виртуальной таблицы в ГДЕплатформа сначала считает остатки по всем данным, потом отбираетусловие в параметрах: .Остатки(&Дата, Склад = &Склад)
Обращение через точку к составному полю (Регистратор.Дата)платформа соединяет со всеми таблицами типов, которые может хранить полеВЫРАЗИТЬ(Регистратор КАК Документ.РеализацияТоваровУслуг).Дата или отбор по ССЫЛКА
Соединение с подзапросомСУБД плохо оценивает размер подзапросавынести подзапрос во временную таблицу с ИНДЕКСИРОВАТЬ ПО
Условия с ИЛИ по разным поляминдекс не используетсядва запроса через ОБЪЕДИНИТЬ ВСЕ
Лишние поля и строкигоняются данные, которые не нужнытолько нужные поля, ПЕРВЫЕ N для проверок существования

Тяжёлые запросы проверяйте на копии базы, а не на рабочей во время закрытия месяца. Долгий запрос на чтение не портит данные, но нагружает сервер и тормозит работу всем пользователям.

Отладка

  • 🔍 Консоль запросов. Вставьте текст, задайте параметры и посмотрите результат каждой временной таблицы пакета. Самый быстрый способ найти, на каком шаге «пропали» строки.
  • 🔍 Отладчик. Поставьте точку останова после Запрос.Выполнить() и вычислите в окне выражений РезультатЗапроса.Выгрузить(): таблицу можно посмотреть прямо в отладчике.
  • 🔍 Замер производительности. В Конфигураторе в меню Отладка включите замер, выполните операцию и выключите. Будет видно, какая строка кода сколько заняла.
  • 🔍 Технологический журнал. Для клиент-серверной базы включается на сервере файлом logcfg.xml. Показывает текст запроса, который ушёл в СУБД, и время его выполнения. Это работа администратора сервера: журнал быстро растёт, включайте его на ограниченное время.

Если строки потерялись — комментируйте по одному условию в ГДЕ и соединения по одному. Как только строки вернулись, виновник найден.

Частые вопросы

Можно ли писать запросы на английском?
Да. У языка запросов есть английский синтаксис: SELECT, FROM, WHERE, GROUP BY. Платформа понимает оба варианта, но в типовых конфигурациях принят русский, и конструктор формирует текст на нём.
Почему в запросе к документу попадают непроведённые и помеченные на удаление?
Запрос к таблице документа читает все записи. Добавьте условие Ссылка.Проведен или НЕ Ссылка.ПометкаУдаления. Запросы к регистрам этого не требуют: непроведённые документы движений не имеют.
Можно ли запросом изменить или удалить данные?
Нет. Язык запросов 1С только читает данные. Получите ссылки запросом, затем изменяйте объекты через ПолучитьОбъект() и Записать() в транзакции.
Чем отличаются РАЗЛИЧНЫЕ и СГРУППИРОВАТЬ ПО?
РАЗЛИЧНЫЕ просто убирает одинаковые строки. СГРУППИРОВАТЬ ПО сворачивает строки и даёт посчитать по группе суммы, количество, максимум. Если считать ничего не нужно, хватит РАЗЛИЧНЫЕ.
Как проверить запрос без доступа к Конфигуратору?
Откройте внешнюю обработку «Консоль запросов» через Файл → Открыть в пользовательском режиме. Нужны права на открытие внешних обработок: без них откройте её у администратора или попросите выдать право.