Функции языка запросов: от агрегатов до условных выражений

· 9 мин

Содержание

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

Язык запросов — не 1С-код. Он не выполняется построчно и не умеет циклы. Запрос описывает, какие данные выбрать, а функции в нём лишь преобразуют поля прямо в выборке: свернуть суммы по группам, обрезать строку, вытащить год из даты. Поэтому и список функций ограничен тем, что умеет СУБД под капотом.

Ниже — рабочие конструкции с примерами на синтаксисе платформы 8.3. Пути в конфигураторе одинаковы для любой конфигурации: запрос пишут в модуле объекта или открывают консоль запросов через Все функции → Стандартные → Консоль запросов (внешнюю обработку подключают через Файл → Открыть).

Агрегатные функции: суммы, количество и экстремумы

Агрегатные функции сворачивают множество строк в одно значение. Их шесть:

  • 💰 СУММА(Поле) — сумма значений;
  • 🔢 КОЛИЧЕСТВО(Поле) — число строк, где поле заполнено;
  • 🔢 КОЛИЧЕСТВО(РАЗЛИЧНЫЕ Поле) — число уникальных значений;
  • ⬆️ МАКСИМУМ(Поле) и ⬇️ МИНИМУМ(Поле) — крайние значения;
  • ➗ СРЕДНЕЕ(Поле) — среднее арифметическое.

Работают они только вместе со СГРУППИРОВАТЬ ПО либо с секцией ИТОГИ. Если в выборке есть агрегатная функция, то каждое неагрегированное поле обязано попасть в СГРУППИРОВАТЬ ПО — иначе платформа выдаст ошибку о том, что поле не входит в группировку.

ВЫБРАТЬ
    Продажи.Контрагент КАК Контрагент,
    СУММА(Продажи.Сумма) КАК Оборот,
    КОЛИЧЕСТВО(РАЗЛИЧНЫЕ Продажи.Ссылка) КАК ЧислоДокументов,
    МАКСИМУМ(Продажи.Дата) КАК ПоследняяОтгрузка
ИЗ
    Документ.РеализацияТоваровУслуг КАК Продажи
СГРУППИРОВАТЬ ПО
    Продажи.Контрагент

КОЛИЧЕСТВО(РАЗЛИЧНЫЕ Ссылка) — честный способ посчитать число документов, когда в запросе развёрнута табличная часть. Обычный КОЛИЧЕСТВО посчитает строки товаров, а не документы.

СРЕДНЕЕ считает среднее только по заполненным строкам: пустые (NULL) оно пропускает, а не подставляет ноль. Если по смыслу пустое значение — это ноль, сначала закройте его через ЕСТЬNULL (см. раздел про условия), а уже потом усредняйте.

Работа с датами: период, компоненты и сдвиг

Дата-функции — самая востребованная группа, потому что отчёты почти всегда режутся по периодам. Ключевые функции:

ФункцияЧто делает
НАЧАЛОПЕРИОДА(Дата, Период)начало дня/месяца/квартала/года
КОНЕЦПЕРИОДА(Дата, Период)конец периода (до секунды)
ДОБАВИТЬКДАТЕ(Дата, Период, Кол)сдвиг на N периодов
РАЗНОСТЬДАТ(Дата1, Дата2, Период)разница в выбранных единицах
ГОД / КВАРТАЛ / МЕСЯЦ / ДЕНЬчисловой компонент даты

Вид периода пишется словом без кавычек: МЕСЯЦ, КВАРТАЛ, ГОД, ДЕНЬ, НЕДЕЛЯ, ПОЛУГОДИЕ. Группировка выручки по месяцам отгрузки выглядит так:

ВЫБРАТЬ
    НАЧАЛОПЕРИОДА(Продажи.Дата, МЕСЯЦ) КАК Месяц,
    СУММА(Продажи.Сумма) КАК Оборот
ИЗ
    Документ.РеализацияТоваровУслуг КАК Продажи
СГРУППИРОВАТЬ ПО
    НАЧАЛОПЕРИОДА(Продажи.Дата, МЕСЯЦ)
УПОРЯДОЧИТЬ ПО
    Месяц

РАЗНОСТЬДАТ удобна для расчёта просрочки или срока хранения:

РАЗНОСТЬДАТ(Заказ.Дата, &ТекущаяДата, ДЕНЬ) КАК ДнейПрошло

Фиксированную дату задают функцией ДАТАВРЕМЯ(Год, Месяц, День, Час, Минута, Секунда) — например, ДАТАВРЕМЯ(2024, 1, 1). Это единственный способ записать константную дату прямо в тексте запроса.

КОНЕЦПЕРИОДА(Дата, ДЕНЬ) возвращает не полночь, а 23:59:59. Поэтому для отбора «по конец месяца включительно» берут именно КОНЕЦПЕРИОДА, а не начало следующего месяца — иначе в выборку попадут документы, проведённые ровно в 00:00:00 первого числа.

Строковые функции: подстрока, регистр и обрезка пробелов

Со строками язык запросов работает скромно. Надёжно есть только эти функции:

  • ✂️ ПОДСТРОКА(Строка, Позиция, Длина) — кусок строки; позиция считается с 1;
  • 📏 СТРДЛИНА(Строка) — длина строки в символах;
  • 🔠 ВРЕГ(Строка) — в верхний регистр, НРЕГ — в нижний, ТРЕГ — первая буква каждого слова заглавная;
  • 🧹 СОКРЛП(Строка) — убрать пробелы слева и справа (СОКРЛ — только слева, СОКРП — справа).

Типичная задача — вытащить первые символы кода или артикула:

ВЫБРАТЬ
    Номенклатура.Наименование,
    ПОДСТРОКА(Номенклатура.Артикул, 1, 3) КАК ГруппаАртикула
ИЗ
    Справочник.Номенклатура КАК Номенклатура

Для надёжного сравнения строк приводите обе части к одному регистру через ВРЕГ, иначе «ООО Ромашка» и «ооо ромашка» будут считаться разными. А перед сравнением данных, пришедших из обмена или из текстового файла, оборачивайте поле в СОКРЛП — лишние пробелы по краям частая причина того, что совпадение «не находится».

Функций поиска и замены внутри строки (СтрНайти, СтрЗаменить) в языке запросов нет — они доступны только во встроенном языке. Если нужно разобрать строку по шаблону, выберите поле запросом как есть и обработайте результат уже в коде.

Условие ВЫБОР: ветвление прямо в выборке

Конструкция ВЫБОР — аналог CASE из SQL. Она возвращает разные значения в зависимости от условия, прямо в колонке выборки:

ВЫБОР
    КОГДА Заказ.Сумма > 100000 ТОГДА "Крупный"
    КОГДА Заказ.Сумма > 10000 ТОГДА "Средний"
    ИНАЧЕ "Мелкий"
КОНЕЦ КАК КатегорияЗаказа

Условия проверяются сверху вниз, срабатывает первое истинное. Ветка ИНАЧЕ необязательна: без неё при невыполнении всех условий поле получит NULL. Внутри КОГДА допустимы И, ИЛИ, НЕ, сравнения, В (...), МЕЖДУ, ПОДОБНО.

Отдельная, очень частая функция — ЕСТЬNULL(Выражение, ЗначениеЕслиNULL). Она подставляет второе значение, если первое оказалось NULL. Это спасение при левых соединениях, где часть полей не нашлась:

ЕСТЬNULL(Остатки.КоличествоОстаток, 0) КАК Остаток

Не путайте функцию ЕСТЬNULL(Поле, 0) и условие Поле ЕСТЬ NULL. Первая подставляет значение, второе — логическая проверка в секции ГДЕ. Арифметика с NULL всегда даёт NULL, поэтому Приход - Расход без ЕСТЬNULL по пустой стороне вернёт пусто, а не разницу.

Для подстановки предопределённых значений (элементов перечислений, пустых ссылок) используют литерал ЗНАЧЕНИЕ: ЗНАЧЕНИЕ(Перечисление.СтавкиНДС.НДС20) или ЗНАЧЕНИЕ(Справочник.Валюты.ПустаяСсылка). Это не функция в строгом смысле, но без неё не обойтись, когда в условие нужно подставить конкретный элемент.

Числа и приведение типа: ВЫРАЗИТЬ вместо округления

Здесь ждёт сюрприз: полноценных математических функций в языке запросов нет. Нет ни ОКРУГЛ, ни возведения в степень, ни корня. Доступны только арифметические операции +, -, *, / и управление разрядностью через ВЫРАЗИТЬ.

ВЫРАЗИТЬ(Выражение КАК Тип) приводит значение к нужному типу. Для чисел тип задаётся как ЧИСЛО(Длина, Точность) — это ограничивает число знаков после запятой:

ВЫРАЗИТЬ(Продажи.Сумма / Продажи.Количество КАК ЧИСЛО(15, 2)) КАК ЦенаЗаЕдиницу

Так цена получит ровно два знака после запятой. Если нужна гарантированная бухгалтерская округлённость по правилам (например, половина — вверх), делайте это уже во встроенном языке функцией Окр после выполнения запроса: ВЫРАЗИТЬ управляет разрядностью на уровне СУБД, и опираться на тонкости его округления не стоит.

Вторая, не менее важная роль ВЫРАЗИТЬ — разыменование полей составного типа. У поля Регистратор или СобытиеДокумент тип неопределённый, и обратиться к его реквизитам напрямую нельзя. Сначала приводим к конкретному типу:

ВЫРАЗИТЬ(Движения.Регистратор КАК Документ.РеализацияТоваровУслуг).Склад

Если фактический тип не совпал с указанным, вернётся NULL — это не ошибка, а штатное поведение, его удобно ловить через ЕСТЬNULL.

Проверьте себя
Какая функция вернёт первое число месяца по дате заказа?
Что сделает ЕСТЬNULL(Остатки.Количество, 0)?
Можно ли вызвать в запросе свою экспортную функцию из общего модуля?

Почему своих функций в запросе нет и чем их заменить

Самый частый вопрос новичка: как вызвать в запросе функцию из общего модуля? Ответ — никак. Язык запросов исполняется на стороне СУБД и знает только свой встроенный набор. Никакой ВЫЗВАТЬ, никакого обращения к вашему коду в тексте запроса не существует.

Это не ограничение, а разделение ролей. Запрос быстро достаёт и агрегирует данные, а прикладная логика живёт в коде. Поэтому заменяют «свою функцию» так:

  1. 🧮 Разложить вычисление на доступные операции. Сложная формула часто собирается из ВЫБОР, арифметики и ВЫРАЗИТЬ прямо в запросе.
  2. 🔁 Вынести расчёт в код. Выбираете исходные поля запросом, а итог считаете при обходе результата: Выборка.Следующий() и вызов любой своей функции.
  3. 📊 Использовать вычисляемые поля СКД. В системе компоновки данных на уровне ресурсов и пользовательских полей доступен ограниченный набор функций (ВычислитьВыражение и другие), которого хватает для расчётов в отчёте.
// обработка результата в коде — здесь функции уже доступны
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
    Итог = МояФункцияРасчёта(Выборка.Сумма, Выборка.Ставка);
КонецЦикла;

Если тянет написать функцию в запросе — почти всегда это сигнал, что часть логики пора перенести в код или в СКД. Запрос должен отдавать «сырьё», а не считать бизнес-правила.

Связка ВЫБОР и агрегатов: условная сумма

Самая мощная комбинация функций — ВЫБОР внутри СУММА. Она даёт то, чего в языке запросов нет готовой функцией: подсчёт суммы по условию (аналог СУММЕСЛИ в таблицах). Приём простой: внутри агрегата возвращаем либо значение, либо ноль.

ВЫБРАТЬ
    Продажи.Контрагент КАК Контрагент,
    СУММА(ВЫБОР
            КОГДА Продажи.Дата >= &НачалоКвартала
                ТОГДА Продажи.Сумма
            ИНАЧЕ 0
        КОНЕЦ) КАК ОборотКвартала,
    СУММА(Продажи.Сумма) КАК ОборотВсего
ИЗ
    Документ.РеализацияТоваровУслуг КАК Продажи
СГРУППИРОВАТЬ ПО
    Продажи.Контрагент

За один проход получаем и общий оборот, и оборот за квартал — без второго запроса и без соединений. Тем же приёмом разносят суммы по колонкам (приход в одной, расход в другой), считают количество документов определённого вида, собирают «шахматку».

Ветку ИНАЧЕ 0 в таком ВЫБОР пишите всегда. Без неё невыполнение условия даст NULL, а СУММА хоть и пропустит его, но при делении или вычитании результата такое пустое значение потом аукнется.

Второй частый приём — КОЛИЧЕСТВО с условием: СУММА(ВЫБОР КОГДА Продажи.Сумма > 100000 ТОГДА 1 ИНАЧЕ 0 КОНЕЦ) посчитает число крупных сделок. Здесь удобнее именно СУММА единиц, а не КОЛИЧЕСТВО: так в выборке остаётся ровно одна строка на контрагента.

Частые ошибки при работе с функциями

Ошибки с функциями почти всегда об одном — о NULL и о группировках.

  • ❌ Арифметика по пустому полю. Приход - Расход после левого соединения даёт NULL, если одна сторона не нашлась. Оборачивайте каждое слагаемое в ЕСТЬNULL(Поле, 0).
  • ❌ Поле вне группировки. Любое неагрегированное поле рядом с СУММА обязано быть в СГРУППИРОВАТЬ ПО. Иначе — ошибка выполнения.
  • ❌ Сравнение строк без нормализации. Разный регистр и пробелы по краям ломают соединения и отборы. Приводите обе части к ВРЕГ(СОКРЛП(...)).
  • ❌ Попытка округлить в запросе функцией. ОКРУГЛ в языке запросов нет. Разрядность — через ВЫРАЗИТЬ КАК ЧИСЛО(15, 2), точное округление — в коде.
  • ❌ Обращение к реквизиту поля составного типа без ВЫРАЗИТЬ. Регистратор, значение характеристики, владелец составного типа сначала приводят к конкретному типу.

Проверять такие вещи удобно в консоли запросов: выполняете запрос на реальной базе и сразу видите, где вместо числа появился NULL или где строк стало больше ожидаемого из-за развёрнутой табличной части.

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

Чем `КОЛИЧЕСТВО(*)` отличается от `КОЛИЧЕСТВО(Поле)`?
КОЛИЧЕСТВО(*) считает все строки группы, включая пустые. КОЛИЧЕСТВО(Поле) считает только строки, где это поле заполнено (не NULL). Для подсчёта уникальных документов берите КОЛИЧЕСТВО(РАЗЛИЧНЫЕ Ссылка).
Можно ли вложить одну функцию в другую?
Да. СУММА(ВЫРАЗИТЬ(Сумма КАК ЧИСЛО(15,2))) или НАЧАЛОПЕРИОДА(ДОБАВИТЬКДАТЕ(Дата, МЕСЯЦ, -1), МЕСЯЦ) — корректные конструкции. Ограничение одно: внутрь агрегатной функции нельзя вложить другую агрегатную.
Почему `ВЫБОР` возвращает `NULL`, хотя я задал условия?
Ни одно КОГДА не оказалось истинным, а ветки ИНАЧЕ нет. Добавьте ИНАЧЕ со значением по умолчанию либо проверьте, не сравнивается ли поле с NULL (такое сравнение всегда ложно).
Как получить номер месяца и год из даты по отдельности?
Функциями МЕСЯЦ(Дата) и ГОД(Дата) — они возвращают число. Для группировки по календарному месяцу удобнее НАЧАЛОПЕРИОДА(Дата, МЕСЯЦ): она сохраняет и год, и порядок сортировки.
Работают ли эти функции в запросах СКД?
Да, текст запроса в системе компоновки данных использует тот же язык и те же функции. Дополнительно на уровне ресурсов и пользовательских полей СКД появляются собственные выражения, которых в обычном запросе нет.