Как перемножить поля в запросе 1С и не получить NULL, дубли и лишние знаки

· 11 мин

Содержание

Чтобы умножить значения в запросе 1С, поставьте между полями оператор *: Количество * Цена КАК Сумма. Если хотя бы одно поле может оказаться пустым после соединения, оберните его в ЕСТЬNULL(Поле, 0), иначе вся строка даст NULL.

Сам синтаксис занимает одну строку. Сложности начинаются дальше: сумма по группе не сходится с документами, после соединения итог вырос вдвое, в результате 0,3333333333 вместо копеек. Каждый такой случай решается одним-двумя приёмами, и код для них можно сразу проверить в консоли запросов.

Оператор * в языке запросов: что можно перемножать

Язык запросов 1С поддерживает четыре арифметических оператора: +, -, *, /. Приоритет обычный: умножение и деление выполняются раньше сложения, скобки меняют порядок.

ВЫБРАТЬ
    Товары.Номенклатура КАК Номенклатура,
    Товары.Количество КАК Количество,
    Товары.Цена КАК Цена,
    Товары.Количество * Товары.Цена КАК Сумма,
    Товары.Количество * Товары.Цена * 1.2 КАК СуммаСНаценкой
ИЗ
    Документ.РеализацияТоваровУслуг.Товары КАК Товары
ГДЕ
    Товары.Ссылка = &Документ

Что умножать можно:

  • 🔢 два числовых поля одной таблицы или разных таблиц соединения;
  • 📌 поле и числовую константу — дробная часть пишется через точку: * 1.2, а не * 1,2;
  • 🧩 поле и параметр: Товары.Сумма * &Коэффициент, параметр передаётся из кода через Запрос.УстановитьПараметр("Коэффициент", 0.9);
  • 🔗 поле, полученное через точку от ссылки: Товары.Количество * Товары.Номенклатура.Вес (платформа сама добавит соединение со справочником).

Что умножать нельзя:

  • ❌ строки, даты и ссылки — запрос не выполнится, платформа сообщит о недопустимом типе операнда;
  • ❌ булево поле напрямую — сначала превратите его в число через ВЫБОР;
  • ❌ поле составного типа, где кроме числа есть строка или ссылка, — сначала приведите его к числу через ВЫРАЗИТЬ.

Псевдоним после КАК обязателен для вычисляемого поля, если вы потом обращаетесь к нему по имени в коде: Выборка.Сумма. Без псевдонима платформа назовёт колонку сама, обычно Поле1.

Умножение в конструкторе запросов

Писать выражение руками не обязательно. В конструкторе на вкладке «Таблицы и поля» добавьте новое поле в список «Поля» — откроется редактор произвольного выражения. Перетащите туда нужные поля из дерева таблиц и поставьте между ними *. Имя колонки задаётся на вкладке «Объединения/Псевдонимы».

Пустые значения и точность результата

Две проблемы встречаются чаще всего: NULL после соединения и «хвост» из дробных знаков.

Почему произведение стало NULL

Любая арифметика с NULL даёт NULL. 5 * NULL — это не 0 и не 5, а NULL. Такое значение появляется, когда вы соединяете таблицы через ЛЕВОЕ СОЕДИНЕНИЕ, а во второй таблице нет пары для строки первой.

Типичный пример — остатки умножаются на цену, а у части товаров цена не установлена:

ВЫБРАТЬ
    Остатки.Номенклатура КАК Номенклатура,
    Остатки.КоличествоОстаток КАК Количество,
    ЕСТЬNULL(Цены.Цена, 0) КАК Цена,
    Остатки.КоличествоОстаток * ЕСТЬNULL(Цены.Цена, 0) КАК Стоимость
ИЗ
    ВТОстатки КАК Остатки
        ЛЕВОЕ СОЕДИНЕНИЕ ВТЦены КАК Цены
        ПО Остатки.Номенклатура = Цены.Номенклатура

Без ЕСТЬNULL товары без цены дадут пустую стоимость, а в итоговой сумме по группе они просто выпадут: агрегатная функция СУММА пропускает NULL. Ошибки не будет — будет неверная цифра.

Решите заранее, что значит «нет цены». Ноль скрывает проблему: товар попадёт в отчёт со стоимостью 0. Если важно увидеть такие позиции, выводите Цены.Цена без ЕСТЬNULL отдельной колонкой и фильтруйте ГДЕ Цены.Цена ЕСТЬ NULL.

Как ограничить количество знаков

При умножении и особенно делении результат может получить больше знаков после запятой, чем нужно. Для денег это неудобно: суммы в таблице значений не совпадают с документами на копейку.

Приведите результат к нужному типу:

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

ЧИСЛО(15, 2) — 15 знаков всего, 2 после запятой. Лишние разряды отбрасываются при приведении. В актуальных версиях платформы 8.3 в языке запросов есть и функция ОКР(Значение, Разрядность) для арифметического округления — используйте её, если важен именно математический результат, а не обрезка.

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

Произведение и группировка: СУММА(Количество * Цена) против СУММА(Количество) * СУММА(Цена)

Здесь кроется самая дорогая ошибка. Порядок «сначала умножить, потом сложить» и «сначала сложить, потом умножить» даёт разные цифры.

Пример. Две строки по одному товару:

СтрокаКоличествоЦенаКоличество × Цена
12100200
23150450
Итого5—650
  • ✅ СУММА(Количество * Цена) = 200 + 450 = 650 — правильная сумма.
  • ❌ СУММА(Количество) * СУММА(Цена) = 5 × 250 = 1250 — бессмыслица.
  • ❌ СУММА(Количество) * МАКСИМУМ(Цена) = 5 × 150 = 750 — тоже неверно, если цены разные.

Правильный запрос с группировкой:

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

Умножение после агрегата уместно, только когда второй множитель одинаков для всей группы. Например, суммарный вес = СУММА(Количество) * Номенклатура.Вес, если группируете по номенклатуре. Тогда вес нужно добавить и в СГРУППИРОВАТЬ ПО, иначе запрос не выполнится.

Средняя цена из произведения

Обратная задача — средневзвешенная цена. Её считают делением суммы произведений на сумму количества, а не функцией СРЕДНЕЕ(Цена):

ВЫБОР
    КОГДА СУММА(Товары.Количество) = 0
        ТОГДА 0
    ИНАЧЕ СУММА(Товары.Количество * Товары.Цена) / СУММА(Товары.Количество)
КОНЕЦ КАК СредняяЦена

ВЫБОР здесь защищает от деления на ноль — без него запрос на строках с нулевым количеством может завершиться ошибкой.

Отбор по произведению: ГДЕ и ИМЕЮЩИЕ

Псевдоним вычисляемого поля нельзя использовать в ГДЕ и ИМЕЮЩИЕ — выражение придётся повторить:

ГДЕ
    Товары.Количество * Товары.Цена > 10000
СГРУППИРОВАТЬ ПО
    Товары.Номенклатура
ИМЕЮЩИЕ
    СУММА(Товары.Количество * Товары.Цена) > 100000

В УПОРЯДОЧИТЬ ПО псевдоним работает: УПОРЯДОЧИТЬ ПО Сумма УБЫВ.

Умножение по условию через ВЫБОР

Конструкция ВЫБОР КОГДА … ТОГДА … ИНАЧЕ … КОНЕЦ позволяет менять множитель в зависимости от данных. Три частых сценария.

Разные коэффициенты для разных групп

Товары.Сумма *
    ВЫБОР
        КОГДА Товары.Номенклатура.Услуга
            ТОГДА 1
        КОГДА Товары.Количество >= 100
            ТОГДА 0.9
        ИНАЧЕ 0.95
    КОНЕЦ КАК СуммаСоСкидкой

Условия проверяются сверху вниз, срабатывает первое истинное. Поэтому порядок КОГДА важен: если поставить проверку количества первой, услуги тоже получат скидку.

Знак движения: приход плюсом, расход минусом

В физической таблице регистра накопления у каждой записи есть поле ВидДвижения. Чтобы получить сальдо одной суммой, умножьте количество на +1 или −1:

СУММА(
    Движения.Количество *
    ВЫБОР
        КОГДА Движения.ВидДвижения = ЗНАЧЕНИЕ(ВидДвиженияНакопления.Приход)
            ТОГДА 1
        ИНАЧЕ -1
    КОНЕЦ
) КАК Изменение

Булево поле как множитель

Булево нельзя умножить на число. Превратите его в 0 или 1:

Товары.Сумма *
    ВЫБОР КОГДА Товары.Ссылка.Проведен ТОГДА 1 ИНАЧЕ 0 КОНЕЦ КАК СуммаПроведенных

Если нужно только отсечь строки, удобнее условие в ГДЕ. Множитель 0/1 полезен, когда в одном запросе надо вывести и общую сумму, и сумму по признаку, не теряя строк.

Перемножение данных виртуальных и временных таблиц

Реальные задачи редко помещаются в одну таблицу. Количество берётся из остатков, цена — из регистра цен, коэффициент — из справочника. Удобнее всего разложить запрос на шаги через временные таблицы.

Остатки × цены из среза последних

Имена регистров ниже условные: в вашей конфигурации регистр остатков и регистр цен могут называться иначе. Логика от этого не меняется.

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

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    Цены.Номенклатура КАК Номенклатура,
    Цены.Цена КАК Цена
ПОМЕСТИТЬ ВТЦены
ИЗ
    РегистрСведений.ЦеныНоменклатуры.СрезПоследних(
        &Дата,
        ТипЦен = &ТипЦен
            И Номенклатура В (ВЫБРАТЬ ВТОстатки.Номенклатура ИЗ ВТОстатки КАК ВТОстатки)
    ) КАК Цены
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    ВТОстатки.Номенклатура КАК Номенклатура,
    ВТОстатки.Количество КАК Количество,
    ЕСТЬNULL(ВТЦены.Цена, 0) КАК Цена,
    ВЫРАЗИТЬ(ВТОстатки.Количество * ЕСТЬNULL(ВТЦены.Цена, 0) КАК ЧИСЛО(15, 2)) КАК Стоимость
ИЗ
    ВТОстатки КАК ВТОстатки
        ЛЕВОЕ СОЕДИНЕНИЕ ВТЦены КАК ВТЦены
        ПО ВТОстатки.Номенклатура = ВТЦены.Номенклатура

Здесь два приёма, которые часто упускают:

  1. Отборы Склад = &Склад и ТипЦен = &ТипЦен стоят в параметрах виртуальной таблицы, а не в ГДЕ. Так платформа отбирает данные до расчёта остатков и среза, а не после.
  2. Срез цен ограничен только теми товарами, которые есть в остатках. Без этого платформа построит срез по всему справочнику номенклатуры.

Почему итог вырос вдвое после соединения

Если во второй таблице на одну номенклатуру приходится несколько строк, соединение размножит строки первой. Произведение Количество * Цена посчитается для каждой копии, и СУММА сложит их все.

Пример: в регистре цен по товару две записи (разные типы цен), вы забыли отбор по типу цен. Остаток 10 штук умножится и на розничную, и на закупочную цену, а сумма по складу удвоится.

Как проверить: выполните запрос без группировки и посмотрите, нет ли повторяющихся строк по ключу соединения. Лечится условием отбора в соединении или предварительной группировкой второй таблицы во временной.

Произведение в итогах запроса

Если запрос строит иерархический отчёт через ИТОГИ, итог по вычисляемому полю задаётся по его псевдониму:

ИТОГИ
    СУММА(Количество),
    СУММА(Стоимость)
ПО
    ОБЩИЕ,
    Номенклатура ИЕРАРХИЯ

В ИТОГИ уже стоит готовое произведение из каждой строки, поэтому сумма считается правильно — по принципу «сначала умножить, потом сложить».

Скорость запросов с вычислениями

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

  • 🐢 Выражение в условии. ГДЕ Количество * Цена > 10000 не использует индекс: СУБД вынуждена посчитать произведение для каждой строки. На небольших табличных частях это незаметно, на регистре с миллионами записей — заметно. Сначала сузьте выборку по индексируемым полям (период, ссылка, измерения), а условие по произведению добавьте следом.
  • 🐢 Разыменование в произведении. Количество * Номенклатура.Вес добавляет неявное соединение со справочником. Если номенклатура составного типа, соединений будет несколько. Выносите такие поля в отдельную временную таблицу.
  • 🐢 Подзапрос в соединении. Цены, вложенные подзапросом прямо в ЛЕВОЕ СОЕДИНЕНИЕ, оптимизатор СУБД оценивает хуже, чем временную таблицу. Пакет с ПОМЕСТИТЬ обычно работает быстрее и легче отлаживается.
  • ⚡ Вычисление в запросе, а не в цикле. Перемножать в коде после выборки — значит гонять лишние данные и считать на сервере приложений построчно. Если результат нужен в запросе (для отбора, группировки, итога), считайте его в запросе.

Для временных таблиц, которые потом соединяются по номенклатуре, добавьте ИНДЕКСИРОВАТЬ ПО Номенклатура после ПОМЕСТИТЬ. На больших объёмах это ускоряет соединение.

Готовые примеры под частые задачи

Сумма НДС от суммы без налога

Ставку удобно хранить в параметре, а не зашивать в текст:

ВЫБРАТЬ
    Товары.Номенклатура КАК Номенклатура,
    Товары.Сумма КАК СуммаБезНДС,
    ВЫРАЗИТЬ(Товары.Сумма * &СтавкаНДС / 100 КАК ЧИСЛО(15, 2)) КАК СуммаНДС
ИЗ
    Документ.ПоступлениеТоваровУслуг.Товары КАК Товары
ГДЕ
    Товары.Ссылка = &Документ

Проверка арифметики: сумма 12 345,67 × 20 / 100 = 2 469,134 → после приведения к двум знакам 2 469,13.

Стоимость по дням: разность дат × ставка

Дату умножить нельзя, но можно умножить количество дней между датами:

РАЗНОСТЬДАТ(Аренда.ДатаНачала, Аренда.ДатаОкончания, ДЕНЬ) * Аренда.СтавкаВДень КАК СуммаАренды

С 1 по 31 марта РАЗНОСТЬДАТ вернёт 30, а не 31: функция считает разницу, а не число дней включительно. Если нужен включительный счёт, прибавьте единицу: (РАЗНОСТЬДАТ(...) + 1) * СтавкаВДень.

Вес отгрузки в килограммах

ВЫБРАТЬ
    Товары.Ссылка КАК Документ,
    СУММА(Товары.Количество * ЕСТЬNULL(Товары.Номенклатура.Вес, 0)) КАК ВесКг
ИЗ
    Документ.РеализацияТоваровУслуг.Товары КАК Товары
ГДЕ
    Товары.Ссылка В (&СписокДокументов)
СГРУППИРОВАТЬ ПО
    Товары.Ссылка

Реквизит Вес у номенклатуры есть не в каждой конфигурации и может называться иначе. Проверьте его имя в конфигураторе, в дереве справочника.

Перевод в валюту по курсу

Остатки.СуммаОстаток * ЕСТЬNULL(Курсы.Курс, 1) / ЕСТЬNULL(Курсы.Кратность, 1) КАК СуммаВРублях

Кратность обязательна: для валют, где курс задан за 10 или 100 единиц, без деления на неё сумма окажется в 10 или 100 раз больше.

Ошибки при умножении: симптом и причина

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

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

Как возвести число в квадрат в запросе 1С?
Оператора степени в языке запросов нет. Умножьте поле само на себя: Поле * Поле.
Можно ли умножить поле на значение из другого запроса пакета?
Да. Поместите значение во временную таблицу и соедините её с основной, например через ВНУТРЕННЕЕ СОЕДИНЕНИЕ по условию ИСТИНА, если в ней одна строка.
Где считать сумму строки — в запросе или в коде?
Если сумма нужна для отбора, группировки, сортировки или итогов — в запросе. Если она нужна только для вывода одной строки на форму — разницы почти нет.
Почему в СКД ресурс считается иначе, чем в запросе?
В системе компоновки выражение ресурса задаётся отдельно. Если указать в нём Сумма(Количество) * Цена, получится умножение после агрегата. Используйте Сумма(Стоимость), где Стоимость уже посчитана в запросе.
Можно ли умножать в условии соединения?
Можно, синтаксис это допускает: ПО Т1.Сумма = Т2.Количество * Т2.Цена. Но индекс по такому условию не работает, на больших таблицах соединение будет медленным.