Как в СКД разделить номенклатуру или клиентов по частоте заказов: разовые за все время, первичные и повторные в периоде?

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

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

  1. Разовый (нулевой) — заказ зафиксирован ровно один раз за всю историю информационной базы.
  2. Первичный (один раз) — заказ оформлен ровно один раз в рамках выбранного пользователем периода (но в базе в целом могут быть и другие заказы).
  3. Повторный (много) — заказ оформлен более одного раза за выбранный период.

Сложность заключается в совмещении двух разных контекстов выборки: глобального (вся история базы без фильтра по датам) и локального (выбранный интервал отчета). Разберем, как правильно реализовать подобный расчет в схеме компоновки данных (СКД) с помощью языка запросов и встроенных механизмов платформы 1С.

Анализ структуры данных и типичные подводные камни

В практических задачах источником информации часто выступают регистры сведений или регистры накопления заказов. Рассмотрим распространенную структуру независимого непериодического регистра сведений Заказы со следующими полями:

  1. Измерение ИдентификаторЗаказа.
  2. Реквизиты Клиент, Номенклатура, ДокументВыполнения, ДатаВыполнения.

Если мы просто установим условие ГДЕ ДатаВыполнения МЕЖДУ &НачалоПериода И &КонецПериода в основном запросе, система полностью отсечет историю вне указанных дат. В результате невозможно будет отличить объект, купленный один раз за все время, от объекта, купленного один раз в этом месяце, но регулярно покупавшегося в прошлом году.

Также важно учитывать поведение агрегатной функции КОЛИЧЕСТВО(РАЗЛИЧНЫЕ ...):

  1. Функция игнорирует значение NULL, но подсчитывает пустые ссылки, пустые строки и значение НЕОПРЕДЕЛЕНО как валидные уникальные значения.
  2. В ветке ИНАЧЕ конструкции ВЫБОР необходимо возвращать именно NULL.
  3. Проверка заполненности реквизита зависит от его типа. Если поле ссылочное, незаполненное значение — это пустая ссылка (например, ЗНАЧЕНИЕ(Документ.ЗаказКлиента.ПустаяСсылка)). Если поле составного типа, то может встретиться и НЕОПРЕДЕЛЕНО.

Способ 1. Условное агрегирование в едином запросе (рекомендуемый)

Проанализируем оптимальный по быстродействию вариант. Вместо ограничения выборки в секции ГДЕ мы выбираем данные без жесткого отсечения периода (или с ограничением только по верхней границе), а разделение на общую историю и интервал отчета выполняем непосредственно внутри агрегатных функций.

Посмотрим на пример текста запроса для набора данных СКД:


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

ВЫБРАТЬ
    Данные.Номенклатура КАК Номенклатура,
    Данные.Клиент КАК Клиент,
    Данные.ЗаказовВсего КАК ЗаказовВсего,
    Данные.ЗаказовВПериоде КАК ЗаказовВПериоде,
    ВЫБОР
        КОГДА Данные.ЗаказовВПериоде > 1 ТОГДА "Повторный"
        КОГДА Данные.ЗаказовВПериоде = 1 И Данные.ЗаказовВсего = 1 ТОГДА "Нулевой (разовый)"
        КОГДА Данные.ЗаказовВПериоде = 1 И Данные.ЗаказовВсего > 1 ТОГДА "Первичный в периоде"
        ИНАЧЕ "Не заказывался в периоде"
    КОНЕЦ КАК КатегорияЗаказа
ИЗ
    ВТ_АгрегированныеДанные КАК Данные
ГДЕ
    Данные.ЗаказовВПериоде > 0

Разберем логику классификации в этом запросе:

  1. Условие ГДЕ Данные.ЗаказовВПериоде > 0 в итоговой выборке отсекает строки, по которым в выбранном интервале не было активности, оставляя только актуальные для отчета позиции.
  2. Если заказ оформлен в периоде больше одного раза — присваивается статус Повторный.
  3. Если заказ в периоде оформлен один раз, и при этом за всю историю базы у позиции ровно один заказ — присваивается статус Нулевой (разовый).
  4. Если заказ в периоде оформлен один раз, но общее количество заказов в базе больше единицы — присваивается статус Первичный в периоде.

Способ 2. Вычисление статуса через вычисляемые поля СКД

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

Разберем по шагам настройку схемы компоновки данных:

  1. В запросе набора данных оставляем получение количественных показателей (ЗаказовВсего и ЗаказовВПериоде), как в первой временной таблице предыдущего примера.
  2. Переходим на вкладку Вычисляемые поля.
  3. Добавляем новое поле СтатусЗаказов.
  4. В колонке Выражение указываем условие:
    
    Выбор 
        Когда ЗаказовВПериоде > 1 Тогда "Много" 
        Когда ЗаказовВПериоде = 1 И ЗаказовВсего = 1 Тогда "Нулевой" 
        Когда ЗаказовВПериоде = 1 Тогда "Один раз" 
        Иначе "Нет заказов" 
    Конец
    
  5. На вкладке Настройки используем вычисленное поле в группировках строк или колонок, а также выводим его в детальные записи.

Способ 3. Пакетный запрос с соединением таблиц периода и истории

Когда регистр содержит миллионы записей, полное сканирование всей таблицы может замедлить отчет. В этом случае эффективнее применить соединение двух независимых выборок:

  1. В первой временной таблице отбираем только те позиции номенклатуры (или клиентов), у которых были заказы строго за выбранный период (накладываем индексный фильтр по дате).
  2. Во второй выборке рассчитываем историю только по тем объектам, которые попали в первую таблицу.

Посмотрим на реализацию пакетного запроса:


ВЫБРАТЬ РАЗЛИЧНЫЕ
    РСЗаказы.Номенклатура КАК Номенклатура
ПОМЕСТИТЬ ВТ_НоменклатураПериода
ИЗ
    РегистрСведений.Заказы КАК РСЗаказы
ГДЕ
    РСЗаказы.ДатаВыполнения МЕЖДУ &НачалоПериода И &КонецПериода
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    РСЗаказы.Номенклатура КАК Номенклатура,
    КОЛИЧЕСТВО(РАЗЛИЧНЫЕ РСЗаказы.ИдентификаторЗаказа) КАК ЗаказовВПериоде
ПОМЕСТИТЬ ВТ_ДанныеПериода
ИЗ
    РегистрСведений.Заказы КАК РСЗаказы
ГДЕ
    РСЗаказы.ДатаВыполнения МЕЖДУ &НачалоПериода И &КонецПериода
СГРУППИРОВАТЬ ПО
    РСЗаказы.Номенклатура
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    РСЗаказы.Номенклатура КАК Номенклатура,
    КОЛИЧЕСТВО(РАЗЛИЧНЫЕ РСЗаказы.ИдентификаторЗаказа) КАК ЗаказовВсего
ПОМЕСТИТЬ ВТ_ДанныеВсего
ИЗ
    РегистрСведений.Заказы КАК РСЗаказы
ГДЕ
    РСЗаказы.Номенклатура В (ВЫБРАТЬ Номенклатура ИЗ ВТ_НоменклатураПериода)
СГРУППИРОВАТЬ ПО
    РСЗаказы.Номенклатура
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    Период.Номенклатура КАК Номенклатура,
    Период.ЗаказовВПериоде КАК ЗаказовВПериоде,
    Всего.ЗаказовВсего КАК ЗаказовВсего,
    ВЫБОР
        КОГДА Период.ЗаказовВПериоде > 1 ТОГДА "Повторный"
        КОГДА Период.ЗаказовВПериоде = 1 И Всего.ЗаказовВсего = 1 ТОГДА "Нулевой"
        ИНАЧЕ "Первичный"
    КОНЕЦ КАК Категория
ИЗ
    ВТ_ДанныеПериода КАК Период
    ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_ДанныеВсего КАК Всего
        ПО Период.Номенклатура = Всего.Номенклатура

В этом варианте фильтр ГДЕ Номенклатура В (...) предотвращает сканирование всей истории базы по позициям, которые не продавались в выбранном временном отрезке.

Оптимизация производительности регистра сведений

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

  1. Кластерный индекс в таких регистрах автоматически формируется только по измерениям.
  2. Если поля Клиент, Номенклатура или ДатаВыполнения находятся в секции Реквизиты, фильтрация и группировка по ним приведет к полному сканированию всей таблицы базы данных (Table Scan).
  3. Для обеспечения быстродействия необходимо либо перенести аналитические разрезы в Измерения регистра, либо включить для соответствующих реквизитов свойство Индексировать.
← На главную