При разработке сложных отчетов на платформе 1С с использованием Системы компоновки данных (СКД) разработчики часто сталкиваются с необходимостью объединения информации из разных источников. Стандартный механизм связи наборов данных в СКД — мощный инструмент, который позволяет избежать классической проблемы СУБД: задвоения сумм по ресурсам при левом соединении «один ко многим». Однако этот механизм имеет жесткое ограничение: все созданные связи между наборами данных всегда объединяются по логическому условию И. Настроить связь по условию ИЛИ напрямую в интерфейсе конструктора невозможно.
Рассмотрим классическую задачу. У нас есть Набор 1, содержащий поле ЗаказКлиента (которое в зависимости от хозяйственной операции может быть либо Договором контрагента, либо Документом расчетов). И есть Набор 2, где присутствуют два отдельных поля: Ссылка (документ) и Договор (договор). Нам необходимо связать эти наборы так, чтобы данные из второго набора подтягивались, если ЗаказКлиента совпадает либо с договором, либо с документом.
Если мы укажем две связи в СКД, система попытается найти записи, где одновременно выполняются оба условия, что логически невозможно. В этой статье мы подробно разберем, почему стандартные попытки обхода не работают, и проанализируем четыре эффективных архитектурных решения этой проблемы.
Прежде чем перейти к решениям, разберем типичные ошибки и ограничения платформы, с которыми сталкиваются разработчики при попытке реализовать такую связь напрямую:
ЗаказКлиента = Договор, вторая: ЗаказКлиента = Ссылка), СКД транслирует это в SQL-запрос или внутренний алгоритм объединения как Условие1 И Условие2. Результат всегда будет пустым, так как одно поле не может быть одновременно и ссылкой на справочник договоров, и ссылкой на документ.ТИПЗНАЧЕНИЯ(ЗаказКлиента) = ТИП(Документ.РеализацияТоваровУслуг)) часто приводит к ошибкам компиляции СКД. Платформа накладывает жесткое ограничение: в выражении связи можно использовать только поля источника (левой таблицы). Обращаться к полям приемника (правой таблицы) внутри сложных логических условий нельзя.Выяснив причины ограничений, перейдем к разбору работающих практических альтернатив.
Проанализируем наиболее элегантный и производительный способ решения задачи. Вместо того чтобы заставлять СКД выполнять сложную логику «ИЛИ» на стыке двух наборов, мы подготовим данные во втором наборе (приемнике) таким образом, чтобы связь шла по одному-единственному общему полю.
Для этого в запросе Набора 2 мы развернем альтернативные условия связи в строки с помощью оператора ОБЪЕДИНИТЬ ВСЕ. Мы создадим виртуальное поле связи, в которое поместим и договоры, и документы.
Посмотрим на пример построения такого запроса для Набора 2:
ВЫБРАТЬ
Реализация.Ссылка КАК ПолеСвязи,
Реализация.Ссылка КАК ДокументСсылка,
Реализация.ДоговорКонтрагента КАК Договор,
Реализация.СуммаДокумента КАК СуммаОплаты
ИЗ
Документ.РеализацияТоваровУслуг КАК Реализация
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ
Реализация.ДоговорКонтрагента КАК ПолеСвязи,
Реализация.Ссылка КАК ДокументСсылка,
Реализация.ДоговорКонтрагента КАК Договор,
Реализация.СуммаДокумента КАК СуммаОплаты
ИЗ
Документ.РеализацияТоваровУслуг КАК Реализация
Что мы сделали в этом запросе? Мы получили дублирующиеся строки для каждой реализации: в первой строке в поле ПолеСвязи находится ссылка на сам документ, во второй строке — ссылка на договор. Теперь в настройках связей наборов данных СКД нам достаточно указать всего одну связь:
ЗаказКлиентаПолеСвязиЭтот метод работает чрезвычайно быстро, не перегружает оперативную память сервера приложений 1С и полностью имитирует логику соединения по условию «ИЛИ».
Если наборы данных имеют слишком сложную структуру, или выгружаются из разных СУБД (или внешних источников данных), и объединять их внутри одного SQL-запроса нецелесообразно, можно прибегнуть к возможностям самого компоновщика СКД. Разберем этот метод по шагам:
ОбъединенныйНабор.Набор_Запрос_Договоры и Набор_Запрос_Документы.Набор_Запрос_Договоры) выполним левое соединение таблиц по договору непосредственно на уровне СУБД.Набор_Запрос_Документы) свяжем эти же таблицы по документу расчетов.При выполнении отчета СКД автоматически сгруппирует строки из обоих наборов-запросов, у которых совпадают пути к данным. При этом ресурсы (суммы) будут рассчитаны корректно, без дублирования, так как система компоновки самостоятельно выполнит постобработку результатов объединения.
Рассмотрим вариант, который применил автор темы для быстрого решения своей проблемы. Суть метода заключается в том, чтобы «поднять» аналитику связи из подчиненных таблиц на уровень первого набора данных.
Если изначально в Наборе 1 поле ЗаказКлиента было составного типа и мы не знали заранее, что в нем хранится, мы можем модифицировать запросы следующим образом:
В запрос Набора 1 добавляем явное определение типов или подтягиваем сопутствующую информацию:
ВЫБРАТЬ
Продажи.ЗаказКлиента КАК ЗаказКлиента,
ВЫБОР
КОГДА Продажи.ЗаказКлиента ССЫЛКА Справочник.ДоговорыКонтрагентов
ТОГДА Продажи.ЗаказКлиента
ИНАЧЕ НЕОПРЕДЕЛЕНО
КОНЕЦ КАК ДоговорДляСвязи,
ВЫБОР
КОГДА Продажи.ЗаказКлиента ССЫЛКА Документ.ЗаказКлиента
ТОГДА Продажи.ЗаказКлиента
ИНАЧЕ НЕОПРЕДЕЛЕНО
КОНЕЦ КАК ДокументДляСвязи,
Продажи.СуммаОбороты КАК Сумма
ИЗ
РегистрНакопления.Продажи.Обороты КАК Продажи
В запрос Набора 2 выводим аналогичные поля связи. После этого на вкладке «Связи наборов данных» мы можем настроить связь по двум отдельным строкам, но уже с использованием параметров и условий, так как они будут четко разделены по типам данных. Либо, как сделал автор, мы можем перенести логику определения связи непосредственно во второй набор, чтобы связать таблицы по одному простому ключу. Это исключает неопределенность при сопоставлении разнородных типов данных.
Иногда возникают ситуации, когда Набор 2 представляет собой внешнюю таблицу значений (например, загруженную из файла Excel или полученную через Web-сервис), структуру запроса которой мы не можем изменить на стороне СУБД. В этом случае применим метод виртуального связывания через константу:
1 КАК КлючСвязи.КлючСвязи со значением 1 для всех строк.КлючСвязи. Это действие заставит СКД соединить каждую строку первой таблицы с каждой строкой второй таблицы в оперативной памяти (аналог SQL-оператора CROSS JOIN).ВычисляемоеПолеСовпадения = Истина.Вычисляемое поле в СКД при этом настраивается следующим выражением:
Выбор
Когда ЗаказКлиента = Договор Или ЗаказКлиента = СсылкаДокумента
Тогда Истина
Иначе Ложь
Конец
Важное предупреждение: Этот метод является крайне ресурсоемким. Если в вашем первом наборе будет 10 000 строк, и во втором наборе тоже 10 000 строк, в памяти сервера будет создана таблица объемом в 100 000 000 (сто миллионов) строк перед тем, как сработает фильтр СКД. Используйте этот обходной путь исключительно для работы с небольшими объемами данных (до нескольких сотен строк).
Вернемся к вопросу, затронутому в обсуждении на форуме: почему при связи двух наборов данных в СКД ресурсы рассчитываются корректно, а в случае обычного левого соединения в одном пакете запросов суммы начинают «двоиться»?
Разберем этот механизм изнутри:
СУММА() на поле из левой таблицы, СУБД послушно складывает все дубли, искажая итоговый финансовый результат.Именно поэтому использование связей наборов данных в СКД является предпочтительным архитектурным решением для построения сложных отчетов со множественными источниками данных и разнородной аналитикой.