При разработке сложных отчетов или механизмов ценообразования часто возникает задача собрать данные из разных табличных частей одного и того же объекта (справочника или документа). Одной из типичных проблем является использование оператора ОБЪЕДИНИТЬ ВСЕ, который при неаккуратном подходе создает "дырявые" таблицы с большим количеством пустых значений NULL. В этой статье мы разберем, почему это происходит и как правильно спроектировать запрос, чтобы получить чистый и производительный результат.
Рассмотрим ситуацию: у нас есть справочник УсловияПредоставленияСкидокНаценок. В нем содержатся две табличные части: ВремяДействия (где указаны часы работы скидки) и Получатели (где указаны конкретные магазины или контрагенты). Нам нужно получить таблицу, в которой в одной строке будут указаны и получатель, и время действия.
Если мы используем конструкцию ОБЪЕДИНИТЬ ВСЕ, мы фактически "склеиваем" две разные таблицы по вертикали. Поскольку в первой таблице нет поля Получатель, а во второй — полей ВремяНачала и ВремяОкончания, нам приходится подставлять NULL. В итоге результат выглядит как шахматная доска, где полезные данные чередуются с пропусками. Это затрудняет дальнейшую обработку данных в коде или в компоновке данных.
Если нам нужно сопоставить данные из разных табличных частей одного и того же элемента справочника, наиболее логичным шагом будет использование связи по полю Ссылка. Проанализируем, как это реализовать на практике.
Посмотрим на пример запроса, который позволяет соединить данные горизонтально:
ВЫБРАТЬ
УсловияПолучатели.Получатель КАК Получатель,
ВременаДействия.ДеньНедели КАК ДеньНедели,
ВременаДействия.ВремяНачала КАК ВремяНачала,
ВременаДействия.ВремяОкончания КАК ВремяОкончания
ПОМЕСТИТЬ ВТ_ИтоговыеДанные
ИЗ
Справочник.УсловияПредоставленияСкидокНаценок.ВремяДействия КАК ВременаДействия
ЛЕВОЕ СОЕДИНЕНИЕ Справочник.УсловияПредоставленияСкидокНаценок.Получатели КАК УсловияПолучатели
ПО ВременаДействия.Ссылка = УсловияПолучатели.Ссылка
ГДЕ
ВременаДействия.ДеньНедели = &мДень
И &мТекВремя МЕЖДУ ВременаДействия.ВремяНачала И ВременаДействия.ВремяОкончания
И УсловияПолучатели.Получатель = &мПолучатель
В данном случае мы используем общую Ссылка (владельца табличных частей) для того, чтобы система "поняла", какие строки времени соответствуют каким получателям. Важный нюанс: такой подход эффективен, когда данных немного. Однако здесь кроется ловушка "Декартова произведения", которую мы разберем ниже.
Если ваша задача — просто проверить, действует ли скидка для конкретного получателя, и при этом получатель передан в запрос как параметр &мПолучатель, вам вообще не нужно обращаться ко второй табличной части в секции ИЗ. Мы можем просто "пробросить" этот параметр в результат запроса.
Проанализируем этот изящный способ:
ВЫБРАТЬ
&мПолучатель КАК Получатель,
ВременаДействия.ДеньНедели КАК ДеньНедели,
ВременаДействия.ВремяНачала КАК ВремяНачала,
ВременаДействия.ВремяОкончания КАК ВремяОкончания
ИЗ
Справочник.УсловияПредоставленияСкидокНаценок.ВремяДействия КАК ВременаДействия
ГДЕ
ВременаДействия.ДеньНедели = &мДень
И &мТекВремя МЕЖДУ ВременаДействия.ВремяНачала И ВременаДействия.ВремяОкончания
И ВременаДействия.Ссылка В (
ВЫБРАТЬ Ссылка
ИЗ Справочник.УсловияПредоставленияСкидокНаценок.Получатели
ГДЕ Получатель = &мПолучатель
)
Этот метод работает быстрее, так как мы не строим соединение таблиц "каждый с каждым", а лишь фильтруем основную таблицу по условию наличия записи во второй таблице через подзапрос.
Разберем ситуацию подробнее. Представим, что у одного условия скидки в таблице ВремяДействия заполнено 5 строк (на каждый рабочий день), а в таблице Получатели указано 10 магазинов.
Если мы выполним прямое СОЕДИНЕНИЕ по полю Ссылка, СУБД создаст комбинацию всех строк. В итоге мы получим не 15 строк (как при объединении), а 50 строк (5 умножить на 10). Это явление называется Декартовым произведением.
Как этого избежать?
ГДЕ Ссылка В (...).ПОМЕСТИТЬ), и только потом соединяйте.ВЫБРАТЬ РАЗЛИЧНЫЕ, если дубликаты строк не несут смысловой нагрузки.В ситуациях, когда вы все же вынуждены использовать ОБЪЕДИНИТЬ ВСЕ или ЛЕВОЕ СОЕДИНЕНИЕ, в результирующей таблице неизбежно появятся NULL. В 1С это не просто "пустое значение", это отсутствие значения как такового, что может привести к ошибкам при арифметических операциях или выводе в отчет.
Рассмотрим применение функции ЕСТЬNULL() для очистки данных:
ВЫБРАТЬ
ЕСТЬNULL(ВременаДействия.ВремяНачала, ДАТАВРЕМЯ(1, 1, 1)) КАК ВремяНачала,
ЕСТЬNULL(УсловияПолучатели.Получатель, ЗНАЧЕНИЕ(Справочник.Магазины.ПустаяСсылка)) КАК Получатель
ИЗ ...
Выясним причину, почему это важно: функция ЕСТЬNULL позволяет заменить неопределенное состояние на типизированное пустое значение (пустую ссылку, пустую дату или ноль). Это гарантирует стабильную работу кода при обходе выборки.
Для крупных баз данных (ERP, УТ 11, Розница) прямые запросы к табличным частям могут быть медленными. Разберем по шагам правильную стратегию построения запроса:
ВТ_ПодходящиеСсылки.ВТ_ПодходящиеСсылки.Такой подход позволяет избежать избыточного сканирования огромных таблиц и делает ваш код профессиональным и масштабируемым.