При работе с языком запросов 1С одной из самых распространенных задач является получение данных из одной таблицы с одновременным подсчетом связанных записей в другой. Часто разработчики сталкиваются с ситуацией, когда при отсутствии данных в присоединяемой таблице запрос возвращает NULL вместо ожидаемого названия объекта и цифры 0. В этой статье мы подробно разберем, почему это происходит, и как правильно составить запрос, чтобы результат всегда был корректным.
Рассмотрим ситуацию, с которой столкнулся автор темы. Имеется регистр сведений СтруктураПодчиненностиОрганизации, в котором хранится иерархия (какая организация какой подчинена). Задача: получить список подчиненных организаций для конкретного «родителя» и для каждой из них посчитать, сколько у нее есть своих собственных подчиненных структур.
Проанализируем ошибочный подход:
ВЫБРАТЬ
СтруктураОрганизации.ВедомственнаяОрганизация КАК Организация,
КОЛИЧЕСТВО(СтруктураОрганизации.Организация.Ссылка) КАК КолПодВедОрганизации
ПОМЕСТИТЬ ВТ_КолПодВедОрганизации
ИЗ
ВТ_ПодВедОрганизации КАК ВТ_ПодВедОрганизации
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.СтруктураПодчиненностиОрганизации КАК СтруктураОрганизации
ПО СтруктураОрганизации.ВедомственнаяОрганизация = ВТ_ПодВедОрганизации.Организация
СГРУППИРОВАТЬ ПО
СтруктураОрганизации.ВедомственнаяОрганизация
В данном примере ВТ_ПодВедОрганизации — это «левая» (основная) таблица, а РегистрСведений.СтруктураПодчиненностиОрганизации — «правая» (присоединяемая). Когда у организации из левой таблицы нет подчиненных записей в правой, механизм ЛЕВОЕ СОЕДИНЕНИЕ подставляет NULL во все поля правой таблицы.
Главная ошибка: Группировка производится по полю СтруктураОрганизации.ВедомственнаяОрганизация, которое принадлежит правой таблице. Если данных нет, это поле равно NULL. В итоге все организации, у которых нет подчиненных, «схлопываются» в одну строку, где в колонке организации стоит NULL, а в колонке количества — 0. Мы же хотим видеть название каждой конкретной организации.
Чтобы решить проблему, мы должны всегда выбирать ключевые поля для вывода и группировки из левой (эталонной) таблицы. Разберем по шагам, как изменить запрос:
ВЫБРАТЬ указываем поле ВТ_ПодВедОрганизации.Организация. Поскольку это поле из основной таблицы, оно гарантированно заполнено.СГРУППИРОВАТЬ ПО также указываем поле из левой таблицы.Посмотрим на исправленный фрагмент кода:
ВЫБРАТЬ
ВТ_ПодВедОрганизации.Организация КАК Организация,
КОЛИЧЕСТВО(СтруктураОрганизации.Организация) КАК КолПодВедОрганизации
ИЗ
ВТ_ПодВедОрганизации КАК ВТ_ПодВедОрганизации
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.СтруктураПодчиненностиОрганизации КАК СтруктураОрганизации
ПО ВТ_ПодВедОрганизации.Организация = СтруктураОрганизации.ВедомственнаяОрганизация
СГРУППИРОВАТЬ ПО
ВТ_ПодВедОрганизации.Организация
Почему это работает: Теперь для каждой строки из ВТ_ПодВедОрганизации будет создана запись в результате. Если в регистре нашлись подчиненные организации, КОЛИЧЕСТВО их посчитает. Если не нашлись, функция КОЛИЧЕСТВО, встретив NULL, вернет значение 0, но само название организации останется нетронутым, так как оно взято из левой таблицы.
Иногда возникают ситуации, когда нам необходимо использовать данные именно из правой таблицы (например, если мы хотим вывести специфическое поле, которое может быть переопределено). В этом случае на помощь приходит функция ЕСТЬNULL. Она позволяет подставить значение по умолчанию, если результат выражения равен NULL.
Рассмотрим пример использования:
ВЫБРАТЬ
ЕСТЬNULL(СтруктураОрганизации.ВедомственнаяОрганизация, ВТ_ПодВедОрганизации.Организация) КАК Организация,
КОЛИЧЕСТВО(СтруктураОрганизации.Организация) КАК КолПодВедОрганизации
...
Хотя этот метод технически работает, первый вариант (выбор поля из левой таблицы) считается более «чистым» и производительным, так как системе не нужно выполнять дополнительные проверки функции в каждой строке.
Выясним причину, по которой важно правильно выбирать аргумент для функции КОЛИЧЕСТВО. В языке запросов 1С есть разница между КОЛИЧЕСТВО(*) и КОЛИЧЕСТВО(Поле):
NULL в правой части). Следовательно, КОЛИЧЕСТВО(*) вернет 1, что будет фактической ошибкой (ведь подчиненных записей нет).NULL. Это именно то, что нам нужно: если записей нет, результат будет 0.Проанализируем ситуацию с производительностью. Если в вашей базе данных сотни или тысячи организаций, выполнение соединения с временной таблицей может замедлиться. Чтобы этого избежать, всегда рекомендуется индексировать временные таблицы по полям связи.
Пример правильной подготовки временной таблицы:
ВЫБРАТЬ
СтруктураОрганизации.Организация КАК Организация
ПОМЕСТИТЬ ВТ_ПодВедОрганизации
ИЗ
РегистрСведений.СтруктураПодчиненностиОрганизации КАК СтруктураОрганизации
ГДЕ
СтруктураОрганизации.ВедомственнаяОрганизация = &вхОрганизация
ИНДЕКСИРОВАТЬ ПО
Организация
;
Наличие индекса позволит платформе 1С мгновенно находить соответствия при выполнении ЛЕВОГО СОЕДИНЕНИЯ в последующих частях запроса.
Ниже приведен полный текст функции, которая корректно получает данные и может быть использована для построения деревьев или иерархических списков в интерфейсе:
&НаСервере
Функция ПолучитьДанныеПодчиненности(Знач ПараметрОрганизация)
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Регистр.Организация КАК Организация
|ПОМЕСТИТЬ ВТ_СписокОрганизаций
|ИЗ
| РегистрСведений.СтруктураПодчиненностиОрганизации КАК Регистр
|ГДЕ
| Регистр.ВедомственнаяОрганизация = &ПараметрОрганизация
|ИНДЕКСИРОВАТЬ ПО
| Организация
|;
|
|ВЫБРАТЬ
| ВТ.Организация КАК Организация,
| КОЛИЧЕСТВО(Подчиненные.Организация) КАК КоличествоПодчиненных
|ИЗ
| ВТ_СписокОрганизаций КАК ВТ
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.СтруктураПодчиненностиОрганизации КАК Подчиненные
| ПО ВТ.Организация = Подчиненные.ВедомственнаяОрганизация
|СГРУППИРОВАТЬ ПО
| ВТ.Организация";
Запрос.УстановитьПараметр("ПараметрОрганизация", ПараметрОрганизация);
Возврат Запрос.Выполнить().Выгрузить();
КонецФункции
Что мы сделали в этом коде:
ВТ_СписокОрганизаций, отобрав прямых подчиненных для переданной организации.ЛЕВОЕ СОЕДИНЕНИЕ этой таблицы с тем же регистром, чтобы проверить наличие «внуков» (следующего уровня иерархии).КОЛИЧЕСТВО(Подчиненные.Организация), чтобы получить 0 для конечных узлов дерева.Использование этого подхода гарантирует стабильную работу ваших отчетов и обработок, исключая появление пустых строк (NULL) там, где пользователь ожидает увидеть конкретные данные.