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