Как составить запрос для выборки новых клиентов за выбранный период в 1С?

Программист 1С v8.3 (Управляемые формы) 1С:Управление торговлей Управленческий учет Торговля и дистрибуция
← На главную

В аналитических отчетах часто возникает задача выделить контрагентов, которые совершили свою самую первую покупку в заданном интервале дат (новых клиентов). Главный критерий такого отбора: за выбранный период оборот по клиенту должен присутствовать, а за все время до начала этого периода оборотов и отгрузок быть не должно.

Разберем типичные ошибки при реализации такого алгоритма, рассмотрим различные варианты построения запросов на платформе 1С:Предприятие и выясним, какой подход обеспечивает наибольшее быстродействие.

Ошибки при обращении к таблицам документов

Попытка строить выборку напрямую по таблице Документ.РеализацияТоваровУслуг несет в себе несколько архитектурных рисков:

  1. Игнорирование статуса проведения: необходимо вручную проверять условие РеализацияТоваровУслуг.Проведен = ИСТИНА, иначе в выборку попадут не проведенные или помеченные на удаление документы.
  2. Неполнота данных: факт продажи может отражаться не только документом реализации, но и чеками ККМ, отчетами о розничных продажах, актами выполненных работ или корректировками.
  3. Низкая скорость выполнения: таблица документов не содержит агрегированных итогов и специализированных индексов по суммам и оборотам в разрезе контрагентов.

Оптимальным источником данных для таких задач всегда является регистр накопления РегистрНакопления.Продажи (или соответствующий регистр подсистемы продаж в используемой конфигурации).

Вариант 1. Соединение двух виртуальных таблиц оборотов (Рекомендуемый)

Рассмотрим по шагам наиболее производительный и надежный способ. Сначала во временную таблицу отбираются клиенты, имевшие продажи в анализируемом периоде. Затем выбираются клиенты с покупками до начала периода, и при помощи левого соединения исключаются те контрагенты, которые покупали ранее.

Посмотрим на пример текста запроса:


ВЫБРАТЬ РАЗЛИЧНЫЕ
    ПродажиТекущие.Контрагент КАК Контрагент
ПОМЕСТИТЬ ВТ_КлиентыПериода
ИЗ
    РегистрНакопления.Продажи.Обороты(&НачалоПериода, &КонецПериода, , ) КАК ПродажиТекущие

ИНДЕКСИРОВАТЬ ПО
    Контрагент
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ РАЗЛИЧНЫЕ
    ПродажиПрошлые.Контрагент КАК Контрагент
ПОМЕСТИТЬ ВТ_КлиентыПрошлые
ИЗ
    РегистрНакопления.Продажи.Обороты(, &ГраницаПрошлыхПродаж, , ) КАК ПродажиПрошлые

ИНДЕКСИРОВАТЬ ПО
    Контрагент
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    ВТ_КлиентыПериода.Контрагент КАК Контрагент
ИЗ
    ВТ_КлиентыПериода КАК ВТ_КлиентыПериода
        ЛЕВОЕ СОЕДИНЕНИЕ ВТ_КлиентыПрошлые КАК ВТ_КлиентыПрошлые
        ПО ВТ_КлиентыПериода.Контрагент = ВТ_КлиентыПрошлые.Контрагент
ГДЕ
    ВТ_КлиентыПрошлые.Контрагент ЕСТЬ NULL

Проанализируем важные детали этого решения:

  1. Индексация временных таблиц: директива ИНДЕКСИРОВАТЬ ПО Контрагент создает индекс по ключевому полю связи. Это исключает тяжелое полное сканирование (Table Scan) временных таблиц в оперативной памяти СУБД.
  2. Учет границ периода: параметры виртуальной таблицы Обороты включают обе границы. Чтобы продажи на дату начала периода не попали во вторую таблицу, параметр &ГраницаПрошлыхПродаж в коде 1С необходимо передавать через объект Новый Граница(НачалоПериода, ВидГраницы.Исключая) либо отступать на одну секунду назад (КонецДня(НачалоПериода - 1)).
  3. Использование итогов: виртуальная таблица Обороты считывает предрассчитанные итоги регистра, что в разы быстрее выборки из физических таблиц движений.

Вариант 2. Группировка и условие в секции ИМЕЮЩИЕ

Разберем альтернативный способ получения списка новых покупателей, в котором за один проход вычисляется минимальная дата движения по каждому покупателю.

Посмотрим на пример запроса:


ВЫБРАТЬ
    Продажи.Контрагент КАК Контрагент,
    МИНИМУМ(Продажи.Период) КАК ДатаПервойПродажи
ИЗ
    РегистрНакопления.Продажи КАК Продажи
СГРУППИРОВАТЬ ПО
    Продажи.Контрагент
ИМЕЮЩИЕ
    МИНИМУМ(Продажи.Период) МЕЖДУ &НачалоПериода И &КонецПериода

Особенности и ограничения данного метода:

  1. Полное сканирование регистра: запрос обращается к реальной таблице движений РегистрНакопления.Продажи без ограничения начальной даты в секции ГДЕ. СУБД вынуждена просматривать всю историю базы с момента начала учета, чтобы вычислить истинный МИНИМУМ(Продажи.Период).
  2. Нагрузка на СУБД: если в базе накоплены миллионы записей движений за много лет, данный запрос вызовет заметные задержки и блокировки.

Вариант 3. Получение даты первой продажи через периодичность виртуальной таблицы

Если требуется вывести не только список клиентов, но и точные даты первой отгрузки, можно использовать виртуальную таблицу с периодичностью День или Регистратор:


ВЫБРАТЬ
    Т.Контрагент КАК Контрагент,
    МИНИМУМ(Т.ПериодДень) КАК ДатаПервойПродажи
ИЗ
    РегистрНакопления.Продажи.Обороты(, &КонецПериода, День, ) КАК Т
СГРУППИРОВАТЬ ПО
    Т.Контрагент
ИМЕЮЩИЕ
    МИНИМУМ(Т.ПериодДень) >= &НачалоПериода

Обратите внимание: в виртуальной таблице Обороты при указании периодичности поле периода получает суффикс интервала, например ПериодДень, ПериодМесяц или ПериодСекунда, обращение к простому полю Период вызовет синтаксическую ошибку.

Рекомендации по оптимизации для высоконагруженных систем

Если база данных содержит миллионы записей, а отчет по новым клиентам формируется регулярно, рекомендуется применить следующие архитектурные оптимизации:

  1. Хранение даты первой продажи: создайте отдельный непериодический регистр сведений или реквизит в справочнике Справочник.Контрагенты (например, ДатаПервойПродажи). Значение заполняется при проведении первой реализации клиенту. В таком случае запрос сводится к элементарному отбору по реквизиту с индексом.
  2. Передача фильтра в виртуальную таблицу: во втором запросе первого варианта (выборка старых клиентов) передавайте условие по измерению Контрагент В (&МассивКлиентовПериода) или соединяйте с временной таблицей, чтобы платформа не считывала историю по всем когда-либо существовавшим контрагентам базы.
← На главную