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