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