Сумма ресурса из регистра накопления в 1С 8.3: разбор запросов и виртуальных таблиц
Содержание
Сумму ресурса из регистра накопления получают запросом к виртуальной таблице: для остатка на дату — к таблице Остатки, для прихода-расхода за интервал — к таблице Обороты. Итоговое число сворачивают агрегатной функцией СУММА(), а разрезы задают конструкцией СГРУППИРОВАТЬ ПО. Прямое чтение реальной таблицы регистра для подсчёта суммы почти никогда не нужно и работает медленнее.
Разберём это на примере складского учёта: регистр хранит количество и стоимость товаров, а нам нужно то остаток на складе на конкретную дату, то оборот за месяц, то сумма по одной номенклатуре. От того, что именно нужно посчитать, зависит выбор виртуальной таблицы и суффикс поля ресурса. Ниже — рабочие тексты запросов, которые можно скопировать в консоль запросов и адаптировать под свой регистр.
Остаточные и оборотные регистры: что вообще можно суммировать
Регистр накопления бывает двух видов, и это первое, что надо определить. Откройте регистр в конфигураторе и посмотрите свойство Вид регистра:
- 📊 Остатки — хранит остатки (товары на складах, взаиморасчёты, деньги на счетах). Доступны три виртуальные таблицы:
Остатки,Обороты,ОстаткиИОбороты. - 🔄 Обороты — хранит только движение (продажи, выручка, начисления). Остатка у него нет по определению, поэтому доступна единственная виртуальная таблица —
Обороты.
Суммируем мы ресурсы — числовые поля регистра: Количество, Сумма, СуммаВзаиморасчетов. Измерения (Номенклатура, Склад, Контрагент) — это разрезы, по которым ресурсы раскладываются; по ним группируют, а не складывают.
Если регистр оборотный, а вам нужны остатки — считать нечего: остатки у него не хранятся. Либо берите обороты, либо задача решается другим регистром.
Остаток на дату через виртуальную таблицу Остатки
Виртуальная таблица Остатки возвращает остаток ресурса, уже свёрнутый на указанный момент времени. Каждый ресурс в ней получает суффикс Остаток: ресурс Количество превращается в поле КоличествоОстаток, ресурс Сумма — в СуммаОстаток.
Запрос, который считает суммарную стоимость товаров на складе на заданную дату:
ВЫБРАТЬ
СУММА(ТоварыОстатки.СуммаОстаток) КАК Стоимость,
СУММА(ТоварыОстатки.КоличествоОстаток) КАК Количество
ИЗ
РегистрНакопления.ТоварыНаСкладах.Остатки(&Дата, Склад = &Склад) КАК ТоварыОстатки
Обратите внимание на два момента:
- Отбор
Склад = &Складстоит внутри скобок виртуальной таблицы, а не в секцииГДЕ. Так платформа отфильтрует данные до расчёта остатков, а не после — это быстрее. - В условии виртуальной таблицы имя измерения пишут напрямую (
Склад), без псевдонима таблицы.
Если дату не передать (оставить первый параметр пустым), платформа возьмёт остатки на текущий момент. А чтобы получить остаток по каждой номенклатуре отдельно, добавьте группировку — об этом ниже.
Прочитать результат из кода:
Запрос = Новый Запрос;
Запрос.Текст = ТекстЗапроса;
Запрос.УстановитьПараметр("Дата", КонецДня(ТекущаяДата()));
Запрос.УстановитьПараметр("Склад", Склад);
Результат = Запрос.Выполнить();
Выборка = Результат.Выбрать();
Если Выборка.Следующий() Тогда
Стоимость = Выборка.Стоимость;
КонецЕсли;
Если на складе нет ни одного товара, СУММА() по пустой выборке вернёт NULL, а не ноль. Как с этим бороться — в разделе про нулевые значения.
Обороты, приход и расход за период
Когда нужна не картина на дату, а движение за интервал — сколько поступило и сколько ушло, — берут виртуальную таблицу Обороты. Её первые два параметра — начало и конец периода. Ресурсы получают уже три суффикса:
| Поле | Что показывает |
|---|---|
СуммаПриход | сумма всех приходных движений за период |
СуммаРасход | сумма всех расходных движений |
СуммаОборот | приход минус расход (чистое изменение) |
Запрос на оборот за месяц по одному складу:
ВЫБРАТЬ
СУММА(ТоварыОбороты.СуммаПриход) КАК Поступило,
СУММА(ТоварыОбороты.СуммаРасход) КАК Выбыло,
СУММА(ТоварыОбороты.СуммаОборот) КАК Изменение
ИЗ
РегистрНакопления.ТоварыНаСкладах.Обороты(
&НачалоПериода,
&КонецПериода,
,
Склад = &Склад) КАК ТоварыОбороты
Третий параметр здесь оставлен пустым — это периодичность. Если оставить пусто, обороты свернутся за весь интервал в одну строку по каждому разрезу. Если указать Месяц, День или Регистратор, таблица вернёт по строке на каждый период, и тогда появится дополнительное поле Период, по которому строят динамику.
Начало периода входит в интервал, конец — тоже. Чтобы захватить движения за весь последний день, передавайте в &КонецПериода не дату, а КонецДня(ДатаКонца) — иначе потеряете записи, сделанные в этот день после полуночи.
Когда нужна не одна цифра за квартал, а разбивка по месяцам, в третий параметр ставят периодичность. Тогда в таблице появляется поле Период, и каждый месяц возвращается своей строкой:
ВЫБРАТЬ
ТоварыОбороты.Период КАК Период,
СУММА(ТоварыОбороты.СуммаОборот) КАК Оборот
ИЗ
РегистрНакопления.ТоварыНаСкладах.Обороты(&Начало, &Конец, Месяц, ) КАК ТоварыОбороты
СГРУППИРОВАТЬ ПО
ТоварыОбороты.Период
УПОРЯДОЧИТЬ ПО
Период
Такой запрос — готовая основа для графика динамики: по оси времени идёт Период, по значению — сумма оборота. Доступные варианты периодичности — День, Неделя, Месяц, Квартал, Год, а также Регистратор (строка на каждый документ-движение).
Для оборотного регистра всё то же самое: таблица Обороты — единственная, что у него есть, и работает она одинаково для обоих видов регистров.
Функция СУММА и группировка по разрезам
Виртуальные таблицы уже сворачивают данные до нужного периода, но внутри строки могут остаться разбиты по измерениям. Чтобы получить одно итоговое число, убирают лишние поля и оборачивают ресурс в СУММА() без группировки:
ВЫБРАТЬ
СУММА(ТоварыОстатки.СуммаОстаток) КАК ОбщаяСтоимость
ИЗ
РегистрНакопления.ТоварыНаСкладах.Остатки(&Дата, ) КАК ТоварыОстатки
Чтобы получить сумму в разрезе — по каждому складу или каждой номенклатуре, — ресурс оставляют под СУММА(), а разрез выносят в СГРУППИРОВАТЬ ПО:
ВЫБРАТЬ
ТоварыОстатки.Номенклатура КАК Номенклатура,
СУММА(ТоварыОстатки.СуммаОстаток) КАК Стоимость
ИЗ
РегистрНакопления.ТоварыНаСкладах.Остатки(&Дата, ) КАК ТоварыОстатки
СГРУППИРОВАТЬ ПО
ТоварыОстатки.Номенклатура
УПОРЯДОЧИТЬ ПО
Стоимость УБЫВ
Правило простое: каждое поле в списке выборки должно быть либо под агрегатной функцией, либо в СГРУППИРОВАТЬ ПО. Забыли вынести измерение в группировку — получите ошибку синтаксиса или свёрнутый не так результат.
Отобрать только позиции с остатком больше заданного помогает ИМЕЮЩИЕ — это фильтр по результату агрегатной функции, который ГДЕ поставить не может:
...
СГРУППИРОВАТЬ ПО
ТоварыОстатки.Номенклатура
ИМЕЮЩИЕ
СУММА(ТоварыОстатки.КоличествоОстаток) > 0
Какую виртуальную таблицу выбрать под конкретную задачу:
Остатки и обороты одним запросом
Если нужно показать и остаток на начало периода, и движение внутри, и остаток на конец — не делайте два отдельных запроса. Для регистров вида «Остатки» есть виртуальная таблица ОстаткиИОбороты, которая отдаёт всё сразу. Её ресурсы получают сразу четыре суффикса: НачальныйОстаток, Приход, Расход, КонечныйОстаток.
ВЫБРАТЬ
ТоварыОстаткиИОбороты.Склад КАК Склад,
СУММА(ТоварыОстаткиИОбороты.СуммаНачальныйОстаток) КАК НаНачало,
СУММА(ТоварыОстаткиИОбороты.СуммаПриход) КАК Приход,
СУММА(ТоварыОстаткиИОбороты.СуммаРасход) КАК Расход,
СУММА(ТоварыОстаткиИОбороты.СуммаКонечныйОстаток) КАК НаКонец
ИЗ
РегистрНакопления.ТоварыНаСкладах.ОстаткиИОбороты(
&НачалоПериода,
&КонецПериода) КАК ТоварыОстаткиИОбороты
СГРУППИРОВАТЬ ПО
ТоварыОстаткиИОбороты.Склад
Такой запрос — основа почти любой ведомости по регистру: колонки «на начало», «приход», «расход», «на конец» получаются из одной выборки. Параметр метода дополнения (Движения, ДвиженияИГраницыПериода) влияет на то, будут ли в результате строки за периоды без движения; по умолчанию достаточно оставить его пустым.
Как ускорить запрос к регистру накопления
На маленькой базе разница незаметна, но на регистре с миллионами записей неудачный запрос считается минутами. Что реально ускоряет выборку суммы:
- 🎯 Отбор — в параметрах виртуальной таблицы, а не в
ГДЕ. УсловиеСклад = &Складвнутри скобок таблицы платформа применит до свёртки остатков. То же условие в секцииГДЕотработает уже по готовому результату — медленнее и с лишними данными. - 🚫 Не читайте реальную таблицу
РегистрНакопления.ТоварыНаСкладахради суммы остатка. Виртуальная таблица использует итоги регистра и не перебирает все движения с начала времён. - 🔗 Не разыменовывайте поля через точку в условиях. Отбор вида
ГДЕ Номенклатура.Родитель = &Группазаставляет платформу неявно соединять таблицы. Если фильтруете по группе часто — передавайте в запрос заранее собранный список элементов. - 📉 Выбирайте только нужные ресурсы и разрезы. Лишнее измерение в группировке — это лишняя детализация и лишняя работа.
Проверить, во что развернулся запрос и где он тормозит, помогает технологический журнал и замер в консоли запросов. Но в 90% случаев достаточно одного правила — перенести отбор внутрь виртуальной таблицы.
NULL, нулевые остатки и свёрнутые строки
Три ситуации, на которых чаще всего ошибаются при подсчёте суммы.
СУММА() по пустой выборке возвращает NULL. Если по отбору не нашлось ни одной записи, итог будет не ноль, а неопределённое значение, и дальнейшая арифметика с ним даст NULL. Оборачивайте результат в ЕСТЬNULL:
ВЫБРАТЬ
ЕСТЬNULL(СУММА(ТоварыОстатки.СуммаОстаток), 0) КАК Стоимость
ИЗ
РегистрНакопления.ТоварыНаСкладах.Остатки(&Дата, ) КАК ТоварыОстатки
Нулевого остатка в виртуальной таблице Остатки нет. Если товар полностью израсходован, строки по нему в таблице Остатки просто не будет — а не строка с нулём. Поэтому список «всех номенклатур с их остатками, включая нулевые» строят не от регистра, а от справочника номенклатуры с левым соединением к остаткам, и уже там ЕСТЬNULL(...) превращает отсутствие в ноль.
Остаток может быть отрицательным. Если в базе разрешено списание в минус, сумма остатка бывает меньше нуля. Это не ошибка запроса — это состояние учёта; учитывайте знак, когда проверяете «хватает ли товара».
Строки могут задвоиться без группировки. Виртуальная таблица без периодичности и без группировки всё равно разбивает данные по измерениям. Если вы ждёте одно число, а получаете несколько строк — значит, в выборке остались разрезы, которые надо либо убрать, либо свернуть через СУММА() + СГРУППИРОВАТЬ ПО.
Готовые примеры и типичные ошибки
Короткие шаблоны под частые задачи — подставьте имя своего регистра и ресурса.
Остаток по одной номенклатуре на сейчас:
ВЫБРАТЬ ЕСТЬNULL(СУММА(Т.КоличествоОстаток), 0) КАК Кол
ИЗ РегистрНакопления.ТоварыНаСкладах.Остатки(, Номенклатура = &Номенклатура) КАК Т
Выручка за период из оборотного регистра продаж:
ВЫБРАТЬ ЕСТЬNULL(СУММА(П.ВыручкаОборот), 0) КАК Выручка
ИЗ РегистрНакопления.Продажи.Обороты(&Начало, &Конец, ) КАК П
Чего избегать — ошибки, которые повторяются чаще всего:
- ❌ Суффикс ресурса не тот. В
Остатки— только...Остаток, вОбороты—...Приход/Расход/Оборот. ПоляСуммаОстатокв таблицеОборотыне существует, запрос выдаст ошибку поля. - ❌ Отбор в
ГДЕвместо параметров таблицы. Работает, но медленно и иногда неверно: после свёртки часть данных уже потеряна. - ❌ Сравнение остатка с
NULLвместо нуля. ПослеСУММА()безЕСТЬNULLлюбое> 0по пустой выборке даётЛОЖЬнеожиданно. - ❌ Обороты у остаточного регистра путают с остатком.
СуммаОборот— это изменение за период, а не остаток на конец. Остаток берут изОстаткиилиКонечныйОстатоктаблицыОстаткиИОбороты. - ❌ Чтение реальной таблицы регистра для итогов. На больших объёмах это главный источник тормозов.
Частые вопросы
Чем виртуальная таблица отличается от реальной таблицы регистра?
Остатки, Обороты, ОстаткиИОбороты) рассчитывается платформой на лету из итогов регистра и сразу отдаёт свёрнутый результат. Для подсчёта суммы берут виртуальную — она быстрее и не требует ручной свёртки.Почему `СУММА` по регистру возвращает NULL, а не 0?
NULL. Оберните результат в ЕСТЬNULL(СУММА(...), 0), и вместо неопределённого значения получите ноль.Как получить остаток на дату, а не на текущий момент?
Остатки: Остатки(&Дата, ). Чтобы захватить все движения нужного дня, передавайте КонецДня(Дата) — иначе остаток посчитается на начало суток.Можно ли посчитать остаток у оборотного регистра?
Обороты. Если остатки нужны — их ведёт другой, остаточный регистр.Как вывести сумму сразу по всем складам в разрезе номенклатуры?
СУММА(), а поля Номенклатура и Склад вынесите в СГРУППИРОВАТЬ ПО. Если склад не нужен как разрез — просто не включайте его ни в выборку, ни в группировку, и суммы свернутся по всем складам сразу.