Язык запросов 1С 8.3: от первой выборки до быстрого отчёта

· 9 мин

Содержание

Запрос в 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С — виртуальные таблицы регистров. Их нет в базе физически, платформа собирает их на лету из движений. Именно через них читают остатки и обороты, а не суммированием документов вручную.

Для регистра накопления доступны:

  • 🔹 .Остатки(&Период, Условие) — остатки на момент времени;
  • 🔹 .Обороты(&Начало, &Конец, Периодичность, Условие) — приход и расход за период;
  • 🔹 .ОстаткиИОбороты(…) — остаток на начало, обороты и остаток на конец одной таблицей.

Для регистра сведений:

  • 🔹 .СрезПоследних(&Период, Условие) — последнее по дате значение на момент (курсы, цены);
  • 🔹 .СрезПервых(&Период, Условие) — первое значение.

Для регистра бухгалтерии — .Остатки, .Обороты, .ОстаткиИОбороты и .ДвиженияССубконто.

Условия отбора задавайте в параметрах виртуальной таблицы, а не в секции ГДЕ. Разница принципиальна: параметр фильтрует данные ещё на этапе их сборки, и платформа поднимает только нужные движения. Условие в ГДЕ отработает уже после того, как виртуальная таблица собрана целиком.

ВЫБРАТЬ
    Остатки.Номенклатура КАК Номенклатура,
    Остатки.Склад КАК Склад,
    Остатки.КоличествоОстаток КАК Остаток
ИЗ
    РегистрНакопления.ТоварыНаСкладах.Остатки(
        &НаДату,
        Склад = &Склад И Номенклатура В (&СписокТоваров)) КАК Остатки

Параметр &НаДату ограничивает период, а условие Склад = &Склад отсекает лишние склады до расчёта остатка. Тот же отбор в ГДЕ заставил бы платформу посчитать остатки по всем складам, а потом выбросить лишнее.

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

Оптимизация запросов и типичные ошибки

Медленный запрос почти всегда упирается в одно из нескольких мест. Разберём их по порядку — от самых дорогих к мелким.

  1. Запрос в цикле. Самая частая и самая дорогая ошибка. Если внутри Для Каждого … Цикл стоит выполнение запроса, платформа дёргает СУБД на каждой итерации. Правильно — собрать все нужные данные одним запросом до цикла и обходить готовую выборку.
  2. Соединение с виртуальной таблицей или подзапросом. Оптимизатор СУБД плохо оценивает такие соединения. Результат виртуальной таблицы помещают во временную таблицу, индексируют и соединяют уже с ней.
  3. Разыменование через точку для составных полей. Обращение вида Регистратор.Дата, где Регистратор — составной тип (много видов документов), платформа разворачивает в соединение со всеми таблицами типа. Для таких полей делают явное соединение с нужной таблицей или ограничивают тип через ВЫРАЗИТЬ(… КАК Документ.РеализацияТоваровУслуг).
  4. Условие по правой таблице в ГДЕ после левого соединения. Как разобрано выше, это молча превращает левое соединение во внутреннее и даёт неверный результат.
  5. Отбор ПОДОБНО "%текст%". Шаблон с процентом в начале не даёт использовать индекс — полный перебор таблицы. По возможности ищите по началу строки: ПОДОБНО "текст%".
  6. Лишние РАЗЛИЧНЫЕ и ОБЪЕДИНИТЬ. Оба заставляют платформу искать и убирать дубли. Если дублей нет, это пустая работа.

Прежде чем оптимизировать «на глаз», замерьте запрос. В технологическом журнале или в консоли запросов видно время выполнения и число прочитанных строк. Оптимизируют то, что реально тормозит, а не то, что кажется подозрительным.

Отдельно про NULL. Он возникает не только при соединениях, но и в оборотах, где по измерению не было движений. Любое поле, которое может оказаться NULL, оборачивайте в ЕСТЬNULL(Поле, 0) до того, как участвует в арифметике или сравнении. Сравнение NULL = 0 не истинно и не ложно — оно тоже NULL, и строка просто выпадет из отбора.

Ещё один приём для читаемости и скорости — условная логика через ВЫБОР:

ВЫБОР
    КОГДА Остатки.Остаток > 0 ТОГДА "В наличии"
    КОГДА Остатки.Остаток = 0 ТОГДА "Нет"
    ИНАЧЕ "Под заказ"
КОНЕЦ КАК Статус

Такой расчёт на стороне СУБД дешевле, чем разбор значений уже в коде после получения выборки, и заметно сокращает объём обработки на стороне сервера приложений.

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

Чем язык запросов 1С отличается от SQL?
Ключевые слова русские, а источники данных — таблицы объектов конфигурации и виртуальные таблицы регистров, а не таблицы СУБД напрямую. Нет команд на изменение данных: запрос только читает. Записью занимаются объекты и наборы записей.
Можно ли запросом изменить или удалить данные?
Нет. UPDATE, DELETE и INSERT в языке запросов 1С отсутствуют намеренно. Запрос возвращает выборку, а изменения проводятся через объектную модель — запись объекта, проведение документа, запись набора записей регистра.
Почему отбор лучше ставить в параметры виртуальной таблицы, а не в ГДЕ?
Параметр фильтрует движения на этапе сборки таблицы, и платформа поднимает только нужные данные. Условие в ГДЕ применяется уже к собранной целиком таблице, то есть лишняя работа уже сделана.
Когда нужен менеджер временных таблиц?
Когда временная таблица из одного запроса пакета должна быть доступна в следующих запросах или в другом объекте Запрос. Без менеджера временная таблица живёт только внутри своего пакета и уничтожается после выполнения.
Как ускорить запрос, который читает из виртуальной таблицы и соединяется с ней?
Поместите результат виртуальной таблицы во временную таблицу, задайте ИНДЕКСИРОВАТЬ ПО по полям соединения и соединяйте уже с временной таблицей. Прямое соединение с виртуальной таблицей оптимизатор СУБД оценивает плохо.