Язык запросов 1С на практике: от простой выборки до оптимизации
Содержание
Запрос в 1С начинается со слова ВЫБРАТЬ, перечисления полей, источника после ИЗ и отбора после ГДЕ. Это общий механизм платформы 8.3: язык запросов одинаков в Бухгалтерии, ЗУП, УТ и любой другой конфигурации, потому что работает на уровне платформы, а не прикладного решения.
Писать запрос можно вручную в модуле или собирать мышью через конструктор. Ниже — синтаксис, рабочие примеры на встроенном языке и приёмы, которые отделяют быстрый запрос от того, что вешает базу на минуту.
Из чего состоит запрос: ВЫБРАТЬ, ИЗ, ГДЕ
Минимальный запрос — три части: что выбрать, откуда и по какому условию. Платформа читает текст сверху вниз, но порядок секций фиксирован.
ВЫБРАТЬ
Номенклатура.Наименование,
Номенклатура.Артикул
ИЗ
Справочник.Номенклатура КАК Номенклатура
ГДЕ
Номенклатура.ПометкаУдаления = ЛОЖЬ
Разберём по строкам:
- 📌
ВЫБРАТЬ— список полей. Через точку обращаемся к реквизитам и реквизитам реквизитов (Номенклатура.ЕдиницаИзмерения.Наименование). - 📌
ИЗ— источник данных. Это таблица объекта:Справочник.Номенклатура,Документ.РеализацияТоваровУслуг, регистр. - 📌
КАК Номенклатура— псевдоним таблицы. Короткое имя, по которому дальше ссылаемся на поля. - 📌
ГДЕ— условие отбора. Строки, не прошедшие условие, в результат не попадают.
Полный набор секций в порядке их следования:
| Секция | Назначение |
|---|---|
ВЫБРАТЬ | поля результата |
ИЗ | источники и соединения |
ГДЕ | отбор строк |
СГРУППИРОВАТЬ ПО | группировка для итогов |
ИМЕЮЩИЕ | отбор уже по сгруппированным данным |
УПОРЯДОЧИТЬ ПО | сортировка результата |
ИТОГИ … ПО | многоуровневые итоги по иерархии |
Служебные слова можно писать строчными или прописными — платформа не различает регистр ключевых слов. Прописные удобнее читать, поэтому так принято в типовых.
ВЫБРАТЬ РАЗЛИЧНЫЕ убирает дубли строк. Но включать его «на всякий случай» не нужно: уникализация стоит ресурсов, а при наличии ссылки на объект дублей обычно и так нет.
Как открыть конструктор запроса
Руками весь текст набирают редко. В модуле (обработка, отчёт, общий модуль) ставим курсор на строку с текстом запроса, правой кнопкой — Конструктор запроса. Если текста ещё нет, конструктор предложит создать новый.
Окно конструктора делится на вкладки:
- Таблицы и поля — слева дерево всех объектов базы, перетаскиваем нужные таблицы в среднюю колонку, поля — в правую.
- Связи — настройка соединений между таблицами.
- Группировка, Условия, Порядок, Итоги — остальные секции отдельными вкладками.
Кнопка ОК вставляет готовый текст в модуль. Обратно текст тоже читается: встали курсором на существующий запрос, открыли конструктор — он разобрал текст по вкладкам. Это удобно, когда правишь чужой код.
Конструктор не понимает текст, собранный из кусков строк через конкатенацию ("ВЫБРАТЬ " + ПолеПеременной). Для конструктора запрос должен быть цельным литералом.
Есть и второй конструктор — Конструктор запроса с обработкой результата. Он дополнительно генерирует код создания объекта Запрос, выполнения и обхода выборки. Для быстрого старта удобно, потом код правят под себя.
Параметры и условия отбора
Хардкодить даты и ссылки прямо в текст нельзя — запрос перестанет быть переиспользуемым. Вместо значения ставим параметр с амперсандом, а само значение передаём из кода.
ВЫБРАТЬ
Продажи.Контрагент,
Продажи.Сумма
ИЗ
Документ.РеализацияТоваровУслуг КАК Продажи
ГДЕ
Продажи.Дата МЕЖДУ &ДатаНачала И &ДатаКонца
И Продажи.Организация = &Организация
И Продажи.Проведен = ИСТИНА
Параметры &ДатаНачала, &ДатаКонца, &Организация задаём в коде перед выполнением:
Запрос = Новый Запрос;
Запрос.Текст = ТекстЗапроса;
Запрос.УстановитьПараметр("ДатаНачала", НачалоДня(ДатаОтчета));
Запрос.УстановитьПараметр("ДатаКонца", КонецДня(ДатаОтчета));
Запрос.УстановитьПараметр("Организация", ВыбраннаяОрганизация);
Результат = Запрос.Выполнить();
Выборка = Результат.Выбрать();
Пока Выборка.Следующий() Цикл
Сообщить(Выборка.Контрагент);
КонецЦикла;
Полезные операторы в секции ГДЕ:
МЕЖДУ … И …— диапазон (включая границы).В (&СписокЗначений)— вхождение в список или массив, переданный параметром.В ИЕРАРХИИ (&Группа)— элемент и все его подчинённые в иерархическом справочнике.ПОДОБНО "%шуруп%"— поиск по шаблону,%заменяет любое число символов.ССЫЛКА— проверка типа ссылочного поля:РегистраторТипа ССЫЛКА Документ.ПоступлениеТоваровУслуг.
Дату периода передавайте через НачалоДня и КонецДня. Если передать просто дату документа, время 00:00:00 отрежет все документы этого дня, кроме полночных. КонецДня ставит время 23:59:59.
Условие с ИЛИ всегда берите в скобки, когда оно соседствует с И: (А ИЛИ Б) И В и А ИЛИ (Б И В) дают разный результат, как в обычной логике.
Соединение таблиц: четыре вида связей
Данные редко лежат в одной таблице. Контрагент — в одном справочнике, его договоры — в другом, продажи — в документе. Соединения связывают их по общему полю.
ВЫБРАТЬ
Товары.Номенклатура,
Товары.Количество,
Цены.Цена
ИЗ
Документ.РеализацияТоваровУслуг.Товары КАК Товары
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныНоменклатуры КАК Цены
ПО Товары.Номенклатура = Цены.Номенклатура
Четыре типа соединения отличаются тем, какие строки остаются, если пары не нашлось:
| Соединение | Что в результате |
|---|---|
ВНУТРЕННЕЕ СОЕДИНЕНИЕ | только строки, у которых есть пара в обеих таблицах |
ЛЕВОЕ СОЕДИНЕНИЕ | все строки левой таблицы; из правой — что нашлось, иначе NULL |
ПРАВОЕ СОЕДИНЕНИЕ | зеркально левому: все строки правой |
ПОЛНОЕ СОЕДИНЕНИЕ | все строки обеих таблиц, непарным ставится NULL |
На практике 90% соединений — ЛЕВОЕ: берём все основные данные и подтягиваем к ним дополнительные. Правое почти не используют — его всегда можно переписать как левое, поменяв таблицы местами.
После левого соединения проверяйте поля правой таблицы на NULL. Если пары не нашлось, в поле будет не ноль и не пустая строка, а NULL, и арифметика с ним даст NULL. Оборачивайте такие поля в ЕСТЬNULL:
ВЫБРАТЬ
Товары.Номенклатура,
ЕСТЬNULL(Цены.Цена, 0) КАК Цена
ЕСТЬNULL(Поле, ЗначениеЕслиNULL) возвращает второй аргумент, когда первый — NULL. Это самая частая причина «неправильных сумм» в отчётах с соединениями.
Группировка и агрегатные функции
Когда нужны не строки, а итоги — сумма по контрагенту, количество документов за день, — включаем группировку. Агрегатная функция в ВЫБРАТЬ и секция СГРУППИРОВАТЬ ПО работают в паре.
ВЫБРАТЬ
Продажи.Контрагент КАК Контрагент,
СУММА(Продажи.Сумма) КАК ВсегоСумма,
КОЛИЧЕСТВО(Продажи.Ссылка) КАК ЧислоДокументов
ИЗ
Документ.РеализацияТоваровУслуг КАК Продажи
ГДЕ
Продажи.Проведен = ИСТИНА
СГРУППИРОВАТЬ ПО
Продажи.Контрагент
Доступные агрегатные функции:
СУММА(…)— сумма числового поля.КОЛИЧЕСТВО(…)— число строк;КОЛИЧЕСТВО(РАЗЛИЧНЫЕ Поле)— число уникальных значений.МАКСИМУМ(…),МИНИМУМ(…)— крайние значения, работают и с датами.СРЕДНЕЕ(…)— среднее по числовому полю.
Главное правило: каждое поле из ВЫБРАТЬ либо стоит под агрегатной функцией, либо перечислено в СГРУППИРОВАТЬ ПО. Иначе платформа не поймёт, какое из множества значений поля показать в строке итога, и вернёт ошибку.
Отбор по уже посчитанным итогам делается не в ГДЕ, а в ИМЕЮЩИЕ:
СГРУППИРОВАТЬ ПО
Продажи.Контрагент
ИМЕЮЩИЕ
СУММА(Продажи.Сумма) > 100000
ГДЕ отсеивает строки до группировки, ИМЕЮЩИЕ — группы после. Если фильтр по полю, которое не агрегируется (например, по организации), ставьте его в ГДЕ — он отработает раньше и быстрее.
Для многоуровневых итогов с промежуточными суммами есть секция ИТОГИ … ПО: она строит дерево результата (итог по контрагенту, внутри — по договору). Но её обходят через Результат.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам), это уже тонкость обхода выборки.
Временные таблицы и пакетные запросы
Когда логика сложная, один запрос превращается в нечитаемую простыню с вложенными подзапросами. Разбить её помогают временные таблицы: промежуточный результат кладётся в таблицу ПОМЕСТИТЬ, а следующий запрос из неё читает.
ВЫБРАТЬ
Заказы.Контрагент КАК Контрагент,
СУММА(Заказы.Сумма) КАК СуммаЗаказов
ПОМЕСТИТЬ ВтЗаказы
ИЗ
Документ.ЗаказПокупателя КАК Заказы
СГРУППИРОВАТЬ ПО
Заказы.Контрагент
;
ВЫБРАТЬ
ВтЗаказы.Контрагент,
ВтЗаказы.СуммаЗаказов
ИЗ
ВтЗаказы КАК ВтЗаказы
ГДЕ
ВтЗаказы.СуммаЗаказов > &Порог
Несколько запросов, разделённых точкой с запятой, — это пакет. Чтобы пакет работал с временными таблицами, запросу нужен менеджер временных таблиц:
МенеджерВТ = Новый МенеджерВременныхТаблиц;
Запрос = Новый Запрос;
Запрос.МенеджерВременныхТаблиц = МенеджерВТ;
Запрос.Текст = ТекстПакета;
Результат = Запрос.Выполнить();
Где временные таблицы реально выручают:
- 🔹 Нужно соединить итоги двух регистров — сначала собираем каждый в свою ВТ, потом соединяем две компактные таблицы вместо тяжёлых виртуальных.
- 🔹 Один и тот же набор данных используется в запросе несколько раз — считаем один раз в ВТ.
- 🔹 Передать в запрос таблицу значений из кода — её кладут во временную таблицу через
Запрос.УстановитьПараметри источник&ТаблицаИзКода.
Если по полю временной таблицы потом идёт соединение или отбор, добавьте ИНДЕКСИРОВАТЬ ПО ИмяПоля после ПОМЕСТИТЬ. На больших таблицах индекс ускоряет следующий запрос в разы.
Частые ошибки и как ускорить запрос
Медленный запрос почти всегда упирается в одни и те же грабли. Разберём их и заодно — что делает запрос быстрым.
Отбор виртуальной таблицы в ГДЕ вместо параметров. Остатки и обороты — это виртуальные таблицы (РегистрНакопления.ОстаткиИОбороты, РегистрБухгалтерии.Остатки). У них есть параметры в скобках. Условие нужно передавать туда, а не в ГДЕ снаружи:
// медленно: отбор снаружи
ИЗ РегистрНакопления.ТоварыНаСкладах.Остатки КАК Остатки
ГДЕ Остатки.Склад = &Склад
// быстро: отбор в параметрах виртуальной таблицы
ИЗ РегистрНакопления.ТоварыНаСкладах.Остатки(, Склад = &Склад) КАК Остатки
Во втором варианте платформа отфильтрует данные до расчёта остатков, а не после. На регистре с миллионами записей разница — секунды против долей секунды.
Соединение с подзапросом. Соединять таблицу с вложенным (ВЫБРАТЬ …) дорого: у подзапроса нет индексов. Вынесите подзапрос во временную таблицу, проиндексируйте поле связи — и соединяйте уже с ней.
Забытый ЕСТЬNULL после левого соединения. Разобрали выше: NULL в сумме ломает итог. Это не про скорость, а про корректность, но встречается чаще всего.
Получение представлений через точку в цикле. Если в выборке дёргать Выборка.Контрагент.Наименование, платформа пойдёт в базу за каждым контрагентом отдельно. Берите ПРЕДСТАВЛЕНИЕ(Продажи.Контрагент) прямо в запросе — тогда имя придёт одним пакетом вместе с данными.
Работа с составными полями без типизации. Поле Регистратор или Субконто может хранить ссылки разных типов. Чтобы обратиться к реквизиту конкретного типа, приведите тип через ВЫРАЗИТЬ:
ВЫРАЗИТЬ(Движения.Регистратор КАК Документ.РеализацияТоваровУслуг).Договор
Короткий тест перед выполнением запроса на рабочей базе:
Проверьте, что поняли ключевые отличия — короткий тест:
Частые вопросы
Чем язык запросов 1С отличается от SQL?
Можно ли запросом менять или удалять данные?
Почему запрос в отчёте показывает суммы с NULL или пустые ячейки?
ЕСТЬNULL. Там, где пара во второй таблице не нашлась, стоит NULL, и он протаскивается в итог. Оберните поля правой таблицы в ЕСТЬNULL(Поле, 0).Как передать в запрос список значений для отбора?
В (&СписокОтбора). Для отбора по группе и всем подчинённым элементам — В ИЕРАРХИИ (&Группа).