Часто разработчики сталкиваются с ситуацией, когда отчет на Системе Компоновки Данных (СКД) работает идеально до тех пор, пока пользователь не применяет отбор. После этого данные из некоторых колонок, особенно полученных через ЛЕВОЕ СОЕДИНЕНИЕ, могут бесследно исчезать. В отчете все строки на месте, но конкретный столбец оказывается пустым, или, что еще хуже, пропадают целые строки, которые должны были остаться. Чтобы упростить быстрый анализ данных и понять причины такого поведения, нужно заглянуть "под капот" генератора запросов.
Давайте разберемся, почему так происходит и как с этим бороться. Проанализируем типичный случай: соединение оборотов регистра накопления со срезом последних регистра сведений.
Основная причина такого поведения кроется в механизме работы СКД. Когда мы создаем запрос в конструкторе, а затем пользователь добавляет отбор в интерфейсе отчета, СКД не просто выполняет наш исходный запрос. Она модифицирует его, добавляя условия из пользовательского отбора в итоговый текст запроса.
По умолчанию, СКД помещает эти условия в секцию ГДЕ (WHERE) самого последнего, итогового запроса. И именно здесь возникает проблема при использовании ЛЕВОЕ СОЕДИНЕНИЕ.
Рассмотрим на упрощенном примере. У нас есть две таблицы:
Мы соединяем их левым соединением, чтобы получить продажи даже по тем товарам, для которых свойства не заданы:
ВЫБРАТЬ
Продажи.Товар,
Продажи.Сумма,
СвойстваТоваров.СтавкаНДС
ИЗ
РегистрНакопления.Продажи.Обороты КАК Продажи
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.СвойстваТоваров.СрезПоследних КАК СвойстваТоваров
ПО Продажи.Товар = СвойстваТоваров.Товар
Без отборов все работает. Но если пользователь добавит отбор, например, Продажи.НоменклатурнаяГруппа = "Бытовая техника", СКД превратит наш запрос примерно в такой:
ВЫБРАТЬ
Продажи.Товар,
Продажи.Сумма,
СвойстваТоваров.СтавкаНДС
ИЗ
РегистрНакопления.Продажи.Обороты КАК Продажи
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.СвойстваТоваров.СрезПоследних КАК СвойстваТоваров
ПО Продажи.Товар = СвойстваТоваров.Товар
ГДЕ
Продажи.НоменклатурнаяГруппа = "Бытовая техника"
Казалось бы, все верно. Но проблема усугубляется, если отбор накладывается на поле, которое само получено через соединение (например, реквизит партнера из договора). Если для какой-то строки из основной таблицы нет соответствия в присоединяемой, то все поля присоединяемой таблицы для этой строки будут иметь значение NULL. И если отбор коснется этих полей, даже косвенно, строка может быть отфильтрована, хотя должна была остаться.
Ключевой момент: отбор, примененный в секции ГДЕ после соединения, фильтрует уже объединенный результат. Это может "убить" строки, для которых левое соединение не нашло пару, или обнулить данные, если группировка происходит по полю из присоединенной таблицы.
Прежде чем что-то исправлять, нужно точно понять, что именно делает СКД. Лучший инструмент для этого — "Консоль системы компоновки данных". Подобные инструменты часто входят в состав расширенных наборов, таких как Инструменты разработчика "Инструментики" для тонкого клиента 1С, которые позволяют эффективно работать с запросами и кодом. Также для глубокой отладки может подойти Консоль запросов УФ, обладающая полной поддержкой пакетных запросов и временных таблиц. Эти инструменты позволяют:
Порядок действий:
Изучив этот запрос, вы увидите, куда именно СКД "прилепила" ваше условие отбора, и сразу поймете, почему пропадают данные.
Это самый надежный и правильный способ решения проблемы. Идея в том, чтобы отфильтровать данные до того, как они будут соединяться с другими таблицами. В СКД это делается путем добавления условия в фигурных скобках {...} в те пакеты запроса, которые формируют временные таблицы.
Посмотрим на запрос из исходной темы. Отбор по партнеру {ГДЕ (НачисленияУдержанияПоСотрудникамОбороты.Договор.Партнер = &Партнер)} применяется в третьем пакете, после того как уже была сформирована временная таблица ВТ_НДС.
Правильнее было бы отфильтровать партнеров на самых ранних этапах.
Шаг 1. Фильтруем партнеров в первом запросе.
Добавляем условие отбора в первый пакет, который собирает иерархию партнеров. Это резко сократит объем данных для дальнейшей обработки.
ВЫБРАТЬ
// ... поля ...
ПОМЕСТИТЬ ВТ_Партнеры
ИЗ
Справочник.Партнеры КАК Партнеры
{ГДЕ
Партнеры.Ссылка.* В (&Партнер)} // Используем возможность СКД развернуть иерархию
Шаг 2. Фильтруем основную таблицу оборотов.
Основной отбор должен быть применен к основной таблице данных — регистру накопления. Перенесем условие сюда.
ВЫБРАТЬ РАЗРЕШЕННЫЕ
// ... поля ...
ВТ_НДС.НДС КАК НДС
ПОМЕСТИТЬ ВТ_Итог
ИЗ
РегистрНакопления.НачисленияУдержанияПоСотрудникам.Обороты(
,
,
День,
ПериодРегистрации >= &ДатаНачала
И ПериодРегистрации <= &ДатаОкончания
И Начисление В (&Начисление)
// Добавляем условие прямо в параметры виртуальной таблицы - это наиболее производительно!
{И Договор.Партнер.* В (&Партнер)}) КАК НачисленияУдержанияПоСотрудникамОбороты
ЛЕВОЕ СОЕДИНЕНИЕ ВТ_НДС КАК ВТ_НДС
ПО НачисленияУдержанияПоСотрудникамОбороты.Договор.Партнер = ВТ_НДС.Партнер
// ... СГРУППИРОВАТЬ ПО ...
Когда мы помещаем условие в параметры виртуальной таблицы, система сначала фильтрует записи регистра по партнеру, и только потом считает обороты. Это не только решает проблему с отбором, но и значительно ускоряет выполнение запроса.
Более "продвинутый" и гибкий способ, который идеально подходит для сложных отчетов — это не создавать один гигантский запрос, а разбить его на несколько независимых наборов данных и связать их уже средствами СКД. Рассматривая оптимизацию отчетов с набором данных - объект, можно заметить, что разделение данных часто помогает избежать проблем производительности и некорректных фильтров.
Как это сделать:
При таком подходе СКД будет выполнять два отдельных, более простых запроса. Отбор по партнеру, примененный в настройках отчета, будет корректно передан в каждый из наборов данных. СКД сама "подтянет" значение НДС для каждого партнера, и проблема с `ЛЕВЫМ СОЕДИНЕНИЕМ` исчезнет, так как его в явном виде в запросе больше нет.
Иногда СКД проявляет излишнюю "самостоятельность". Совет отключить автозаполнение — это шаг к полному контролю над отчетом. Если вам требуется программно управлять структурой схемы, можно применить СКДБилдер: упрощаем программное создание СКД, который делает код более читаемым и сокращает его объем.
В конструкторе схемы на закладке "Настройки" есть кнопка для настройки компоновки данных. Там можно:
Когда вы делаете это вручную, вы точно контролируете, какие поля доступны пользователю и как они будут использоваться. Это помогает избежать ситуаций, когда СКД неверно интерпретирует связи полей и генерирует некорректный запрос.
Итог: Проблема с пропадающими данными при отборе в СКД — это не ошибка, а особенность работы механизма. Поняв, что СКД применяет отборы к итоговому результату, можно легко решить эту проблему, либо перенося условия фильтрации на более ранние этапы (во временные таблицы и параметры виртуальных таблиц), либо используя более гибкий механизм связей наборов данных.