В практике разработчика 1С часто возникают задачи, связанные с анализом поведения клиентов. Одна из типичных ситуаций: необходимо выделить сегмент «неэффективных» посещений. Представим, что у нас есть справочник Контрагенты и подчиненный ему справочник ПосещенияМагазина. Нам нужно получить список тех клиентов, которые заходили к нам (возможно, многократно), но так и не совершили ни одной покупки за всю историю. При этом для анализа причин нам нужна не просто информация о факте, а данные именно из последней строки посещения.
Проблема осложняется тем, что если клиент совершил хотя бы одну покупку (признак — заполненный реквизит ДатаОплаты), мы должны полностью исключить его из выборки, даже если у него было десять других посещений без оплаты. В этой статье мы подробно разберем, как реализовать такую логику с помощью механизмов запросов 1С.
Прежде чем приступать к написанию кода, проанализируем структуру данных и логические фильтры. Нам необходимо выполнить три условия:
ДатаОплаты заполнена, этот контрагент нам не интересен.ПосещенияМагазина, чтобы прочитать его специфические реквизиты (например, комментарий менеджера или цель визита).Наиболее надежный и производительный способ решения такой задачи — использование временных таблиц. Это позволит нам разделить сложную логику на простые и понятные этапы. Рассмотрим этот процесс по шагам.
Шаг 1: Отбор «удачливых» контрагентов
Сначала выясним, кто из клиентов совершил хотя бы одну покупку. Мы создадим список таких контрагентов, чтобы в дальнейшем исключить их из основного запроса.
ВЫБРАТЬ РАЗЛИЧНЫЕ
ПосещенияМагазина.Владелец КАК Контрагент
ПОМЕСТИТЬ ВТ_КупившиеКонтрагенты
ИЗ
Справочник.ПосещенияМагазина КАК ПосещенияМагазина
ГДЕ
ПосещенияМагазина.ДатаОплаты > ДАТАВРЕМЯ(1, 1, 1)
;
Шаг 2: Поиск последнего посещения для остальных клиентов
Теперь мы выберем всех контрагентов, которых нет в нашей первой таблице ВТ_КупившиеКонтрагенты, и найдем для каждого из них максимальную дату посещения. Проанализируем ситуацию: нам важно использовать ЛЕВОЕ СОЕДИНЕНИЕ или конструкцию НЕ В.
ВЫБРАТЬ
ПосещенияМагазина.Владелец КАК Контрагент,
МАКСИМУМ(ПосещенияМагазина.ДатаПосещения) КАК МаксДата
ПОМЕСТИТЬ ВТ_ПоследниеВизиты
ИЗ
Справочник.ПосещенияМагазина КАК ПосещенияМагазина
ГДЕ
НЕ ПосещенияМагазина.Владелец В (ВЫБРАТЬ Контрагент ИЗ ВТ_КупившиеКонтрагенты)
СГРУППИРОВАТЬ ПО
ПосещенияМагазина.Владелец
;
Шаг 3: Получение финальных данных
На заключительном этапе мы соединяем нашу временную таблицу с исходным справочником ПосещенияМагазина. Соединение выполняется по двум полям: Владелец и ДатаПосещения. Это позволит нам получить ссылку на конкретный элемент и все его реквизиты.
ВЫБРАТЬ
ПоследниеВизиты.Контрагент,
Посещения.Ссылка КАК ПоследнееПосещение,
Посещения.Комментарий,
Посещения.Менеджер
ИЗ
ВТ_ПоследниеВизиты КАК ПоследниеВизиты
ВНУТРЕННЕЕ СОЕДИНЕНИЕ Справочник.ПосещенияМагазина КАК Посещения
ПО ПоследниеВизиты.Контрагент = Посещения.Владелец
И ПоследниеВизиты.МаксДата = Посещения.ДатаПосещения
;
Если задача кажется слишком громоздкой для временных таблиц, можно попробовать решить её одним запросом, используя агрегатные функции и секцию ИМЕЮЩИЕ. Посмотрим на пример, как отобрать контрагентов, у которых максимальная дата оплаты является пустой.
ВЫБРАТЬ
ПосещенияМагазина.Владелец КАК Контрагент,
МАКСИМУМ(ПосещенияМагазина.ДатаПосещения) КАК ДатаПоследнегоВизита
ИЗ
Справочник.ПосещенияМагазина КАК ПосещенияМагазина
СГРУППИРОВАТЬ ПО
ПосещенияМагазина.Владелец
ИМЕЮЩИЕ
МАКСИМУМ(ПосещенияМагазина.ДатаОплаты) = ДАТАВРЕМЯ(1, 1, 1)
Выясним причину, почему этот способ может быть опасен: если в будущем в логику добавится NULL (например, при внешнем соединении), условие с пустой датой может не сработать. Кроме того, этот запрос вернет только дату, но не позволит вытащить другие реквизиты строки без повторного соединения с таблицей.
При работе с такими запросами следует учитывать несколько критически важных моментов:
0001-01-01. В запросах ее лучше передавать через параметр или использовать конструкцию ДАТАВРЕМЯ(1, 1, 1). Помните, что NULL и пустая дата — это разные сущности. NULL возникает там, где записи физически не существует (например, при левом соединении).ВТ_ПоследниеВизиты дополнительно выбирать МАКСИМУМ(Ссылка) и в финальном запросе соединяться именно по ссылке.Владелец и ДатаПосещения проиндексированы. В подчиненных справочниках поле Владелец индексируется автоматически, а вот для даты это нужно проверить в конфигураторе.Разберем еще один момент: почему мы используем именно ВНУТРЕННЕЕ СОЕДИНЕНИЕ в финальном шаге? Поскольку в ВТ_ПоследниеВизиты мы уже отобрали только тех, кто нам нужен, внутреннее соединение отсечет лишние данные и будет работать быстрее, чем левое.
Таким образом, комбинируя методы исключения множеств (тех, кто купил) и методы получения среза последних через группировку по максимальной дате, мы получаем эффективный инструмент для аналитики посещений.