Почему запросы на вывод товаров без оборотов работают по-разному: разбор механизмов 1С

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

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

Анализ первого варианта: Использование подзапроса и отрицания

Рассмотрим первый вариант запроса, который применил автор. Здесь используется классическая логика исключения через конструкцию ГДЕ НЕ Ссылка В (...). Проанализируем этот код:


ВЫБРАТЬ
    Товар.Код,
    Товар.Наименование,
    КОЛИЧЕСТВО(РАЗЛИЧНЫЕ Товар.Ссылка) КАК Колво
ИЗ
    Справочник.Номенклатура КАК Товар
ГДЕ   
    НЕ Ссылка В
        (ВЫБРАТЬ
            Номенклатура
        ИЗ
            РегистрНакопления.ТоварыОрганизаций.Обороты (&НачалоПериода, &КонецПериода)
        ГДЕ
            КоличествоОборот <> 0) 
    И НЕ Товар.ЭтоГруппа 
    И Товар.Родитель В ИЕРАРХИИ(&ГруппаТовара) 
СГРУППИРОВАТЬ ПО
    Товар.Код,
    Товар.Наименование

В данном случае логика следующая: мы выбираем всю номенклатуру, которая не входит в список товаров, имеющих ненулевой оборот. Важный нюанс кроется в условии КоличествоОборот <> 0 внутри подзапроса. Если по товару был приход в 10 единиц и расход в 10 единиц, то КоличествоОборот в виртуальной таблице будет равно 0. Следовательно, такой товар не попадет в подзапрос, и основная выборка выведет его как товар без оборота. Это критическая точка, которую мы сравним со вторым вариантом.

Анализ второго варианта: Левое соединение и проверка на NULL

Теперь разберем второй подход, основанный на соединении таблиц:


ВЫБРАТЬ
    Товар.Код,
    Товар.Наименование КАК Название,
    КОЛИЧЕСТВО(РАЗЛИЧНЫЕ Товар.Ссылка) КАК Колво
ИЗ
    Справочник.Номенклатура КАК Товар
ЛЕВОЕ СОЕДИНЕНИЕ 
    РегистрНакопления.ТоварыОрганизаций.Обороты(&НачалоПериода, &КонецПериода) КАК ТоварыОрганизацийОбороты
    ПО Товар.Ссылка = ТоварыОрганизацийОбороты.Номенклатура
ГДЕ
    НЕ Товар.ЭтоГруппа
    И ТоварыОрганизацийОбороты.Номенклатура ЕСТЬ NULL
    И Товар.Родитель В ИЕРАРХИИ(&ГруппаТовара)
СГРУППИРОВАТЬ ПО
    Товар.Код,
    Товар.Наименование

Здесь используется ЛЕВОЕ СОЕДИНЕНИЕ со справочником номенклатуры. Условие ТоварыОрганизацийОбороты.Номенклатура ЕСТЬ NULL означает, что в регистре накопления за указанный период вообще нет записей по данному товару. Проанализируем разницу: если по товару был приход 10 и расход 10, то запись в виртуальной таблице Обороты существует (пусть и с нулевым ресурсом). В этом случае условие ЕСТЬ NULL не выполнится, и товар не попадет в результат. Именно поэтому количество товаров во втором случае оказывается меньше.

Главная причина расхождения: Поведение виртуальной таблицы Обороты

Разберем подробнее, как 1С формирует виртуальную таблицу Обороты. Многие ошибочно полагают, что если сумма движений равна нулю, то и записи в таблице не будет. Однако это не так. Рассмотрим пример:

  1. Движение 1: Товар "Ботинок", Количество +10
  2. Движение 2: Товар "Ботинок", Количество -10

Виртуальная таблица Обороты при обращении к ней выдаст строку, где Номенклатура = "Ботинок", а КоличествоОборот = 0. В первом запросе условие КоличествоОборот <> 0 отсечет эту строку в подзапросе, и "Ботинок" будет считаться товаром "без оборота". Во втором запросе соединение найдет эту строку, поле Номенклатура из регистра не будет равно NULL, и "Ботинок" не попадет в список товаров без оборотов.

Важный вывод: Первый запрос ищет товары с нулевым итоговым сальдо оборотов, а второй — товары, по которым вообще не было записей в регистре за период.

Проблемы производительности и "антипаттерны"

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

1. Использование NOT IN (НЕ В): Эта конструкция считается "тяжелой" для SQL-сервера. При больших объемах данных в справочнике Номенклатура и большом количестве движений в регистре, такой запрос будет выполняться крайне медленно. Оптимизатор СУБД часто не может эффективно использовать индексы при обработке отрицания в подзапросе.

2. Отсутствие параметров в виртуальной таблице: Во втором запросе автора параметры виртуальной таблицы (условия по номенклатуре) не используются. Вместо этого фильтрация В ИЕРАРХИИ вынесена в секцию ГДЕ. Это заставляет систему сначала получить обороты по всей номенклатуре базы, а уже потом отсекать лишнее. Правильнее передавать условия непосредственно в параметры виртуальной таблицы.

3. Группировка по полям Код и Наименование: Использование СГРУППИРОВАТЬ ПО для получения уникального списка — избыточная операция, если мы работаем со справочником. В данном контексте лучше использовать ключевое слово РАЗЛИЧНЫЕ или временные таблицы.

Рекомендуемое решение задачи

Проанализируем наиболее правильный и производительный способ решения этой задачи. Разделим процесс на шаги с использованием временных таблиц. Это позволит нам четко контролировать, что именно мы считаем "отсутствием оборота".

Шаг 1: Получаем список товаров, у которых были движения. Если нам нужно исключить товары с любыми движениями (даже нулевыми в итоге), мы просто выбираем их во временную таблицу.


ВЫБРАТЬ РАЗЛИЧНЫЕ
    Обороты.Номенклатура КАК Ссылка
ПОМЕСТИТЬ ВТ_ТоварыСДвижениями
ИЗ
    РегистрНакопления.ТоварыОрганизаций.Обороты(&НачалоПериода, &КонецПериода, , Номенклатура В ИЕРАРХИИ(&ГруппаТовара)) КАК Обороты
;

Шаг 2: Получаем итоговый список методом левого соединения. Теперь мы выбираем товары из справочника, которых нет в нашей временной таблице.


ВЫБРАТЬ
    Товар.Код,
    Товар.Наименование
ИЗ
    Справочник.Номенклатура КАК Товар
ЛЕВОЕ СОЕДИНЕНИЕ
    ВТ_ТоварыСДвижениями КАК ТоварыСДвижениями
    ПО Товар.Ссылка = ТоварыСДвижениями.Ссылка
ГДЕ
    Товар.Родитель В ИЕРАРХИИ(&ГруппаТовара)
    И НЕ Товар.ЭтоГруппа
    И ТоварыСДвижениями.Ссылка ЕСТЬ NULL

Почему этот способ лучше?

Таким образом, мы выяснили, что основная причина различий в работе запросов автора — это разная трактовка "нулевого оборота". В 1С важно помнить: отсутствие записей в физических таблицах (NULL при соединении) и нулевое значение ресурса в виртуальной таблице — это разные состояния данных. Всегда выбирайте метод ЛЕВОЕ СОЕДИНЕНИЕ с предварительной подготовкой данных во временных таблицах для достижения максимальной точности и производительности ваших отчетов.

← На главную