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