Функции языка запросов: от агрегатов до условных выражений
Содержание
Набор функций в языке запросов 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.
Почему своих функций в запросе нет и чем их заменить
Самый частый вопрос новичка: как вызвать в запросе функцию из общего модуля? Ответ — никак. Язык запросов исполняется на стороне СУБД и знает только свой встроенный набор. Никакой ВЫЗВАТЬ, никакого обращения к вашему коду в тексте запроса не существует.
Это не ограничение, а разделение ролей. Запрос быстро достаёт и агрегирует данные, а прикладная логика живёт в коде. Поэтому заменяют «свою функцию» так:
- 🧮 Разложить вычисление на доступные операции. Сложная формула часто собирается из
ВЫБОР, арифметики иВЫРАЗИТЬпрямо в запросе. - 🔁 Вынести расчёт в код. Выбираете исходные поля запросом, а итог считаете при обходе результата:
Выборка.Следующий()и вызов любой своей функции. - 📊 Использовать вычисляемые поля СКД. В системе компоновки данных на уровне ресурсов и пользовательских полей доступен ограниченный набор функций (
ВычислитьВыражениеи другие), которого хватает для расчётов в отчёте.
// обработка результата в коде — здесь функции уже доступны
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
Итог = МояФункцияРасчёта(Выборка.Сумма, Выборка.Ставка);
КонецЦикла;
Если тянет написать функцию в запросе — почти всегда это сигнал, что часть логики пора перенести в код или в СКД. Запрос должен отдавать «сырьё», а не считать бизнес-правила.
Связка ВЫБОР и агрегатов: условная сумма
Самая мощная комбинация функций — ВЫБОР внутри СУММА. Она даёт то, чего в языке запросов нет готовой функцией: подсчёт суммы по условию (аналог СУММЕСЛИ в таблицах). Приём простой: внутри агрегата возвращаем либо значение, либо ноль.
ВЫБРАТЬ
Продажи.Контрагент КАК Контрагент,
СУММА(ВЫБОР
КОГДА Продажи.Дата >= &НачалоКвартала
ТОГДА Продажи.Сумма
ИНАЧЕ 0
КОНЕЦ) КАК ОборотКвартала,
СУММА(Продажи.Сумма) КАК ОборотВсего
ИЗ
Документ.РеализацияТоваровУслуг КАК Продажи
СГРУППИРОВАТЬ ПО
Продажи.Контрагент
За один проход получаем и общий оборот, и оборот за квартал — без второго запроса и без соединений. Тем же приёмом разносят суммы по колонкам (приход в одной, расход в другой), считают количество документов определённого вида, собирают «шахматку».
Ветку ИНАЧЕ 0 в таком ВЫБОР пишите всегда. Без неё невыполнение условия даст NULL, а СУММА хоть и пропустит его, но при делении или вычитании результата такое пустое значение потом аукнется.
Второй частый приём — КОЛИЧЕСТВО с условием: СУММА(ВЫБОР КОГДА Продажи.Сумма > 100000 ТОГДА 1 ИНАЧЕ 0 КОНЕЦ) посчитает число крупных сделок. Здесь удобнее именно СУММА единиц, а не КОЛИЧЕСТВО: так в выборке остаётся ровно одна строка на контрагента.
Частые ошибки при работе с функциями
Ошибки с функциями почти всегда об одном — о NULL и о группировках.
- ❌ Арифметика по пустому полю.
Приход - Расходпосле левого соединения даётNULL, если одна сторона не нашлась. Оборачивайте каждое слагаемое вЕСТЬNULL(Поле, 0). - ❌ Поле вне группировки. Любое неагрегированное поле рядом с
СУММАобязано быть вСГРУППИРОВАТЬ ПО. Иначе — ошибка выполнения. - ❌ Сравнение строк без нормализации. Разный регистр и пробелы по краям ломают соединения и отборы. Приводите обе части к
ВРЕГ(СОКРЛП(...)). - ❌ Попытка округлить в запросе функцией.
ОКРУГЛв языке запросов нет. Разрядность — черезВЫРАЗИТЬ КАК ЧИСЛО(15, 2), точное округление — в коде. - ❌ Обращение к реквизиту поля составного типа без
ВЫРАЗИТЬ. Регистратор, значение характеристики, владелец составного типа сначала приводят к конкретному типу.
Проверять такие вещи удобно в консоли запросов: выполняете запрос на реальной базе и сразу видите, где вместо числа появился NULL или где строк стало больше ожидаемого из-за развёрнутой табличной части.
Частые вопросы
Чем `КОЛИЧЕСТВО(*)` отличается от `КОЛИЧЕСТВО(Поле)`?
КОЛИЧЕСТВО(*) считает все строки группы, включая пустые. КОЛИЧЕСТВО(Поле) считает только строки, где это поле заполнено (не NULL). Для подсчёта уникальных документов берите КОЛИЧЕСТВО(РАЗЛИЧНЫЕ Ссылка).Можно ли вложить одну функцию в другую?
СУММА(ВЫРАЗИТЬ(Сумма КАК ЧИСЛО(15,2))) или НАЧАЛОПЕРИОДА(ДОБАВИТЬКДАТЕ(Дата, МЕСЯЦ, -1), МЕСЯЦ) — корректные конструкции. Ограничение одно: внутрь агрегатной функции нельзя вложить другую агрегатную.Почему `ВЫБОР` возвращает `NULL`, хотя я задал условия?
КОГДА не оказалось истинным, а ветки ИНАЧЕ нет. Добавьте ИНАЧЕ со значением по умолчанию либо проверьте, не сравнивается ли поле с NULL (такое сравнение всегда ложно).Как получить номер месяца и год из даты по отдельности?
МЕСЯЦ(Дата) и ГОД(Дата) — они возвращают число. Для группировки по календарному месяцу удобнее НАЧАЛОПЕРИОДА(Дата, МЕСЯЦ): она сохраняет и год, и порядок сортировки.