Язык запросов 1С с нуля: от первого ВЫБРАТЬ до временных таблиц
Содержание
Запрос в 1С пишут на встроенном языке запросов и выполняют через объект Запрос: задают текст, передают параметры методом УстановитьПараметр(), вызывают Выполнить() и обходят результат. Текст удобнее всего создавать не руками, а через конструктор: в модуле Конфигуратора щёлкните правой кнопкой мыши и выберите Конструктор запроса....
Типичная ситуация: бухгалтер просит выгрузить продажи по контрагентам за квартал, а готового отчёта нет. Программист открывает Конфигуратор, за пять минут собирает запрос и проверяет его в консоли. Ниже — как писать запросы в 1С так, чтобы они работали быстро и не падали на чужих данных.
Где писать и проверять запрос
Запрос живёт в трёх местах:
- 🧩 Модуль формы, объекта или общего модуля — запрос выполняется кодом.
- 📊 Схема компоновки данных — набор данных типа «Запрос» в отчёте.
- 🔍 Консоль запросов — внешняя обработка для отладки. В неё вставляют текст, задают параметры и сразу видят результат таблицей.
Начинающим лучше идти по порядку: собрали текст в конструкторе, проверили в консоли на реальной копии базы, только потом перенесли в модуль.
Отлаживайте запросы на копии базы. Сам запрос данные не меняет, но тяжёлый запрос без отборов на большой рабочей базе может надолго загрузить сервер, и пользователи почувствуют это сразу.
Конструктор запроса: из чего состоит окно
Конструктор вызывают из контекстного меню текстового редактора модуля. Есть два пункта:
| Пункт меню | Что делает |
|---|---|
Конструктор запроса... | Создаёт или редактирует только текст запроса |
Конструктор запроса с обработкой результата... | Дополнительно генерирует код выполнения и обхода выборки |
Если курсор стоит внутри уже готового текста запроса, конструктор откроет его для правки. Если внутри текста есть синтаксическая ошибка, конструктор сначала сообщит о ней.
Основные вкладки окна:
- Таблицы и поля — слева дерево базы данных, посередине выбранные таблицы, справа поля результата. Таблицы и поля переносят двойным щелчком или стрелками.
- Связи — условия соединения таблиц, если выбрано больше одной.
- Группировка — поля группировки и суммируемые поля с агрегатной функцией.
- Условия — отборы, то есть секция
ГДЕ. - Дополнительно —
ПЕРВЫЕ N,РАЗЛИЧНЫЕ,ДЛЯ ИЗМЕНЕНИЯ, тип запроса (выборка данных или создание временной таблицы). - Объединения/Псевдонимы — несколько запросов в один результат и имена колонок.
- Порядок — сортировка.
- Итоги — секция
ИТОГИ ПО. - Пакет запросов — список запросов в пакете, сюда добавляют временные таблицы.
Кнопка Запрос в нижней части окна показывает готовый текст. Полезно смотреть туда после каждой вкладки: так быстрее запоминается синтаксис.
У виртуальных таблиц регистров (Остатки, Обороты, СрезПоследних) есть параметры. В конструкторе они открываются кнопкой Параметры виртуальной таблицы над списком выбранных таблиц. Отбор, заданный там, работает быстрее отбора в ГДЕ.
Синтаксис запроса 1С: секции и порядок
Минимальный запрос состоит из двух слов и источника:
ВЫБРАТЬ
Номенклатура.Ссылка,
Номенклатура.Наименование,
Номенклатура.Артикул
ИЗ
Справочник.Номенклатура КАК Номенклатура
Полная схема выглядит так. Порядок секций строгий, менять его нельзя:
| Секция | Назначение | Обязательна |
|---|---|---|
ВЫБРАТЬ | Список полей результата | Да |
ИЗ | Источники данных | Почти всегда |
ГДЕ | Отбор строк до группировки | Нет |
СГРУППИРОВАТЬ ПО | Группировка | Нет |
ИМЕЮЩИЕ | Отбор после группировки | Нет |
ОБЪЕДИНИТЬ / ОБЪЕДИНИТЬ ВСЕ | Склейка нескольких выборок | Нет |
УПОРЯДОЧИТЬ ПО | Сортировка | Нет |
ИТОГИ ПО | Итоговые строки | Нет |
Несколько правил, которые экономят время на отладке:
- 🔤 Ключевые слова можно писать по-английски (
SELECT,FROM) — платформа понимает оба варианта. Смешивать в одном запросе не стоит. - 🏷️ Каждой таблице давайте псевдоним через
КАК. Без него поля из двух таблиц становятся неоднозначными. - 🔗 Через точку можно обращаться к реквизитам ссылки:
Реализация.Контрагент.ИНН. Это удобно, но каждая точка — неявное соединение с ещё одной таблицей. - 📄 Табличную часть документа выбирают как отдельную таблицу:
Документ.РеализацияТоваровУслуг.Товары. ПолеСсылкав ней ведёт на сам документ.
Чтобы выполнить запрос из кода, нужно пять строк:
Запрос = Новый Запрос;
Запрос.Текст = "ВЫБРАТЬ
| Номенклатура.Ссылка КАК Ссылка,
| Номенклатура.Наименование КАК Наименование
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
|ГДЕ
| НЕ Номенклатура.ПометкаУдаления";
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
Сообщить(Выборка.Наименование);
КонецЦикла;
Результат можно не только обходить выборкой. Метод Выгрузить() превращает его в таблицу значений, а Пустой() быстро проверяет, вернулось ли хоть что-то.
Параметры и условия отбора
Значения в текст запроса не подставляют склейкой строк. Для этого есть параметры: в тексте пишут имя с амперсандом, а значение передают отдельно.
Запрос.Текст = "ВЫБРАТЬ
| Реализация.Ссылка КАК Документ,
| Реализация.Контрагент КАК Контрагент,
| Реализация.СуммаДокумента КАК Сумма
|ИЗ
| Документ.РеализацияТоваровУслуг КАК Реализация
|ГДЕ
| Реализация.Дата МЕЖДУ &НачалоПериода И &КонецПериода
| И Реализация.Проведен
| И Реализация.Организация = &Организация";
Запрос.УстановитьПараметр("НачалоПериода", НачалоДня(Дата1));
Запрос.УстановитьПараметр("КонецПериода", КонецДня(Дата2));
Запрос.УстановитьПараметр("Организация", Организация);
Почему параметры, а не склейка: платформа сама приводит типы, нет проблем с форматом дат, текст запроса не ломается от кавычек в наименованиях.
Операторы, которые чаще всего нужны в ГДЕ:
| Оператор | Пример | Когда нужен |
|---|---|---|
=, <>, <, > | Сумма > 10000 | Простое сравнение |
МЕЖДУ … И … | Дата МЕЖДУ &Н И &К | Период, диапазон |
В (…) | Склад В (&СписокСкладов) | Значение из списка или массива |
В ИЕРАРХИИ | Номенклатура В ИЕРАРХИИ (&Группа) | Элемент с учётом вложенных групп |
ПОДОБНО | Наименование ПОДОБНО "%болт%" | Поиск по части строки |
ЕСТЬ NULL | Цены.Цена ЕСТЬ NULL | Нет пары в левом соединении |
ССЫЛКА | Регистратор ССЫЛКА Документ.ПоступлениеТоваровУслуг | Проверка типа ссылки |
Конец периода передавайте через КонецДня(). Если передать просто дату, она придёт с временем 00:00:00, и документы последнего дня выпадут из выборки. Это самая частая причина «у меня в отчёте сумма меньше, чем в журнале».
Для отбора остатков и оборотов регистров условие лучше ставить в параметры виртуальной таблицы:
ВЫБРАТЬ
Остатки.Номенклатура,
Остатки.КоличествоОстаток
ИЗ
РегистрНакопления.ТоварыНаСкладах.Остатки(&Дата, Склад = &Склад) КАК Остатки
Если то же условие перенести в ГДЕ, платформа сначала посчитает остатки по всем складам, а потом отбросит лишнее. На базе с десятком складов разница во времени заметна.
Соединения таблиц и объединение выборок
Когда данные лежат в разных таблицах, их соединяют. В конструкторе это вкладка «Связи», в тексте — секция ИЗ.
| Вид соединения | Что вернёт |
|---|---|
ВНУТРЕННЕЕ СОЕДИНЕНИЕ | Только строки, у которых есть пара в обеих таблицах |
ЛЕВОЕ СОЕДИНЕНИЕ | Все строки левой таблицы; поля правой — NULL, если пары нет |
ПРАВОЕ СОЕДИНЕНИЕ | Зеркально левому; на практике его переписывают в левое |
ПОЛНОЕ СОЕДИНЕНИЕ | Все строки обеих таблиц |
Пример: номенклатура с остатком, включая позиции без остатка:
ВЫБРАТЬ
Номенклатура.Ссылка КАК Номенклатура,
ЕСТЬNULL(Остатки.КоличествоОстаток, 0) КАК Остаток
ИЗ
Справочник.Номенклатура КАК Номенклатура
ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ТоварыНаСкладах.Остатки(&Дата) КАК Остатки
ПО Номенклатура.Ссылка = Остатки.Номенклатура
ГДЕ
НЕ Номенклатура.ЭтоГруппа
Функция ЕСТЬNULL() здесь обязательна. Без неё у позиций без остатка придёт NULL, и любое сложение с ним в коде даст ошибку или пустое значение.
Отбор по правой таблице левого соединения ставьте в условие ПО, а не в ГДЕ. Условие в ГДЕ вида Остатки.Склад = &Склад выкинет строки, где Остатки пустые, и левое соединение фактически станет внутренним.
ОБЪЕДИНИТЬ решает другую задачу: складывает строки нескольких запросов в одну таблицу. Например, поступления и реализации в один список движений. Число и порядок полей во всех частях должны совпадать. ОБЪЕДИНИТЬ ВСЕ оставляет дубли и работает быстрее; просто ОБЪЕДИНИТЬ удаляет одинаковые строки.
Группировка и агрегатные функции
Чтобы получить сумму продаж по контрагенту, строки группируют и сворачивают агрегатными функциями.
ВЫБРАТЬ
Реализация.Контрагент КАК Контрагент,
КОЛИЧЕСТВО(Реализация.Ссылка) КАК ЧислоДокументов,
СУММА(Реализация.СуммаДокумента) КАК Выручка
ИЗ
Документ.РеализацияТоваровУслуг КАК Реализация
ГДЕ
Реализация.Проведен
И Реализация.Дата МЕЖДУ &НачалоПериода И &КонецПериода
СГРУППИРОВАТЬ ПО
Реализация.Контрагент
ИМЕЮЩИЕ
СУММА(Реализация.СуммаДокумента) > 100000
УПОРЯДОЧИТЬ ПО
Выручка УБЫВ
Агрегатных функций пять, плюс вариант для уникальных значений:
- ➕
СУММА()— сумма числового поля. - 🔢
КОЛИЧЕСТВО()— число строк;КОЛИЧЕСТВО(РАЗЛИЧНЫЕ Поле)— число разных значений. - ⬆️
МАКСИМУМ()и ⬇️МИНИМУМ()— крайние значения, работают и с датами. - ➗
СРЕДНЕЕ()— среднее арифметическое.
Правило группировки простое: каждое поле в ВЫБРАТЬ либо стоит в СГРУППИРОВАТЬ ПО, либо обёрнуто агрегатной функцией. Нарушите — запрос не выполнится.
ГДЕ и ИМЕЮЩИЕ путают постоянно. ГДЕ отбирает исходные строки до свёртки, ИМЕЮЩИЕ — уже готовые группы. Условие «выручка больше 100 000» возможно только в ИМЕЮЩИЕ, потому что до группировки выручки по контрагенту ещё нет.
Секция ИТОГИ ПО добавляет к результату итоговые строки, например подытог по каждому контрагенту и общий итог. Её используют для иерархических выборок: обходят результат с параметром ОбходРезультатаЗапроса.ПоГруппировкам. Для отчётов на СКД итоги обычно не нужны — компоновка считает их сама.
Временные таблицы, пакеты и скорость запросов
Сложную выборку удобнее разбить на шаги. Первый запрос кладёт данные во временную таблицу, следующие работают с ней.
Запрос = Новый Запрос;
Запрос.МенеджерВременныхТаблиц = Новый МенеджерВременныхТаблиц;
Запрос.Текст = "ВЫБРАТЬ
| Товары.Номенклатура КАК Номенклатура,
| СУММА(Товары.Количество) КАК Количество
|ПОМЕСТИТЬ ВТ_Продажи
|ИЗ
| Документ.РеализацияТоваровУслуг.Товары КАК Товары
|ГДЕ
| Товары.Ссылка.Проведен
| И Товары.Ссылка.Дата МЕЖДУ &НачалоПериода И &КонецПериода
|СГРУППИРОВАТЬ ПО
| Товары.Номенклатура
|ИНДЕКСИРОВАТЬ ПО
| Номенклатура
|;
|
|////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ВТ_Продажи.Номенклатура,
| ВТ_Продажи.Количество,
| ЕСТЬNULL(Остатки.КоличествоОстаток, 0) КАК Остаток
|ИЗ
| ВТ_Продажи КАК ВТ_Продажи
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ТоварыНаСкладах.Остатки(
| &КонецПериода,
| Номенклатура В (ВЫБРАТЬ Номенклатура ИЗ ВТ_Продажи)) КАК Остатки
| ПО ВТ_Продажи.Номенклатура = Остатки.Номенклатура";
Что здесь происходит:
ПОМЕСТИТЬ ВТ_Продажипревращает первый запрос в создание временной таблицы.- Точка с запятой отделяет запросы пакета. Строка из косых черт — просто комментарий для читаемости, её вставляет конструктор.
ИНДЕКСИРОВАТЬ ПОстроит индекс по полю, по которому дальше идёт соединение.- Виртуальная таблица остатков сразу ограничена номенклатурой из продаж — платформа не считает остатки по всему справочнику.
Выполнить() вернёт результат последнего запроса пакета. Если нужны результаты всех шагов, вызывайте ВыполнитьПакет() — он возвращает массив результатов. Таблица живёт, пока жив менеджер временных таблиц; освободить её раньше можно запросом УНИЧТОЖИТЬ ВТ_Продажи.
Приёмы, которые дают основной выигрыш в скорости:
- 🚫 Не выполняйте запрос в цикле. Сто запросов по одному документу медленнее одного запроса по ста документам. Соберите ссылки в массив и передайте параметром в условие
В (&Ссылки). - 🎯 Отбор — в параметры виртуальных таблиц, а не в
ГДЕ. - 🔗 Меньше точек через составные ссылки. Обращение
Регистратор.Контрагент.ИНН, где регистратор может быть документом десяти видов, порождает соединение с каждой таблицей. Уточните тип черезВЫРАЗИТЬ(Регистратор КАК Документ.РеализацияТоваровУслуг). - 📦 Подзапросы в соединениях заменяйте временными таблицами. Оптимизатор СУБД хуже работает с вложенными запросами в
ПО. - 🔝
ПЕРВЫЕ Nдля проверок. Когда нужно лишь узнать, есть ли хоть одна запись,ВЫБРАТЬ ПЕРВЫЕ 1быстрее полной выборки.
Частые ошибки в запросах и как их найти
Платформа указывает в сообщении строку и колонку ошибки, например {(12, 5)}. Счёт строк идёт от начала текста запроса, а не модуля. Скопируйте текст в консоль или конструктор — так проще найти место.
Отдельно о правах. Запрос выполняется с правами текущего пользователя. Если у бухгалтера нет доступа к таблице, запрос упадёт с ошибкой нарушения прав, хотя у администратора всё работает. Если на объекты настроены ограничения на уровне записей, а в запросе нет слова РАЗРЕШЕННЫЕ после ВЫБРАТЬ, пользователь получит ошибку вместо выборки только доступных ему записей.