Почему в отчете СКД пропадают товары без характеристик или исчезают позиции при отборе по виду цен?

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

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

В этой статье мы подробно разберем две критические технические ошибки, которые приводят к подобному поведению отчета, выясним причины их возникновения на уровне СУБД и рассмотрим по шагам правильные варианты построения запросов в СКД.

Симптомы проблемы

Рассмотрим классический сценарий. Нам необходимо построить отчет, который показывает объем выпущенной продукции и оценивает его по определенному виду цен (например, по дилерской цене). Для этого мы соединяем виртуальную таблицу оборотов регистра накопления ВыпускПродукции.Обороты и виртуальную таблицу среза последних регистра сведений ЦеныНоменклатуры.СрезПоследних.

В процессе работы мы можем столкнуться со следующими проблемами:

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

Давайте подробно проанализируем каждую из этих проблем и найдем надежное решение.

---

Проблема №1: Избыточное разыменование пустых ссылок (.Ссылка) в условиях связи

Рассмотрим фрагмент некорректного условия соединения, которое часто пишут начинающие разработчики:


ПО ВыпускПродукцииОбороты.АналитикаУчетаНоменклатуры.Номенклатура.Ссылка = ЦеныНоменклатурыСрезПоследних.Номенклатура.Ссылка
И ВыпускПродукцииОбороты.АналитикаУчетаНоменклатуры.Характеристика.Ссылка = ЦеныНоменклатурыСрезПоследних.Характеристика.Ссылка

На первый взгляд, это условие кажется логичным. Однако использование конструкции .Ссылка для полей, которые уже сами по себе являются ссылочными типами данных (таких как Номенклатура или Характеристика), таит в себе серьезную опасность.

Выясним причину, почему это ломает логику левого соединения для пустых характеристик:

1. Неявное левое соединение (Implicit Join) на уровне СУБД. Когда мы обращаемся к реквизиту через точку (в данном случае пишем Характеристика.Ссылка), платформа 1С вынуждена сгенерировать SQL-запрос, который делает дополнительное левое соединение с соответствующей таблицей справочника (в данном случае с таблицей характеристик). Это снижает производительность запроса и усложняет его структуру.

2. Обработка пустой ссылки. Если у товара не ведется учет по характеристикам, то в поле Характеристика хранится значение ЗначениеЗаполнено() = Ложь, то есть пустая ссылка данного типа (состоящая из одних нулей в уникальном идентификаторе). При попытке обратиться через точку к свойству .Ссылка пустой ссылки, СУБД пытается найти запись с нулевым идентификатором в таблице справочника характеристик. Естественно, такой записи там нет. В результате этого неявного соединения СУБД возвращает значение NULL.

3. Сравнение с NULL в языке SQL. В результирующем SQL-запросе наше условие соединения превращается в сравнение: NULL = NULL (или сравнение поля с NULL). По правилам трехзначной логики SQL, любое сравнение значения с NULL, а также сравнение NULL = NULL возвращает результат UNKNOWN (ложь). Из-за этого условие связи для товаров с пустой характеристикой никогда не выполняется, и левое соединение по этим строкам разрывается.

Решение проблемы №1: Никогда не используйте обращение через точку к полю Ссылка. Поля Номенклатура и Характеристика уже содержат ссылки. Сравнивать нужно непосредственно сами поля:


ПО ВыпускПродукцииОбороты.АналитикаУчетаНоменклатуры.Номенклатура = ЦеныНоменклатурыСрезПоследних.Номенклатура
И ВыпускПродукцииОбороты.АналитикаУчетаНоменклатуры.Характеристика = ЦеныНоменклатурыСрезПоследних.Характеристика

В этом случае СУБД сравнивает физические значения идентификаторов в таблицах (включая пустые ссылки типа "00000000-..."), не обращаясь к таблицам справочников. Соединение отработает корректно, и товары без характеристик не пропадут.

---

Проблема №2: Превращение ЛЕВОГО СОЕДИНЕНИЯ во ВНУТРЕННЕЕ из-за отборов в СКД

Вторая проблема заключается в поведении отборов по правой (присоединяемой) таблице. Допустим, мы хотим видеть цены только определенного вида — "Дилерская".

Если мы просто добавим левое соединение с таблицей цен, а в настройках СКД (или в секции ГДЕ запроса) укажем отбор:

ЦеныНоменклатурыСрезПоследних.ВидЦены = &ВидЦены

то наш отчет моментально лишится всех строк, для которых цена "Дилерская" не установлена. Почему это происходит?

Механизм работы ЛЕВОГО СОЕДИНЕНИЯ с фильтрацией:

  1. Сначала СУБД выполняет левое соединение. Для товаров, у которых нет цены выбранного вида, в полях правой таблицы (включая ВидЦены и Цена) возвращаются значения NULL.
  2. Затем СУБД применяет условие фильтрации (отбор), указанное в секции ГДЕ или наложенное СКД на весь результат запроса.
  3. Для товаров без цен условие фильтрации превращается в: NULL = "Дилерская".
  4. Это условие возвращает ложь, и СУБД безжалостно удаляет всю строку с товаром из итоговой выборки. Левое соединение фактически превратилось во внутреннее.

Почему без отбора цены выводятся пустыми, хотя они есть в системе?

Если мы уберем отбор по виду цен вообще, то виртуальная таблица СрезПоследних без параметров вернет последнюю по времени запись для каждого товара, независимо от вида цены. Если последней установленной ценой для товара была "Закупочная", то именно она вернется из среза. Если в самом отчете выводится колонка только для "Дилерской" цены, то поле останется пустым, так как дилерская цена не была последней в регистре.

Решение проблемы №2: Чтобы сохранить логику левого соединения и гарантировать вывод всех товаров (даже без цен), любые отборы по правой таблице необходимо накладывать до момента соединения — непосредственно в параметрах виртуальной таблицы правой части.

---

Правильный вариант реализации отчета

Рассмотрим по шагам, как правильно составить запрос и настроить систему компоновки данных для корректного вывода цен и товаров.

Шаг 1. Перенос условий в параметры виртуальной таблицы

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

Также рекомендуется использовать функцию ЕСТЬNULL, чтобы в отчете вместо пустых значений (NULL) для товаров без цен выводился аккуратный ноль.

Посмотрим на правильный текст запроса:


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

Шаг 2. Использование фигурных скобок для динамической настройки СКД

Если мы хотим предоставить пользователю возможность гибко выбирать вид цен прямо в настройках отчета (на вкладке "Отбор"), но при этом сохранить корректность запроса, нам следует использовать механизм расширения языка запросов СКД — фигурные скобки {}.

Перепишем левое соединение с использованием фигурных скобок:


ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныНоменклатуры.СрезПоследних(, {ВидЦены.* КАК ВидЦены}) КАК ЦеныНоменклатурыСрезПоследних
ПО ВыпускПродукцииОбороты.АналитикаУчетаНоменклатуры.Номенклатура = ЦеныНоменклатурыСрезПоследних.Номенклатура
И ВыпускПродукцииОбороты.АналитикаУчетаНоменклатуры.Характеристика = ЦеныНоменклатурыСрезПоследних.Характеристика

Разберем подробнее, как это работает:

Резюме и рекомендации

При проектировании сложных отчетов в 1С:ERP и других конфигурациях на платформе 1С:Предприятие 8 всегда придерживайтесь следующих правил:

← На главную