← Вернуться к списку тем

Как составить структуру хранения пользовательских фильтров

Всем привет. Пишу для себя конструктор ботов и встала задача сохранять условие/фильтр в базу.

Например, я могу выставить несколько условий разных, типо, если пользователь новый и нет в базе то сделать то и то, либо, если прислал картинку или файл сделать то и то...

Проблема в том, что этих фильтров может быть много. Разных с условием ИЛИ и И

Как всё это сохранять в базу данных и потом красиво делать проверку логики?

Пробовал сохранять в json, но кажется что есть более лучший вариант.

Авторизуйтесь через Telegram, чтобы оставить комментарий.
Откройте по ссылке или QR бот @iMakeBot, нажмите кнопку Старт/Start.
Следуйте инструкциям бота.

  • Денис Программист-Фрилансер [10 месяцев назад]

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

    Плизз, выручайте 

  • iMakeBots [10 месяцев назад → Денис Программист-Фрилансер]

    Можно и в json хранить, но только вспомогательные данные. Типа какое условие и в каком месте применяется.

    Для самих условий думаю стоит создать класс, где будет логика для каждого типа.

    В бд создать таблицу где записывать условия: название, тип, параметры для сравнения, тип сравнения >,<,==,!= …

    Далее эти условия добавлять к месту, типа в этом шаге учитывать такие то условия, и сделать конструктор комбинирования условий или/и возможно многоуровнего использования 
    Связку условий хранить можно и в json, но лучше просто в бд в строковом формате с привязкой к месту применения

  • Денис Программист-Фрилансер [10 месяцев назад → iMakeBots]

    Можно пример? Про привязку? Не совсем понял.

    У меня реализовано так сейчас, есть поле type=text

    В него пишу выборку-фильтр

    Масив, ключ AND - тут масив фильтра И

    И ключ OR - масив ИЛИ.

    Но решение очень кривое.

    Хотелось бы понять, как более красиво и правильно хранить логику. Ведь её может быть много. И базу данных постоянно на каждый фильтр дёргать не хочется.

    Вот как работают конструкторы ботов? Там и логику сохраняют и переменные и параметры. Неужели раздувают таблицы под хранения?

    Сам код обработки я без проблем реализую, а вот с хранением фильтров не могу найти какое-то нормальное решение.

    Буду рад, если хоть простые примерчики, так сказать, натолкнуть на мысль 

  • iMakeBots [10 месяцев назад → Денис Программист-Фрилансер]

    Таблица связи это многое ко многим

    То есть, есть множество условий и множество мест, каждое условие может быть использовано во многих местах.

    Чтобы их связать делаем дополнительную таблицу и в ней пишем связи между местом использования и условием, можно еще учесть  какие-либо дополнения для этой связи, но необязательно.


    Вам бы уйти от ручного указания условий в type=text, так как они могут быть переиспользованы в проекте лучше создать конструктор этих условий, и в нужном месте выбирать их через например select.

    В конструкторе условий учесть тип условия, тип сравнения, и данные для сравнения. 

  • Денис Программист-Фрилансер [10 месяцев назад → iMakeBots]

    Многие ко многим - это понятно.

    Например, теги на сайте, делал такое.

    Но вот с фильтрами не понимаю как реализовать. То что json надо убрать, понимаю, кривое решение 

    Конструктор условий? Что из себя представляет? Одна таблица хранит что? Название фильтра и принадлежность у боту? К шагу?

    Вторая, как понимаю типы фильтра, сравнение дат, времени, текста и ещё что-то.

    А третья?

    Либо уже мозги не работают, либо что-то не допонимаю я (((

    Не пойму никак, каким образом хранить фильтры, делать связку и чтобы потом без кучу циклов проходить фильтры

    Может пример есть какой? Тезисно хотя бы для понимания 

  • Денис Программист-Фрилансер [10 месяцев назад → iMakeBots]

    Или дополнительно сделать таблицу по группе фильтров?

    Например, есть таблица фильтров

    id type_filter type_param group_id

    И тогда в другой таблице

    id type писать значение or and?

    Группировки? 

  • Денис Программист-Фрилансер [10 месяцев назад → iMakeBots]

    Тоесть, база содержит так

    id bot_id filer_type filter_param

    Да записать можно

    233 = cyrent_data

    С одним фильтром понятно, а как строить логику из ИЛИ и И?

    Если допустим, мне надо сравнить и дату и переменную и параметр.

    Или через ИЛИ, должна быть переменная ИЛИ такая та дата?! Условия могут быть разные. Вся проблема встала по правильной сборке имено фильтров И и ИЛИ.

    Отдельно фильтры понятно как, а совокупность? 

  • iMakeBots [10 месяцев назад → Денис Программист-Фрилансер]

    Совокупность - группа условий в том числе многоуровневая по типу:

    if(true || (true && true && (true || true))) {
        // ...
    }

    В этом случае нужно использовать логику рекурсивного вызова

    Например у вас есть стартовый экран, в котором вы хотите проверить условия, в которых текущая дата больше заданной на 2 дня и (id пользователя равно заданному или в его имени Jack есть буква H)

    Классу фильтрации нужно сгенерировать исходя из наличия в бд примерно вот такой массив фильтров-условий

    // массив проверок
    $filters = [
      [
        "type" => "AND",
        "params" => [],
        "children" => [
            [
                "type" => null,
                "params" => [">=", "2023-06-17", "2023-06-15"],
                "children" => [
                    [
                        "type" => "OR" 
                        "params" => [],
                        "children" => [
                           [
                               "type" => null,
                               "params" => ["==", 12345678, 87654321],
                               "children" => []
                           ],        
                           [
                               "type" => null,
                               "params" => ["include", "Jack", "H"],
                               "children" => []
                           ]        
                    ],
                ]
            ]
       ];

    Далее передать этот массив в рекурсивный метод некого класса проверки условий

    В этом классе необходимы методы типов проверок для простых условий типа >=, == ... и также для кастомных по типу include (проверяет вхождение подстроки)

    Хранение условий нужно организовать так чтобы можно было также в рекурсивном методе генерировать необходимый массив (как на примере выше)

    Делал без проверки на "скорую руку" ))) скорее всего нужно будет где-то логику переделать - но уже оттолкнуться думаю можно

  • Денис Программист-Фрилансер [10 месяцев назад → iMakeBots]

    Ну тут можно хранить получается тот же json полностью фильтр.

    Или поучить из базы фильтры, собрать масив. А дальше уже на обработку. И как я понял, масив нужно составить сразу с данными, а потом рекурсивно проходить проверку?! Но уже есть зарождение мысли, спасибо, хоть не до конца всё уловил. С базы данных получается мы получаем все фильтры. Потом формируем масив. А дальше строим рекурсивно условие

    if(true || (true && true && (true || true))) {
        // ...
    }

    Так получается? 

  • iMakeBots [10 месяцев назад → Денис Программист-Фрилансер]

    JSON вам придется хранить с данными на момент его генерации - но в момент вызова данные могут отличаться.

    Поэтому лучше генерить в момент вызова

  • Денис Программист-Фрилансер [10 месяцев назад → iMakeBots]

    А как в базе тогда сохранять без данных? Типы? Тесть, есть веб форма добавления условий

    Допустим, поле дата сравнить с текущей

    Далее понадобилось условие И

    Добавляем поле ещё условия.

    Тогда в базу как записать? Это будет уже два условия?

    Первое допустим

    id type children

    Где children равно второму полю? 

  • iMakeBots [10 месяцев назад → Денис Программист-Фрилансер]

    Типы условий можно хранить в самом классе например в статичном свойстве в массиве

    Конструктор конечно лучше сделать на html + js, при отправке в обработку направлять массив полей, а хранить все в одной таблице с привязкой вверх к родителю (parent), не вниз к children так как children может не быть, а вот у child точно будет parent

  • iMakeBots [10 месяцев назад → Денис Программист-Фрилансер]

    А дальше строим рекурсивно условие

    Да, строим, но собирать его необязательно для чтобы потом где-то запускать проверку условий - метод который его строит уже сразу может его обрабатывать и в случае если на каком-то этапе крашиться проверка - то можно выходит и вернуть false

    То есть метод который его строит возвращает булевое значение

  • Денис Программист-Фрилансер [10 месяцев назад → iMakeBots]

    Никак не пойму общую структуру всей логики от структуры хранения фильтров в базе, выборки и постройки. Вроде смотрю на пример, кажется понятно. Но не могу понять как собрать всё воедино. На каком этапе делать запрос. Сразу получать фильтры или по одному и строить масив фильтра?

  • iMakeBots [10 месяцев назад → Денис Программист-Фрилансер]

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

  • Денис Программист-Фрилансер [10 месяцев назад → iMakeBots]

    Мои мозги окончательно сгорели 😢

  • iMakeBots [10 месяцев назад → Денис Программист-Фрилансер]

    Фильтры получаете сразу конечно - зачем их вызывать по одному - вы передете команду на проверку фильтров, если фильтры есть то погнали доставать их из бд, достали сформировали массив для проверки - проверили выдали результат

  • Денис Программист-Фрилансер [10 месяцев назад → iMakeBots]

    Я вот никак не могу понять. Да, фильтры я достану с базы, дерево построить смогу через parent.

    Но никак не могу понять каким образом мне фиксировать фильтр ИЛИ & И

    Ведь запрос проверки может быть таким дата == 21.09.2023 OR (param1==2 AND param2==3)

    Или мне каждую часть фильтра проверять отдельно? Или весь собранный фильтр сразу проверять?

  • iMakeBots [10 месяцев назад → Денис Программист-Фрилансер]

    Обратите внимание на массив, в нем есть место для этим параметров OR, AND или могут быть другие

    "type" => "AND",
        "params" => [],
        "children" => [ ...

    Это нужно учесть в конструкторе при создании

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

  • Денис Программист-Фрилансер [10 месяцев назад → iMakeBots]

    Так, чтобы всё это понять надо идти от базы.

    Каждый фильтр мы сохраняем в таблицу, и по примеру таблица такая

    id - parent - type - children

    Запрос к базе получаем масив этих фильтров

    Теперь цикл (образно)

    Допустим все данные в масиве filters[] 

    Foreach (filters as item_filter ) {И вот тут как тогда строить логику опираясь на OR или AND }

    Мне бы понять на простых фильтрах OR, and, ==, !===

  • iMakeBots [10 месяцев назад → Денис Программист-Фрилансер]

    children зачем? в нем вы что планируете хранить?

  • Денис Программист-Фрилансер [10 месяцев назад → iMakeBots]

    Так какая тогда структура таблицы?

    Как в таблице записаны должны быть фильтры?

    Допустим, мне надо сделать фильтр

    Пользователь имеет метку "клиент"

    И не имеет метку "про"

    По сути это два фильтра, ну как я понимаю. Как это должно храниться в базе? Фуххх, не доходит что-то до меня 🤔😢🙄

  • iMakeBots [10 месяцев назад → Денис Программист-Фрилансер]

    Что-то типа этого

    Таблица связей: id, screen_id, type, parent, filter_id, params

    Таблица фильтров: id, name, type

  • Денис Программист-Фрилансер [10 месяцев назад → iMakeBots]

    Ну это если screen известен. А если нужно проверить по несколько фильтрам пользователя? Например, сделал запрос пользователь боту и не важно на каком он шаге, надо проверить допустим кучу условий, есть там метки, какие параметры.

    Экран хорошо, когда предыдущий шаг записан. И связная таблица поле param там масив параметров одного экрана (шага) получается?

  • iMakeBots [10 месяцев назад → Денис Программист-Фрилансер]

    Это не важно к чему привязан набор фильтров. Можете назвать не screen_id а moment.

    Все это будет зависеть от того как у вас логика настроена. В любом случае фильтры к пустому месту создавать не будете - смысл от них нулевой будет.

    Например, сделал запрос пользователь боту и не важно на каком он шаге

    Это у вас например набор фильтров который привязан к любому шагу пользователя, поэтому можно в логике бота записать moment = alltime 

  • Денис Программист-Фрилансер [10 месяцев назад → iMakeBots]

    Ааа, понял. Фильтры конкретно привязаны к шагу и общие фильтры. Супер. Это уже хорошо.

    Вроде с базой данных и хранением немного вроде разобрался.

    Теперь по сборке фильтра. Абстрактный класс описывает общие свойства и методы.

    Далее, потом каждый потомок (действие, сравнение, поиск, и так далее) собирает свой масив и возвращает. И общий метод абстрактного класса собирает весь масив фильтра? Так?

    Вроде немного понятно. А как саму проверку реализовать этого собранного массива фильтров? Циклом пробежать по масиву? Или сразу на моменте создания массива делать проверку? Тоже этот момент не пойму.

    Хотя, весь фильтр то проверять не надо. Если есть конструкция And и первое условие не подошло, тогда наверное можно сразу выходить из метода 

  • iMakeBots [10 месяцев назад → Денис Программист-Фрилансер]

    Класс необязательно абстрактный, подход абстрактный )) от общего к частному

    Вызываете метод в который передаете массив условий, который сразу же проверяете на выходе возвращаете bool

    Если в момент проверки массива натыкаетесь на false - то смысла дальше проверять нет - останавливаете проверку и возвращаете false

  • Денис Программист-Фрилансер [10 месяцев назад → iMakeBots]

    Класс. 👍 Тоесть, мы частями передаём масив, тоесть фильтры и получаем bool. А если на самом деле вернулось false, то режим всю дальнейшую проверку. 👍

  • iMakeBots [10 месяцев назад → Денис Программист-Фрилансер]

    передаем весь массив, в режиме рекурсии проходим по нему - и вернем результат

  • Денис Программист-Фрилансер [10 месяцев назад → iMakeBots]

    Да да, и если на каком то шаге рекурсии есть false, то просто остановить выполнение. Смысла нету. Особенно при условии AND 

  • iMakeBots [10 месяцев назад → Денис Программист-Фрилансер]

    Даже лучше так: если в момент проверки понимаете что смысла дальше нет проверять так как условия все равно по итогу вернут false - останавливаете и возвращаете false

  • Денис Программист-Фрилансер [10 месяцев назад → iMakeBots]

    Супер. Теперь есть от чего отталкиваться. Спасибо огромное 👍👍👍👍👍👍 сейчас попытаюсь всё переварить. Теоретически понятно, пока технически не совсем понял как решить... 

  • iMakeBots [10 месяцев назад → Денис Программист-Фрилансер]

    Получаете все фильтры экрана с order by id asc где parent = 0

    Далее проходите по ним циклом, если type не равен null тогда это то очень вы пишите - строка которая говорит что она является оператором объядинения условий - значит идем в бд и получаем ее children по условию где parent = ее id и сортировка order by id asc

  • Денис Программист-Фрилансер [10 месяцев назад]

    Логика же разная. Там что угодно. Дату сравнить. Параметр. Переменную, текст по условию. Либо запрос внешний сделать.

    Обобщенно мне понятно вроде суть, но на практике не могу применить.

    Захотел сделать себе тоже конструктор ботов, ну пусть не сложный, но хотя бы примитивные вещи, чтобы можно было указать текст команды, сравнить её, если пользователь добавился и есть метка то сделать действия. Ух. Мозги пухнут... Чего только не пробовал, медленно всё работает. Если каждый фильтр сохранять отдельной записью, то потом все фильтры перебирать в цикле? Я думал, что можно сразу получить все фильтры, составить запрос... Или я наивно не в ту сторону шагаю? 

  • iMakeBots [10 месяцев назад → Денис Программист-Фрилансер]

    Лучше вам идти в сторону абстракции. Вы же сейчас хотите плодить конкретику.

  • Денис Программист-Фрилансер [10 месяцев назад → iMakeBots]

    Хорошо. Абстрактный класс. Что изначально он делает? Базовый фильтр? И потомки создают конкретно свой тип фильтра, сравнение дат, сравнение времени, сравнение ещё и ещё что-то.

    Стукните мне по голове - не понимаю пока, как реализовать 🙄

  • Денис Программист-Фрилансер [10 месяцев назад → iMakeBots]

    Или хотя бы пример абстракции, может так дойдёт до меня 🙄

  • iMakeBots [10 месяцев назад → Денис Программист-Фрилансер]

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

    Для начала нужно определить свойства фильтра-условия, у него есть тип сравнения (простые: ==, !=; "сложные": is_null, isset) и параметры сравнения - что с чем сравнивать или что проверять в случае "сложного"

    Для этого создаем Класс в котором будут описаны типы сравнения и методы обработки у которых на выходе будет булевое значение

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

    Далее в нужном месте в конструкторе выбрать нужный фильтр, указать к нему параметры обработки, также можно универсально предопределить методы подстановки параметров по типу текущая дата минус столько дней, или имя пользователя или id пользователя - это также можно определить в Классе - при этом просто указать методы определения данных

    Записать это в бд - и при вызове проверки генерировать нужный массив данных как указанно в примере 

  • Денис Программист-Фрилансер [10 месяцев назад]

    Ссори, за флуд.

    Просто одно дело многие ко многим выбирать допустим теги для сайта, а другое дело хранение логики, которое может содержать условие И ИЛИ

    Равно, меньше, или не равно, или равно именьше, или не равном больше.... Фильтров же много...