Фильтр отбора в группе (ДК)¶
Этот фильтр работает только по данным доступным по формату ДК.
Фильтр отбора состоит из списка элементов задающих условия отбора или условия объединения других условий.
Пустой фильтр (т.е. не содержащий ни одного элемента) ни когда не совпадает ни с чем и результат его использования всегда пустой.
По своей структуре фильтр аналогичен условию отбора Универсальной Статистики - у них общая структура одного условия фильтра, общие логически и группирующие условия и многие операции.
Фильтр представляет из себя список элементарных условий объединяемых через "И".
Большинство условий позволяют сравнивать значение из основного ключа данных (задаётся в "a") как с константой (задаётся в "v") так и со значением из другого ключа данных (задаётся в "b").
Ознакомьтесь в разделе "Общие замечания" с особенностями сравнения чисел и дат при пустых или отсутствующих значениях.
Пример фильтра ДК¶
Адрес имеет 2 и меньше ошибки доставкиИрегион проживания один из RU01,RU05,RU40Идень рождения завтраИ (входи в группу abcИЛИвходит в группу xyz) И регион проживания совпадает с регионом заказа
[
{
"a" : "member.error.error" -- Адрес имеет 2 и меньше ошибки доставки
"op" : "<=",
"v" : "2"
},
{
"a" : "personal.region", -- регион проживания один из RU01,RU05,RU40
"op" : "in",
"v" : [ "RU01" ,"RU13" ,"RU40" ]
},
{
"a" : "personal.birthday:YD", -- день рождения завтра
"op" : "==",
"v" : "current +1 day",
},
{
"op" : "OR"
,"v" : [
{
"op" : "in_group", -- входит в группу abc
"v" : "abc",
},
{
"op" : "in_group", -- входит в группу xyz
"v" : "xyz",
}
]
],
{
"a" : "personal.region", -- регион проживания
"op" : "in",
"b" : "order.region"
}
]
Метки условий¶
Метки условий позволяют узнать каким условиям сработал фильтр.
Это можно использовать для тонкой персонализации писем рассылки и красивого оформления сложных отчётов из member.list (например колонка в отчёте в которую выводится значение метки будет содержать или не содержать какое-то слово в зависимости от того сработало условие или нет).
Меткой может быть дополнено любое условии кроме "ИЛИ" (формат не позволяет, но метками можно снабдить внутренние условия этого "ИЛИ").
{
<прочие условия фильтра>
"label" : "lalala" -- имя метки фильтра, обязательно если хотите отметить это условие
,"label.yes" : "yyy" -- не обязательно, значение метки при выполнении условия
,"label.no" : "nnn" -- не обязательно, значение метки при не выполнении условия
}
Если указано только label, то при выполнении условия значением метки становится её же имя (lalala=lalala).
Если указано не пустое label.yes, то при выполнении условия значением метки становится значение label.yes (lalala=yyy)
Если указано не пустое label.no, то при не выполнении условия значением метки становится значение label.no (lalala=nnn)
Если значение присваивается метке, которая уже имеет значение, то старое значение заменяется новым.
Набор меток по результатам фильтрации доступен:
-
для каждого адреса рассылки при персонализации текста письма - через объект member.LABEL.*
-
в вызове member.list в ответе - через параметр label
-
в вызове member.list в формате - через элемент { "label" : "lalala" }
Условия объединения в фильтре ДК¶
Для обеспечения приоритета условий используйте скобки, а не предположения какой у чего приоритет на основании "в таком-то языке так-то".
Условия вычисляются только до момента достаточного для принятия решения об их исходе.
Список условий ИЛИ прекращает вычисляться после первого же условия завершившегося положительно.
Список условий И прекращает вычислять после первого же условия завершившегося отрицательно.
Условие "И" в фильтре ДК¶
{
"op" : "!AND"
,"v" : [
массив описывающий элементы фильтра. "И" между элементами потом отрицание полученного результата
]
}
Условие "ИЛИ" в фильтре ДК¶
Действует между элементами прочих списков условий кроме основного списка фильтра и списка группировки.
{
"op" : "!OR"
,"v" : [
массив описывающий элементы фильтра. "ИЛИ" между элементами потом отрицание полученного результата
]
}
Вхождение в состав группы в фильтре ДК¶
Попадание в выборку Универсальной Статистики в фильтре ДК¶
{
"op" : "stat.uni" или "!stat.uni"
-- с просто фильтром stat.uni
-- подразумевается unique = 1
,"v" : [ условия выборки как у filter запроса в вызове stat.uni ]
-- с дополнительными параметрами
,"v" : {
"filter" : [ условия выборки как у filter запроса в вызове stat.uni ]
,"have" : [ условие фильтрации агрегирования как у запроса в вызове stat.uni ] -- не обязательно
,"cache" : [ настройки кэширования как у запроса в вызове stat.uni ]
,"save" : "метка сохранения" -- не обязательно. символы из набора A-Za-z0-9_
-- только для "stat.uni"
-- сохранение дополнительных данных что именно привело к попаданию в выборку
-- описание ниже
,"select" : [ дополнительный данные для сохранения ] -- используйте совместно с save
}
}
Сохранение дополнительных данных¶
В ряде случаев, требуется понимать что именно привело в попаданию адреса под условия stat.uni
Например, при выборке в рассылку адресов с не завершёнными заказами хорошо бы знать номера и даты таких заказов что бы использовать в шаблонизаторе.
Указание save + select позволяют легко решить эту задачу.
Благодаря условию в фильтре группы:
{
"op" : "stat.uni"
,"v":{
"save":"order"
,"select": ["ssec_order.dt","ssec_order.transaction_id"]
,"filter": [
{"a":"ssec_order.dt","op":">=","v":"current - 7 days"}
,{"a":"ssec_order.dt","op":"<=","v":"current - 1 days"}
,{"a":"ssec_order.transaction_status","op":"!=","v":"completed"}
]
}
}
В выпуск будут выбраны адреса с заказами от вчера до недели назад которые не завешены и в данных персонализации станут доступны даты и номера таких заказов - в специальном ключе anketa.member.FILTER в который собираются результаты всех stat.uni:save+select
Перебор anketa.member.FILTER.order c помощью цикла в шаблонизаторе позволит сформировать красивое содержимое.
anketa:
{
"member" : {
"email" : "test@test.ru"
,"FILTER" : {
"order" : [ -- массив на случай если статистика вернёт более одной записи
{
"ssec_order.dt" : "2023-04-24 14:41:34"
"ssec_order.tramsaction_id" : "Щ-123"
}
,
{
"ssec_order.dt" : "2023-04-24 14:41:34"
"ssec_order.tramsaction_id" : "Ы-456"
}
....
]
}
}
При использовании группы с таким фильтров с member.list результаты сохранения доступны в ответе в ключе save.
Целочисленное сравнение в фильтре ДК¶
Скалярное значение по ключу данных "a" как число не равно (!=), равно (==), меньше (<), меньше или равно (<=), больше или равно (>=), больше (>) чем число в "v" или величина по ключу данных "b"
{
,"a" : "ключ данных"
,"op" : "!= | == | < | <= | => | >"
-- или
,"v" : "целое число"
-- или
,"b" : "ключ данных"
}
Сравнение "между"¶
Скалярное значение по ключу данных "a" больше или равно величина-1 и меньше или равно величина-2.
Сравнение целочисленное. Если у "a" указана точность даты, то сравниваются даты и величины-1/-2 трактуются как даты соответствующей точности.
!between работает как отрицание результата between.
Строчное сравнение¶
В большинстве случаев его использования можно избежать, если изначально спланировать структуру данных.
Скалярное значение по ключу данных "a" как строка не равно (ne), равно (eq), меньше (lt), меньше или равно (le), больше или равно (ge), больше (gt) чем строка в "v" или величина по ключу данных "b"
{
,"a" : "ключ данных"
,"op" : "ne | eq | lt | le | ge | gt"
-- или
,"v" : "строка"
-- или
,"b" : "ключ данных"
}
Совпадение подстроки¶
В большинстве случаев его использования можно избежать, если изначально спланировать структуру данных.
Это относительно медленная операция и в большинстве случаев может быть заменена проверкой на вхождение элемента или его существование.
begins / !begins - строка по ключу данных "a" начинается/не начинается со строки "v" или строки по ключу данных "b"
contains / !contains - строка по ключу данных "a" содержит/не содержит строки "v" или строки по ключу данных "b"
ends / !ends - строка по ключу данных "a" оканчивается/не оканчивается на строку "v" или строку по ключу данных "b"
{
,"a" : "ключ данных"
,"op" : "begins | !begins | contains | !contains | ends | !ends"
-- или
,"v" : "строка"
-- или
,"b" : "ключ данных"
}
Величина null¶
Скалярное значение по ключу есть (is_null) или не есть (!is_null) неопределённое значение null
- in/!in (есть в stat.uni) - скаляр в списке / не в списке (строчное сравнение)
Вхождение в список¶
Скалярное значение по ключу входит (in) или не входит (!in) в список. Сравнение строчное.
Если данные по ключу не существуют, то in не совпадёт ни когда, а !in совпадёт всегда.
Если данные по ключу не скаляр, то in не совпадёт ни когда, а !in совпадёт всегда.
Если данные по ключу это null, то он совпадёт с "" в списке.
Пусто¶
Ключ не существует или существует и есть null.
Если существует, то: скаляр пуст, объект без ключей, массив без элементов.
!is empty - отрицание is empty
Процент аудитории¶
{ "op" : "percent" "!percent" ,"v" : "целое число от 0 до 100" }
Отбирает процент аудитории примерно равный указанному
Некая "быстрая" примерная замена А-Б тестирования или параметра выпуска "процент аудитории"
При одной и той же аудитории и при одном и том же проценте результат один и тот же.
Не правильно думать, что percent(40) это те, кто не попал в percent(60)
Те кто не попал в 60 это !percent(60)
Т..е percent(XX) + !percent(XX) всегда даст всю аудиторию
Поиск по ключам объекта¶
has any of - среди ключей объекта "a" есть хоть один из списка "v" (строчное сравнение). если "a" не объект то не совпадёт
has each of - среди ключей объекта "a" есть каждый из списка "v" (строчное сравнение). если "a" не объект то не совпадёт
!has any of - отрицание has any of. если "a" не объект то не совпадёт
!has each of - отрицание has each of. если "a" не объект то не совпадёт
{
,"a" : "ключ данных"
,"op" : "has any of" | "has each of" | "!has any of" | "!has each of"
,"v" : [ "строка-1", "строка-2" ... ]
}
Итератор¶
Итератор предназначен для перебора всех значений списка или объекта и применения к ним суб-фильтра.
Итератор "has" хоть один элемент подходит под условие суб-фильтра.
{
,"a" : "базовый ключ данных"
,"op" : "has"
,"v" : [
суб-фильтр группы. подразумевается "И"
]
,"save" : "id-сохранения" -- не обязательно
}
Итератор "!has" ни один элемент не подходит под условие суб-фильтр.
Если базовый ключ: - не существует или null - результат итератора "не правда" - если скаляр - фильтр вызывается один раз - если массив - фильтр вызывается для каждого элемента, по порядку начиная с 0 - если объект - фильтр вызывается для каждого элемента, порядок не определён
При переборе существуют специальные ключи данных для доступа к перебираемым данных:
- "@" - значение текущего перебираемого элемента: элемент объекта или элемент массива или скаляр - "@id" - "позиция" для текущего перебираемого элемента: индекса массива или ключ объекта текущего элемента или null для скаляра - "$" - весь пользователь
При вложенных переборах - "@[ddd]" - элемент индекса или массива или скаляря от внешнего has на ddd уровней выше - "@id[ddd]" - величина индекса массива или ключа объекта текущего элемента или null для скаляра от внешнего has на ddd уровней выше
Множественные совпадения итератора has¶
Существуют ситуации, когда необходимо не просто выяснить результат итератора "совпал-не совпал", а что именно вызвало совпадение.
Например, у подписчика хранится список купленных им лицензий с указанием, среди прочего, даты окончания лицензии.
И надо выпустить напоминание тем, кто ещё не продлил лицензии, оканчивающиеся завтра.
{
"a" : "product.license" -- ключ списка лицензий
,"op" : "has"
,"v" : [
{ -- лицензия истекает завтра
"a" : "@.expired:YD"
,"op" : "=="
,"v" : "current + 1 day"
}
,{ -- и ещё не продлена
"a" : "@.prolongation"
,"op" : "=="
,"v" : "0"
}
,{ -- и адрес не в домене test.ru
-- (формально эту проверку надо делать вне итератора, так как её итог не зависит от данных
-- в элементе "license" и тут она приведена только для примера специального ключа "$")
"a" : "$.member.domain"
,"op" : "ne"
,"v" : "test.ru"
}
]
}
Простое применение "has" даст только список подписчиков у которых есть лицензии истекающие в ближайшее время.
А как определить какие именно лицензии вызвали совпадение и что делать если таких лицензий несколько, а писем должно быть по одному на лицензию ?
Для этого предназначен параметр сохранения "save" - каждое совпадение вызывает сохранениеключа данных(а не самих данных) тестирование которого дало совпадение.
Если у подписчика совпало, допустим, три лицензии, то их ключи данных будут записаны в специальный ключ данных в системной анкете member.FILTER.<величина-из-save>
{
"member" : {
...
,"FILTER" : {
"lic" : [
"product.license[2]"
,"product.license[5]"
,"product.license[6]"
]
}
...
}
}
При выпуске рассылки, в зависимости от параметра выпуска "multiple", сохранённые данные будут доступны как:
-
multiple = 0 - как описано выше. member.FILTER хранит все совпадения всех итераторов группы-фильтра. подписчик получитодно письмо
-
multiple = 1 - в группе фильтре должен сработать только один итератор c save. подписчик получитнесколько писемв данных персонализации которых ключ сохранения будет скаляром последовательно принимающим все совпавшие значения.
В примере с лицензиями, это будет письмо с member.FILTER.lic ="product.license[2]", потом письмо с member.FILTER.lic ="product.license[5]"и ещё письмо с member.FILTER.lic ="product.license[6]".
Для доступа к данным совпавшей лицензии используйте команду [% datakey(anketa,member.FILTER.lic) %]
Так же, можно просто получить список совпадения воспользовавшись вызовом member.list - в ответе в параметре "save" будет структура со всеми совпадениями аналогичная member.FILTER
Работа с датой и временем¶
Дата и время хранятся как обычные строки.
Однако при их записи в базу (member.set, member.import) рекомендуется указать тип данных, что бы при записи происходила верификация и нормализация величины.
Без этого, строка представляющая дату-время может не верно проходить проверку с величиной current, так как такие операции требуют строки строго в формате "YYYY-ММ-DD hh:mm:ss" - т.е. год из четырёх цифр, а остальные из двух (с ведущим 0, если надо).
Для работы с датой-временем требуется указать квалификатор точности в ключе данных параметра "a" (или, при наличии, в ключе данных "b"). Это автоматически включит режим работы с датой-временем.
Он задаётся аналогично stat.uni - две буквы точности ("от" и "до") после двоеточия. Например"a.b[0].c[7]:Ym"
Указание квалификатора позволяет понять, что вы хотите использовать значение указанного ключа данных как дату-время, а не как строку или число.
При указании квалификатора, величина "v":
-
при задании даты-времени константой - проверяется на соответствие указанной точности. иначе - ошибка
-
при задании относительного времени (current) - его результат вычисляется с указанной точностью
-
при задании квалификатора у "а", величина "b" без квалификатора считается указанной с таким же квалификатором.
-
при задании квалификатора у "b", величина "a" без квалификатора считается указанной с таким же квалификатором.
-
при задании квалификаторов и у "а" и у "b" они должны совпадать
При задании квалификатора, точность значения "a" ("b") приводится к указанной точности путём её уменьшения (или сохранения, если они совпадают).
Если "a" ("b") меньшей точность чем указано в квалификаторе, то значение останется как есть. В любом случае дата-время в "a" ("b") должны быть в нормализованном формате "YYYY-MM-DD hh:mm:ss" (пример для точности Ys) и с ведущими нулями для компонентов меньших 10 (т.е не "1971-5-4 3:2:1", а "1971-05-04 03:02:01"). Для этого используйте параметр "type" когда вносите данные через member.set и member.import
Относительное время задаётся аналогично stat.uni - использованием слова "current" и добавлением/вычитанием необходимых компонентов (лет, месяцев, дней, часов..).
Величина "current" вычисляется один раз в момент начала использования фильтра для отбора подписчиков, что бы даже при переборе большой базы избежать побочных эффектов от изменения текущего времени.
Если значение по ключу данных не дата-время в верном формате, null, массив или объект, то результат не предсказуем и зависит как от значения, там и от используемого "op".
Допустимые операции c датой-временем
-
численные и строчные сравнения (в данном случае они ведут себя одинаково так как явно указан тип данных)
-
in и !in
-
begins/!begins/contains/!contains/ends/!ends
-
between и !between
Если вы уверены, что никогда не ошибётесь в значении "v" при внесении в базу и задании даты-времени константой и не используете относительное время, то квалификатор точности можно не использовать - операции строчного сравнения, in/!in, begins/!begins/contains/!contains/ends/!ends сработают как и ожидается.
Функция size()¶
Обычный ключ для size¶
К величине на которую указывает ключ данных в параметре "a" можно применить функцию размера size().
Если величина null, то результат - null.
Если величина скаляр, то результат - его длина в символах.
Если величина массив, то результат - количество элементов в массиве.
Если величина объект, то результат - количество пар "ключ-значение" в объекте.
По результатам has/save для size¶
Применение size() к результатам, сохранённым в итераторе has, даст количество сохранённых элементов для данного id-сохранения
Функции агрегации¶
Доступны функции суммы величин sum(), минимальной величины min(), максимальной величины max(), среднего значения - avg().
В примерах используется agr как имя любой их этих функций
Обычный ключ для агрегации¶
Если ключ показывает на скаляр, то результат - значение скаляра.
Если ключ показывает на массив, то результат - сумма/минимальный/максимальный/среднее из элементов массива которые должный быть скалярами. Элемент null пропускается. Строки преобразуются в числа. Не скалярный элемент вызовет ошибку фильтра.
Если ключ показывает на объект, то результат - сумма/минимальный/максимальный/среднее из элементов (которые должный быть скалярами) взятых взятых от каждой пары объекта по относительному ключу. Элемент null пропускается. Строки преобразуются в числа. Не скалярный элемент вызовет ошибку фильтра.
По результатам has/save для агрегации¶
Применение к результатам, сохранённым в итераторе has, даст соответствующую функции величину из элементов взятых по относительному ключу от каждого ключа из сохранённых в id-сохранения. Элемент null пропускается. Строки преобразуются в числа. Не скалярный элемент вызовет ошибку фильтра.
Фильтрация по свойствам голов у тела¶
Реализуется через has к ключу member.head
В данный момент использование такой фильтрации делает весь фильтр НЕ ОПТИМИЗИРУЕМЫМ - он будет работать дольше.
Один элемент можно фильтровать по:
@.id - id перебираемой головы
@.addr_type - тип перебираемой головы (email, msisdn...)
@.email - идентификатор перебираемой головы
@.origin - источник перебираемой головы
@.app - участие головы в "приложении" -- номер authext для pushapp, tg, vk, max
-- только с "op" : "=="
Например