При разработке отчетов по складскому учету часто возникает задача исключить внутренние перемещения между складами, чтобы увидеть только чистые внешние приходы (поступления от поставщиков) и расходы (отгрузки клиентам). Если строить запрос к стандартной виртуальной таблице ОстаткиИОбороты регистра накопления ТоварыНаСкладах, движения по документу ПеремещениеТоваров по умолчанию попадают в приход одного склада и расход другого.
Разберем по шагам, как правильно составить условие по типу документа в запросе, с какими математическими особенностями виртуальных таблиц нам предстоит столкнуться и какие подходы позволяют решить задачу корректно.
Проанализируем распространенную ошибку при попытке отфильтровать тип документа. В языке запросов 1С конструкции вида ТИПЗНАЧЕНИЯ(...) = ТипЗнч(...) не применяются для проверки конкретного вида документа. Для проверки принадлежности составного ссылочного поля (такого как Регистратор) определенному метаданному объекту используется логический оператор ССЫЛКА.
Посмотрим на корректный синтаксис исключения документа:
НЕ Регистратор ССЫЛКА Документ.ПеремещениеТоваров
Также допустим альтернативный вариант с использованием функций работы с типами:
НЕ ТИПЗНАЧЕНИЯ(Регистратор) = ТИП(Документ.ПеремещениеТоваров)
Однако первый вариант с оператором ССЫЛКА является более компактным, общепринятым и оптимизированным стандартом разработки в среде 1С:Предприятие.
Выясним причину, по которой обычное наложение фильтра по регистратору на виртуальную таблицу ОстаткиИОбороты приводит к ошибочным данным в отчете. Виртуальная таблица рассчитывает остатки и обороты в строгом математическом соответствии:
Конечный остаток = Начальный остаток + Приход - Расход
Если мы укажем периодичность Регистратор и исключим документ ПеремещениеТоваров в параметрах таблицы или в секции ГДЕ, платформа полностью проигнорирует движения перемещения при подсчете остатка. В результате конечный остаток в отчете перестанет совпадать с реальным фактическим остатком товара на складе на дату окончания отчета.
Рассмотрим конкретный пример:
Таким образом, если нам требуются реальные начальные и конечные остатки на складах, но при этом «чистые» обороты без перемещений, использовать простой отбор по регистратору в таблице ОстаткиИОбороты нельзя.
Рассмотрим решение, при котором мы получаем полную таблицу остатков и оборотов с периодичностью Регистратор, но не накладываем отбор на саму таблицу. Вместо этого мы очищаем обороты с помощью условного выражения ВЫБОР непосредственно в полях выборки, а затем группируем результат.
Разберем пример реализации:
ВЫБРАТЬ
ВложенныйЗапрос.Склад КАК Склад,
ВложенныйЗапрос.Номенклатура КАК Номенклатура,
ВложенныйЗапрос.КоличествоНачальныйОстаток КАК НачальныйОстаток,
СУММА(ВложенныйЗапрос.ПриходБезПеремещений) КАК Приход,
СУММА(ВложенныйЗапрос.РасходБезПеремещений) КАК Расход,
ВложенныйЗапрос.КоличествоКонечныйОстаток КАК КонечныйОстаток
ИЗ
(ВЫБРАТЬ
Товары.Склад КАК Склад,
Товары.Номенклатура КАК Номенклатура,
Товары.КоличествоНачальныйОстаток КАК КоличествоНачальныйОстаток,
ВЫБОР
КОГДА НЕ Товары.Регистратор ССЫЛКА Документ.ПеремещениеТоваров
ТОГДА Товары.КоличествоПриход
ИНАЧЕ 0
КОНЕЦ КАК ПриходБезПеремещений,
ВЫБОР
КОГДА НЕ Товары.Регистратор ССЫЛКА Документ.ПеремещениеТоваров
ТОГДА Товары.КоличествоРасход
ИНАЧЕ 0
КОНЕЦ КАК РасходБезПеремещений,
Товары.КоличествоКонечныйОстаток КАК КоличествоКонечныйОстаток
ИЗ
РегистрНакопления.ТоварыНаСкладах.ОстаткиИОбороты(&ДатаНач, &ДатаКон, Регистратор, , ) КАК Товары) КАК ВложенныйЗапрос
СГРУППИРОВАТЬ ПО
ВложенныйЗапрос.Склад,
ВложенныйЗапрос.Номенклатура,
ВложенныйЗапрос.КоличествоНачальныйОстаток,
ВложенныйЗапрос.КоличествоКонечныйОстаток
При таком подходе поля остатков отражают реальную картину на складах, а обороты не содержат внутренних перемещений.
Для высоконагруженных баз использование периодичности Регистратор в таблице ОстаткиИОбороты может работать медленно, так как платформе приходится рассчитывать остатки на каждый документ за период. Оптимальным решением с точки зрения производительности является раздельное получение реальных остатков и очищенных оборотов.
Посмотрим на схему построения запроса через объединение:
ВЫБРАТЬ
Итоги.Склад КАК Склад,
Итоги.Номенклатура КАК Номенклатура,
СУММА(Итоги.НачОст) КАК НачОст,
СУММА(Итоги.Приход) КАК Приход,
СУММА(Итоги.Расход) КАК Расход,
СУММА(Итоги.КонОст) КАК КонОст
ИЗ
(ВЫБРАТЬ
ОстаткиНач.Склад КАК Склад,
ОстаткиНач.Номенклатура КАК Номенклатура,
ОстаткиНач.КоличествоОстаток КАК НачОст,
0 КАК Приход,
0 КАК Расход,
0 КАК КонОст
ИЗ
РегистрНакопления.ТоварыНаСкладах.Остатки(&ДатаНач, ) КАК ОстаткиНач
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ
Обороты.Склад,
Обороты.Номенклатура,
0,
Обороты.КоличествоПриход,
Обороты.КоличествоРасход,
0
ИЗ
РегистрНакопления.ТоварыНаСкладах.Обороты(&ДатаНач, &ДатаКон, Регистратор, ) КАК Обороты
ГДЕ
НЕ Обороты.Регистратор ССЫЛКА Документ.ПеремещениеТоваров
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ
ОстаткиКон.Склад,
ОстаткиКон.Номенклатура,
0,
0,
0,
ОстаткиКон.КоличествоОстаток
ИЗ
РегистрНакопления.ТоварыНаСкладах.Остатки(&ДатаКон, ) КАК ОстаткиКон) КАК Итоги
СГРУППИРОВАТЬ ПО
Итоги.Склад,
Итоги.Номенклатура
В этом варианте виртуальные таблицы остатков берут агрегированные данные из итогов регистра с максимальной скоростью, а фильтрация по регистратору затрагивает исключительно таблицу оборотов.
Если задача заключается в получении объемов закупок у поставщиков и продаж клиентам в типовых конфигурациях (например, Управление торговлей 10.3 или УПП), правильнее отказаться от чтения регистра остатков ТоварыНаСкладах в пользу специализированных оборотных регистров:
В этих регистрах внутренние складские перемещения отсутствуют изначально, что избавляет от необходимости усложнять запросы дополнительными условиями.