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

Программист 1С v8.3 (Управляемые формы) 1С:Управление нашей фирмой Управленческий учет Торговля и дистрибуция
← На главную

При разработке отчетов на языке запросов 1С программисты часто сталкиваются с ситуацией, когда итоговое количество товара (остатки или обороты) оказывается в несколько раз больше реального. Особенно часто это проявляется при использовании виртуальной таблицы ОстаткиИОбороты и попытке соединить её с дополнительными данными, такими как цены или штрихкоды. Проанализируем эту ситуацию на примере кода, разберем причины "мультипликации" данных и найдем способы решения этой проблемы.

Причина 1: Использование периодичности Регистратор

Рассмотрим внимательно исходный запрос. В параметрах виртуальной таблицы РегистрНакопления.Запасы.ОстаткиИОбороты указана периодичность Регистратор. Это критически важный момент. Когда мы выбираем периодичность до регистратора, система формирует записи по каждой хозяйственной операции (документу), которая изменила состояние регистра в выбранном периоде.

Проблема заключается в том, как 1С рассчитывает поля КоличествоНачальныйОстаток и КоличествоКонечныйОстаток для каждой строки с регистратором. Если за период было 10 движений по одной номенклатуре, то запрос вернет 10 строк. В каждой из этих строк будет указан начальный и конечный остаток на момент этого конкретного движения. Если затем мы применяем функцию СУММА к этим полям в секции СГРУППИРОВАТЬ ПО, мы фактически складываем остаток сам с собой столько раз, сколько было движений. Выясним причину на простом примере: если на складе лежало 5 штук и было 3 продажи, то при группировке без самого регистратора вы получите начальный остаток 15 (5+5+5) вместо 5.

Решение: Если вам не нужно выводить детальный список документов (регистраторов), уберите этот параметр из виртуальной таблицы. Замените его на пустой параметр или укажите Период / Авто. Рассмотрим исправленный фрагмент вызова таблицы:


РегистрНакопления.Запасы.ОстаткиИОбороты(
    &НачДата,
    &КонДата,
    , // Периодичность убрана
    ,
    Номенклатура В ИЕРАРХИИ (&Номенклатура)
        И СтруктурнаяЕдиница = &Склад) КАК ЗапасыОстаткиИОбороты

Причина 2: Множественные записи в левых соединениях

Разберем ситуацию с ЛЕВОЕ СОЕДИНЕНИЕ. В исходном запросе идет соединение с временной таблицей штрихкодов и виртуальной таблицей среза последних цен. Это стандартная ловушка: если для одной комбинации Номенклатура + Характеристика в базе данных найдется более одной записи в соединяемой таблице, то исходная строка с количеством будет продублирована столько раз, сколько соответствий найдено.

Проанализируем таблицу ВТ_Штрихкоды. Автор использует МАКСИМУМ(Штрихкод) и группировку по номенклатуре и характеристике. Это правильный шаг для предотвращения дублей по штрихкодам. Однако аналогичная проверка часто забывается для регистра цен.

В регистре сведений ЦеныНоменклатуры, даже при использовании СрезПоследних, могут существовать дополнительные измерения, которые не участвуют в условии связи. Например:

Если в регистре цен по одному и тому же ВидуЦены для товара задано две цены в разных валютах, левое соединение "размножит" строку остатка, и сумма по количеству удвоится.

Причина 3: Группировка по полям, содержащим NULL

Посмотрим на пример формирования наименования: ЗапасыОстаткиИОбороты.Номенклатура.Наименование + ", " + .... При использовании ЛЕВОЕ СОЕДИНЕНИЕ, если штрихкод или цена не найдены, соответствующие поля будут содержать значение NULL. В некоторых случаях сложные выражения в полях группировки, оперирующие значениями из присоединенных таблиц, могут приводить к некорректному "схлопыванию" или, наоборот, разделению строк при выполнении SQL-запроса.

Рекомендуемый алгоритм построения запроса без задвоений

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

  1. Подготовка данных во временных таблицах. Сначала соберем все уникальные данные, которые могут иметь дубли. Например, цены и штрихкоды.
  2. Получение чистых остатков. Сформируем таблицу остатков и оборотов без детализации до регистратора (если она не требуется для отчета).
  3. Финальное объединение. Соединим таблицу остатков с подготовленными временными таблицами по ключевым полям Номенклатура и Характеристика.

Рассмотрим пример правильной подготовки цен во временную таблицу, чтобы исключить дублирование из-за лишних измерений:


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

Этот подход гарантирует, что на одну позицию номенклатуры в соединении придет ровно одна цена, что исключает математическое задвоение остатков.

Как проверить, в чем именно ошибка?

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

Также стоит помнить совет опытных разработчиков: если вам не нужны остатки на начало и конец периода, а интересует только объем движения товара, используйте виртуальную таблицу Обороты. Она работает быстрее и менее подвержена ошибкам суммирования при использовании различных периодичностей.

Итоговые рекомендации:

← На главную