При разработке отчетов в Системе компоновки данных (СКД) на платформе 1С:Предприятие разработчики часто сталкиваются с необходимостью заменить пустые аналитические разрезы (например, незаполненный склад в проводках) на значения из других связанных таблиц. На первый взгляд задача кажется тривиальной: достаточно использовать стандартную конструкцию ВЫБОР КОГДА. Однако на практике программисты часто обнаруживают, что даже после прописания условий в результирующей выборке все равно остаются пустые строки.
В этой статье мы подробно проанализируем ситуацию, выясним причины некорректного поведения запроса, разберем природу «пустоты» в полях составного типа (таких как Субконто) и научимся оптимизировать соединения таблиц, чтобы избежать критического снижения производительности СУБД.
Рассмотрим фрагмент запроса, с которым возникли сложности у автора темы:
ВЫБРАТЬ РАЗРЕШЕННЫЕ
ВЫБОР
КОГДА ХозрасчетныйОбороты.Субконто2 = ""
ТОГДА Трансп_РегистрСведений.Машина
ИНАЧЕ ХозрасчетныйОбороты.Субконто2
КОНЕЦ КАК Склад,
...
Автор пытается сопоставить значение ХозрасчетныйОбороты.Субконто2 с пустой строкой "". На первый взгляд логика понятна: если субконто пустое, то мы хотим вывести значение поля Машина из регистра сведений. Но на практике это условие не срабатывает, и ветка ТОГДА просто игнорируется системой. Давайте разберем по шагам, почему так происходит.
---
Основная ошибка заключается в сравнении поля составного типа с пустой строкой. В планах счетов 1С поле Субконто практически всегда имеет составной тип данных (может содержать ссылки на справочники «Склады», «Контрагенты», «Физические лица» и т.д.). СУБД не может неявно привести ссылку или неопределенное значение к строке типа "", поэтому сравнение всегда возвращает Ложь. Разобраться в нюансах и ошибках при сопоставлении ссылок и строк поможет публикация о сравнении строк и ссылочных типов в запросах.
В платформе 1С:Предприятие для полей составного типа существует три принципиально разных состояния «пустоты»:
Справочник.Склады.ПустаяСсылка) — когда тип значения определен (это склад), но конкретный элемент справочника в документе выбран не был.Рассмотрим подробнее, как правильно обрабатывать каждый из этих вариантов в запросе.
Способ 1. Проверка на конкретную пустую ссылку
Если мы точно знаем, какой тип данных должен храниться в этом субконто (например, справочник «Склады»), то мы можем напрямую сравнить его с пустой ссылкой этого типа с помощью функции Значение():
ВЫБОР
КОГДА ХозрасчетныйОбороты.Субконто2 = ЗНАЧЕНИЕ(Справочник.Склады.ПустаяСсылка)
ТОГДА Трансп_РегистрСведений.Машина
ИНАЧЕ ХозрасчетныйОбороты.Субконто2
КОНЕЦ КАК Склад
Способ 2. Универсальная проверка через реквизит «Ссылка»
Если тип субконто может меняться или нам нужно отловить абсолютно любое пустое значение составного типа (и Неопределено, и пустые ссылки любых справочников), можно применить популярный прием с обращением к свойству Ссылка:
ВЫБОР
КОГДА (НЕ ХозрасчетныйОбороты.Субконто2 ЕСТЬ NULL)
И ХозрасчетныйОбороты.Субконто2.Ссылка ЕСТЬ NULL
ТОГДА Трансп_РегистрСведений.Машина
ИНАЧЕ ХозрасчетныйОбороты.Субконто2
КОНЕЦ КАК Склад
Важное предупреждение по производительности: Обращение через точку к реквизиту составного поля (Субконто2.Ссылка) заставляет СУБД генерировать левое соединение со всеми физическими таблицами, типы которых входят в это составное поле. Если в типе субконто настроены десятки справочников, ваш запрос начнет сильно тормозить на больших базах данных! Используйте этот метод с осторожностью.
Способ 3. Простая проверка на НЕОПРЕДЕЛЕНО
Если в проводке субконто не заполнено физически, оно принимает значение НЕОПРЕДЕЛЕНО. В запросе его можно проверить простым условием:
ВЫБОР
КОГДА ХозрасчетныйОбороты.Субконто2 = НЕОПРЕДЕЛЕНО
ТОГДА Трансп_РегистрСведений.Машина
ИНАЧЕ ХозрасчетныйОбороты.Субконто2
КОНЕЦ КАК Склад
---
Проанализируем структуру связей в запросе автора. В тексте написано ПОЛНОЕ СОЕДИНЕНИЕ, но при этом в секции ГДЕ наложено жесткое условие на период правой таблицы:
ИЗ РегистрСведений.Трансп_РегистрСведений КАК Трансп_РегистрСведений
ПОЛНОЕ СОЕДИНЕНИЕ РегистрБухгалтерии.Хозрасчетный.Обороты(...) КАК ХозрасчетныйОбороты
ПО Трансп_РегистрСведений.Машина = ХозрасчетныйОбороты.Субконто2
ГДЕ Трансп_РегистрСведений.Период >= &НачалоПериода
И Трансп_РегистрСведений.Период <= &КонецПериода
Это классическая ошибка проектирования запросов в 1С. Когда СУБД обрабатывает секцию ГДЕ, она применяет фильтр ко всей результирующей таблице. Если для какого-то оборота из регистра бухгалтерии не нашлось соответствующей машины в регистре сведений, поля таблицы Трансп_РегистрСведений (включая поле Период) заполняются значениями NULL.
Проверка условия NULL >= &НачалоПериода возвращает значение Ложь (или NULL), в результате чего эти строки полностью исключаются из финальной выборки. Таким образом, ваше ПОЛНОЕ СОЕДИНЕНИЕ фактически превращается в ЛЕВОЕ (или даже во внутреннее), уничтожая саму суть первоначального замысла.
Чтобы избежать искажения результатов и оптимизировать работу СУБД, у нас есть два пути.
Путь 1. Перенос условий фильтрации в условия связи (секция ПО)
Все условия на ограничение полей соединяемой таблицы необходимо перенести непосредственно в параметры связи:
ПОЛНОЕ СОЕДИНЕНИЕ РегистрБухгалтерии.Хозрасчетный.Обороты(...) КАК ХозрасчетныйОбороты
ПО Трансп_РегистрСведений.Машина = ХозрасчетныйОбороты.Субконто2
И Трансп_РегистрСведений.Период МЕЖДУ &НачалоПериода И &КонецПериода
Путь 2. Использование Временных Таблиц (Рекомендуемый подход)
Для повышения читаемости кода и производительности запроса (особенно в СУБД PostgreSQL) рекомендуется сначала выбрать отфильтрованные данные из регистра сведений во временную таблицу, а затем уже соединять ее с оборотами:
// Помещаем отфильтрованные данные регистра во временную таблицу
ВЫБРАТЬ
Трансп_РегистрСведений.Машина КАК Машина,
Трансп_РегистрСведений.НормаНа100 КАК НормаНа100,
Трансп_РегистрСведений.Пробег КАК Пробег
ПОМЕСТИТЬ ВТ_ДанныеТранспорта
ИЗ
РегистрСведений.Трансп_РегистрСведений КАК Трансп_РегистрСведений
ГДЕ
Трансп_РегистрСведений.Период МЕЖДУ &НачалоПериода И &КонецПериода
ИНДЕКСИРОВАТЬ ПО
Машина;
////////////////////////////////////////////////////////////////////////////////
// Основной запрос с левым или полным соединением
ВЫБРАТЬ
ЕСТЬNULL(ХозрасчетныйОбороты.Субконто2, ВТ_ДанныеТранспорта.Машина) КАК Склад,
...
ИЗ
ВТ_ДанныеТранспорта КАК ВТ_ДанныеТранспорта
ЛЕВОЕ СОЕДИНЕНИЕ РегистрБухгалтерии.Хозрасчетный.Обороты(...) КАК ХозрасчетныйОбороты
ПО ВТ_ДанныеТранспорта.Машина = ХозрасчетныйОбороты.Субконто2
Обратите внимание на использование функции ЕСТЬNULL. Если в результате соединения ХозрасчетныйОбороты.Субконто2 возвращает NULL (из-за отсутствия связи), функция автоматически подставит значение Машина. Это самый быстродействующий способ решения проблемы пустых значений!
---
Проанализируем еще одну важную деталь в пакете запросов автора темы:
ЕСТЬNULL(Данные.КоличествоОборотДт / Данные.Пробег * 100, 0) КАК ФактРасход100
Использование функции ЕСТЬNULL в данном контексте защищает только от ситуации, когда весь результат деления принимает значение NULL. Однако, если поле Данные.Пробег окажется равным 0, СУБД мгновенно выдаст критическую ошибку "Division by zero" (Деление на ноль), и отчет аварийно завершит свою работу.
Чтобы обезопасить математические расчеты в запросах, всегда проверяйте делитель с помощью конструкции ВЫБОР КОГДА:
ВЫБОР
КОГДА ЕСТЬNULL(Данные.Пробег, 0) = 0
ТОГДА 0
ИНАЧЕ Данные.КоличествоОборотДт / Данные.Пробег * 100
КОНЕЦ КАК ФактРасход100
---
Поскольку мы разрабатываем отчет на СКД, нам не обязательно перегружать исходный SQL-запрос сложными логическими условиями. Платформа 1С предоставляет мощные визуальные инструменты, которые могут решить эти задачи на уровне компоновки данных.
1. Вычисляемые поля
Вы можете перенести логику подмены пустых значений на вкладку «Вычисляемые поля» в схеме компоновки данных. Добавьте новое поле (например, СкладВычисляемый) и напишите выражение на встроенном языке СКД:
Выбор Когда Склад Есть Null Тогда Машина Иначе Склад Конец
Вычисления на уровне СКД происходят уже после получения сырых данных из СУБД, что снижает нагрузку на сервер базы данных.
2. Связи наборов данных
Вместо написания сложного запроса с полным соединением, вы можете создать два простых отдельных набора данных (НаборДанных1 — Обороты бухгалтерии, НаборДанных2 — Данные регистра сведений) и связать их на вкладке «Связи наборов данных». СКД самостоятельно сопоставит строки по ключевым полям и корректно обработает ситуации, когда в одном из наборов отсутствуют сопоставимые данные.
3. Условное оформление
Если задача заключается только в красивом визуальном представлении данных в отчете, воспользуйтесь вкладкой «Условное оформление» в настройках компоновки. Добавьте элемент оформления:
Машина.Склад Не Заполнено.Склад.При выводе отчета СКД автоматически подменит пустое поле склада на наименование машины, не изменяя при этом саму структуру исходных данных запроса.