Язык запросов 1С 8.3 с нуля: от первого ВЫБРАТЬ до пакета с временными таблицами
Содержание
Чтобы создать запрос в 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 и проверяйте Пустой(). Платформа не будет тянуть всю таблицу.
Для прав пользователей есть слово РАЗРЕШЕННЫЕ: ВЫБРАТЬ РАЗРЕШЕННЫЕ .... С ним записи, закрытые ограничением доступа на уровне записей, просто не попадут в результат. Без него пользователь с ограниченными правами получит ошибку чтения вместо отчёта.
Конструктор запросов: собираем текст без ручного набора
Конструктор открывается в Конфигураторе из контекстного меню модуля: Конструктор запроса... или Конструктор запроса с обработкой результата.... Второй вариант сразу допишет код с выборкой и циклом. Если курсор стоит внутри уже готового текста запроса, конструктор откроет его для правки.
Порядок работы на вкладках:
- Таблицы и поля. Слева — дерево метаданных. Перетащите нужную таблицу в центр, затем поля в правую колонку. Табличная часть документа лежит внутри узла документа.
- Связи. Появится, если таблиц две и больше. Укажите условие связи и отметьте флажки «Все» — от них зависит тип соединения.
- Группировка. Перенесите поля группировки налево, суммируемые поля — в «Суммируемые поля» и выберите функцию.
- Условия. Перетащите поле, конструктор сам подставит параметр вида
&Номенклатура. Галочка «Произвольное» позволяет написать условие вручную. - Дополнительно. Здесь включаются
ПЕРВЫЕ,РАЗЛИЧНЫЕ,РАЗРЕШЕННЫЕ, а также переключатель «Создание временной таблицы». - Объединения/Псевдонимы. Переименуйте поля и добавьте второй запрос для
ОБЪЕДИНИТЬ. - Порядок и Итоги — сортировка и итоги по группировкам.
- Пакет запросов. Если запросов несколько, они видны здесь списком.
Кнопка «Запрос» внизу показывает получившийся текст. Подглядывать туда полезно: так язык запоминается быстрее, чем по справочнику.
Конструктор переписывает текст целиком по своим правилам. Комментарии // внутри текста запроса и ручное форматирование после его открытия пропадут.
Если доступа к Конфигуратору нет, запросы пишут и проверяют во внешней обработке «Консоль запросов». Её открывают в пользовательском режиме через Файл → Открыть. Внутри консоли обычно есть тот же конструктор.
Параметры и временные таблицы в запросах
Значения в текст запроса не вклеивают строкой. Вместо этого пишут параметр с амперсандом &ИмяПараметра и передают значение через УстановитьПараметр(). Плюсы: ссылки, даты и списки передаются как есть, без преобразования в строку, а платформа может переиспользовать план запроса.
В параметр можно передать массив или список значений и проверять вхождение: ГДЕ Товары.Номенклатура В (&СписокТоваров).
Конец периода передавайте как КонецДня(Дата). Если передать просто дату из поля ввода, у неё время 00:00:00, и документы последнего дня в отбор МЕЖДУ не попадут.
Временные таблицы и пакет запросов
Временная таблица — промежуточный результат, к которому следующие запросы обращаются как к обычной таблице. Создаётся словом ПОМЕСТИТЬ, запросы пакета разделяются точкой с запятой.
Пример для БП 3.0: сначала собираем продажи, затем подтягиваем к ним цену по типу цен из регистра.
ВЫБРАТЬ
Товары.Номенклатура КАК Номенклатура,
СУММА(Товары.Количество) КАК Количество
ПОМЕСТИТЬ ВТПродажи
ИЗ
Документ.РеализацияТоваровУслуг.Товары КАК Товары
ГДЕ
Товары.Ссылка.Проведен
И Товары.Ссылка.Дата МЕЖДУ &НачалоПериода И &КонецПериода
СГРУППИРОВАТЬ ПО
Товары.Номенклатура
ИНДЕКСИРОВАТЬ ПО
Номенклатура
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
ВТПродажи.Номенклатура КАК Номенклатура,
ВТПродажи.Количество КАК Количество,
ЕСТЬNULL(Цены.Цена, 0) КАК Цена
ИЗ
ВТПродажи КАК ВТПродажи
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныНоменклатуры.СрезПоследних(
&КонецПериода,
ТипЦен = &ТипЦен
И Номенклатура В
(ВЫБРАТЬ ВТ.Номенклатура ИЗ ВТПродажи КАК ВТ)) КАК Цены
ПО ВТПродажи.Номенклатура = Цены.Номенклатура
Как это выполнить в коде:
Запрос.Выполнить()вернёт результат последнего запроса пакета.Запрос.ВыполнитьПакет()вернёт массив результатов всех запросов. Индексы начинаются с нуля.- Чтобы временные таблицы пережили выполнение и были доступны другим запросам, назначьте
Запрос.МенеджерВременныхТаблиц = Новый МенеджерВременныхТаблици передавайте этот менеджер дальше.
Ненужную таблицу внутри пакета удаляют командой УНИЧТОЖИТЬ ВТПродажи. Без менеджера временные таблицы исчезают сами после выполнения пакета.
Соединения таблиц: какое выбрать
Соединение нужно, когда данные лежат в разных таблицах: продажи — в документе, цены — в регистре, остатки — в другом регистре. Тип соединения решает, какие строки попадут в результат, если пары не нашлось.
| Соединение | Что попадёт в результат | Типичная задача |
|---|---|---|
ВНУТРЕННЕЕ СОЕДИНЕНИЕ | только строки, у которых есть пара в обеих таблицах | товары, у которых есть и продажи, и цена |
ЛЕВОЕ СОЕДИНЕНИЕ | все строки левой таблицы, справа NULL, если пары нет | все продажи, цена — где задана |
ПРАВОЕ СОЕДИНЕНИЕ | то же зеркально | на практике заменяют левым, переставив таблицы |
ПОЛНОЕ СОЕДИНЕНИЕ | все строки обеих таблиц | сверка двух источников: что есть только здесь, что только там |
В конструкторе тип задают флажки «Все» на вкладке «Связи»: «Все» у левой таблицы — левое соединение, у обеих — полное, ни у одной — внутреннее.
Что знать про NULL после внешнего соединения:
- ❗ Сравнение
Цены.Цена = 0для строки без пары вернёт не Истину, а ложь: там NULL. ПроверяйтеЦены.Цена ЕСТЬ NULLили оборачивайте вЕСТЬNULL(). - ❗ Условие на правую таблицу в
ГДЕпревращает левое соединение во внутреннее: строки с NULL отсекаются. Такое условие переносят вПОили в параметры виртуальной таблицы. - ❗ Связь по неуникальному полю размножает строки. Если у товара две цены, сумма продаж удвоится. Сначала сверните правую таблицу до одной строки на ключ.
Отдельно от соединений существует ОБЪЕДИНИТЬ ВСЕ: оно не ставит таблицы рядом, а дописывает строки одной под другой. Так склеивают, например, поступления и реализации в один список движений. Обычный ОБЪЕДИНИТЬ без ВСЕ ещё и убирает одинаковые строки, поэтому работает медленнее; если дублей быть не может, пишите ОБЪЕДИНИТЬ ВСЕ.
Группировка, агрегатные функции и итоги
Агрегатные функции: СУММА, КОЛИЧЕСТВО, МАКСИМУМ, МИНИМУМ, СРЕДНЕЕ. Для подсчёта уникальных значений — КОЛИЧЕСТВО(РАЗЛИЧНЫЕ Поле), например число разных покупателей за месяц.
Правило, на котором спотыкаются новички: каждое поле в ВЫБРАТЬ либо стоит в СГРУППИРОВАТЬ ПО, либо обёрнуто в агрегатную функцию. Иначе запрос не выполнится с ошибкой о поле, которое не входит в группу.
Разница между ГДЕ и ИМЕЮЩИЕ:
ГДЕотбирает строки до свёртки: «только проведённые документы».ИМЕЮЩИЕотбирает группы после свёртки: «только товары, продажи по которым больше 100 000».
СГРУППИРОВАТЬ ПО
РеализацияТовары.Номенклатура
ИМЕЮЩИЕ
СУММА(РеализацияТовары.Сумма) > 100000
Итоги по группировкам
ИТОГИ ПО добавляет в результат строки промежуточных итогов, как в отчёте с группировками. Например, ИТОГИ СУММА(Сумма) ПО ОБЩИЕ, Контрагент даст общую строку, строку по каждому контрагенту и детальные строки под ним.
Такой результат обходят вложенными выборками: Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам) на верхнем уровне и такой же вызов у строки выборки для уровня ниже. Для табличного документа или дерева значений это удобнее, чем считать итоги в коде.
Как ускорить и отладить запрос
Запрос, который на демо-базе отрабатывает мгновенно, на рабочей базе в несколько миллионов строк может висеть минутами. Причины почти всегда одни и те же.
Что тормозит и как исправить
| Проблема | Почему медленно | Что сделать |
|---|---|---|
| Запрос внутри цикла | на 1000 строк — 1000 обращений к СУБД | один запрос с параметром-массивом или временной таблицей |
Отбор виртуальной таблицы в ГДЕ | платформа сначала считает остатки по всем данным, потом отбирает | условие в параметрах: .Остатки(&Дата, Склад = &Склад) |
Обращение через точку к составному полю (Регистратор.Дата) | платформа соединяет со всеми таблицами типов, которые может хранить поле | ВЫРАЗИТЬ(Регистратор КАК Документ.РеализацияТоваровУслуг).Дата или отбор по ССЫЛКА |
| Соединение с подзапросом | СУБД плохо оценивает размер подзапроса | вынести подзапрос во временную таблицу с ИНДЕКСИРОВАТЬ ПО |
Условия с ИЛИ по разным полям | индекс не используется | два запроса через ОБЪЕДИНИТЬ ВСЕ |
| Лишние поля и строки | гоняются данные, которые не нужны | только нужные поля, ПЕРВЫЕ N для проверок существования |
Тяжёлые запросы проверяйте на копии базы, а не на рабочей во время закрытия месяца. Долгий запрос на чтение не портит данные, но нагружает сервер и тормозит работу всем пользователям.
Отладка
- 🔍 Консоль запросов. Вставьте текст, задайте параметры и посмотрите результат каждой временной таблицы пакета. Самый быстрый способ найти, на каком шаге «пропали» строки.
- 🔍 Отладчик. Поставьте точку останова после
Запрос.Выполнить()и вычислите в окне выраженийРезультатЗапроса.Выгрузить(): таблицу можно посмотреть прямо в отладчике. - 🔍 Замер производительности. В Конфигураторе в меню
Отладкавключите замер, выполните операцию и выключите. Будет видно, какая строка кода сколько заняла. - 🔍 Технологический журнал. Для клиент-серверной базы включается на сервере файлом
logcfg.xml. Показывает текст запроса, который ушёл в СУБД, и время его выполнения. Это работа администратора сервера: журнал быстро растёт, включайте его на ограниченное время.
Если строки потерялись — комментируйте по одному условию в ГДЕ и соединения по одному. Как только строки вернулись, виновник найден.
Частые вопросы
Можно ли писать запросы на английском?
Почему в запросе к документу попадают непроведённые и помеченные на удаление?
Ссылка.Проведен или НЕ Ссылка.ПометкаУдаления. Запросы к регистрам этого не требуют: непроведённые документы движений не имеют.Можно ли запросом изменить или удалить данные?
ПолучитьОбъект() и Записать() в транзакции.Чем отличаются РАЗЛИЧНЫЕ и СГРУППИРОВАТЬ ПО?
РАЗЛИЧНЫЕ просто убирает одинаковые строки. СГРУППИРОВАТЬ ПО сворачивает строки и даёт посчитать по группе суммы, количество, максимум. Если считать ничего не нужно, хватит РАЗЛИЧНЫЕ.Как проверить запрос без доступа к Конфигуратору?
Файл → Открыть в пользовательском режиме. Нужны права на открытие внешних обработок: без них откройте её у администратора или попросите выдать право.