В процессе разработки и кастомизации конфигураций на платформе «1С:Предприятие 8» (таких как «Управление торговлей 11», «ERP Управление предприятием 2» или «Комплексная автоматизация 2») часто возникает задача определения даты или момента последней активности клиента по его карте лояльности. На первый взгляд задача кажется тривиальной, однако при детальном рассмотрении мы сталкиваемся с серьезными архитектурными препятствиями.
Основная сложность заключается в том, что карты лояльности могут фигурировать в самых разных документах: от фактических продаж (Документ.РеализацияТоваровУслуг, Документ.ЧекККМ) до предварительных намерений или черновиков (Документ.ЗаказКлиента, Документ.КоммерческоеПредложениеКлиенту). При этом единого регистра, который бы регистрировал абсолютно все эти события, в типовых конфигурациях нет. Давайте вместе подробно разберем, как решить эту задачу максимально эффективно, быстро и без ущерба для производительности системы.
Проанализируем сначала идею использования регистров накопления. В типовых решениях 1С существует специализированный регистр «БонусныеБаллы». Он действительно хранит информацию о начислениях и списаниях бонусов в разрезе карт лояльности. Однако использовать его для получения даты последней операции нельзя по следующим причинам:
Коммерческое предложение клиенту или Заказ клиента (в статусе черновика), вообще не делают движений по регистрам накопления.Ввод остатков или отмененные заказы, в которых тем не менее была указана карта, также не попадут в регистры, хотя для бизнеса факт предъявления карты в этих операциях может быть важен.Таким образом, если перед нами стоит задача зафиксировать любое упоминание карты лояльности в системе, нам придется обращаться напрямую к таблицам документов. Выясним, как сделать это правильно.
Если ваша конфигурация находится на доработке (снята со строгой поддержки) и вы можете добавлять новые объекты метаданных, то использование Критерия отбора — это самый красивый, производительный и правильный с точки зрения архитектуры 1С способ решения проблемы.
Критерий отбора позволяет платформе на уровне СУБД автоматически построить и поддерживать индекс по определенному реквизиту во всех связанных таблицах. Разберем по шагам, как его настроить:
СвязанныеДокументыПоКартеЛояльности.СправочникСсылка.КартыЛояльности.ЧекККМ.КартаЛояльности, ЗаказКлиента.КартаЛояльности, КоммерческоеПредложениеКлиенту.КартаЛояльности и т.д.).После сохранения конфигурации СУБД создаст оптимальные индексы. Теперь мы можем написать простой и невероятно быстрый запрос для получения даты последней операции по конкретной карте лояльности:
ВЫБРАТЬ ПЕРВЫЕ 1
Критерий.Ссылка КАК Документ,
Критерий.Ссылка.Дата КАК ДатаПоследнейОперации
ИЗ
КритерийОтбора.СвязанныеДокументыПоКартеЛояльности(&КартаЛояльности) КАК Критерий
УПОРЯДОЧИТЬ ПО
Критерий.Ссылка.Дата УБЫВ
Преимущества данного способа: запрос выполняется практически мгновенно, так как платформа обращается к уже подготовленному СУБД индексу. Нам не нужно вручную прописывать сложные соединения или объединения таблиц.
Что делать, если конфигурация находится на замке (на строгой поддержке фирмы "1С") и создавать новые критерии отбора запрещено правилами поддержки? В этом случае нам придется писать запрос к таблицам документов напрямую.
Рассмотрим типичную ошибку начинающих разработчиков. Часто пытаются объединить справочник карт лояльности с таблицами документов через ЛЕВОЕ СОЕДИНЕНИЕ. Так делать категорически нельзя! Левое соединение с десятком «тяжелых» таблиц документов (таких как Реализация товаров и услуг или Заказ клиента) создаст колоссальную нагрузку на сервер SQL и гарантированно приведет к зависанию системы на реальной базе данных с сотнями тысяч документов.
Вместо соединений мы должны использовать конструкцию ОБЪЕДИНИТЬ ВСЕ (UNION ALL). Мы получим максимальную дату по каждому документу отдельно, а затем выберем максимум из полученного набора данных. Посмотрим на пример правильного запроса:
ВЫБРАТЬ
МАКСИМУМ(ВТ_Даты.Дата) КАК ДатаПоследнейОперации
ИЗ
(ВЫБРАТЬ
МАКСИМУМ(Док.Дата) КАК Дата
ИЗ
Документ.ЧекККМ КАК Док
ГДЕ
Док.КартаЛояльности = &КартаЛояльности
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ
МАКСИМУМ(Док.Дата) КАК Дата
ИЗ
Документ.ЗаказКлиента КАК Док
ГДЕ
Док.КартаЛояльности = &КартаЛояльности
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ
МАКСИМУМ(Док.Дата) КАК Дата
ИЗ
Документ.КоммерческоеПредложениеКлиенту КАК Док
ГДЕ
Док.КартаЛояльности = &КартаЛояльности
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ
МАКСИМУМ(Док.Дата) КАК Дата
ИЗ
Документ.РеализацияТоваровУслуг КАК Док
ГДЕ
Док.КартаЛояльности = &КартаЛояльности
) КАК ВТ_Даты
Почему этот запрос работает быстро? СУБД выполняет поиск по индексу для каждой конкретной таблицы отдельно. Так как мы ищем по конкретной карте лояльности, СУБД мгновенно находит нужные записи в каждой таблице, выбирает из них максимальную дату и затем быстро находит максимум из небольшого результирующего массива дат (в котором будет ровно столько строк, сколько видов документов мы опрашиваем).
Чтобы описанный во втором способе запрос работал быстро, реквизит КартаЛояльности в структуре каждого из опрашиваемых документов обязательно должен быть проиндексирован.
Проверить это можно в конфигураторе: откройте свойства реквизита КартаЛояльности нужного документа и убедитесь, что свойство «Индексировать» установлено в значение Индексировать или Индексировать с дополнительным упорядочиванием. В типовых конфигурациях 1С разработчики обычно индексируют этот реквизит для основных документов, но если вы используете самописные документы, обязательно выполните это действие вручную.
Проанализируем еще один важный момент. Если вы планируете использовать полученную точку во времени не просто для вывода текстовой информации на форму, а для проведения расчетов, отката транзакций или выстраивания жесткой последовательности документов, сравнения по простой дате может быть недостаточно.
В одну и ту же секунду в базе данных может быть проведено несколько документов. Обычное сравнение по дате (которая имеет точность до секунды) может привести к неоднозначности. В таких случаях в запросе вместо Дата лучше выбирать МоментВремени. Момент времени в 1С гарантированно уникален, так как состоит из даты и уникальной ссылки на сам документ:
ВЫБРАТЬ ПЕРВЫЕ 1
ВТ_Моменты.Момент КАК МоментПоследнейОперации
ИЗ
(ВЫБРАТЬ
Док.МоментВремени КАК Момент
ИЗ
Документ.ЧекККМ КАК Док
ГДЕ
Док.КартаЛояльности = &КартаЛояльности
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ
Док.МоментВремени КАК Момент
ИЗ
Документ.ЗаказКлиента КАК Док
ГДЕ
Док.КартаЛояльности = &КартаЛояльности
) КАК ВТ_Моменты
УПОРЯДОЧИТЬ ПО
ВТ_Моменты.Момент УБЫВ
Использование Момента времени исключит любые коллизии при совпадении времени документов с точностью до секунды.
Подводя итог, отметим: если у вас есть возможность вносить изменения в структуру метаданных конфигурации — всегда выбирайте Способ 1 (Критерии отбора). Если же вам приходится работать в жестких рамках закрытой конфигурации, используйте Способ 2 (ОБЪЕДИНИТЬ ВСЕ), не забывая контролировать наличие индексов по реквизиту карты в документах.