Структура для хранения логических выражений в СУБД

Учтите, что следующие переменные генерируются службой анализатора игрока:

    level = 6;
    errors = 4;
    score = 12;
    ...

И у нас есть некоторые правила и сообщения:

 1. errors == 0 AND level > 5 : Senior player
 2. score == 10 OR errors == 3: Border line player
 3. score > 10 AND score < 13: Not good, just passed
 4. ...

Теперь мы должны напечатать правильные сообщения.

Другой пример: рассмотрим следующие переменные, генерируемые службой Food analyzer:

    fruit = 2;
    coca = 6;
    ...

И у нас есть некоторые правила и сообщения:

 1. fruit == 0 : Consider buying some fruits
 2. coca == 0: That's healthy
 3. ...

Теперь мы должны напечатать правильные сообщения.

Как я должен сохранить правила и сообщения в СУБД, таких как MySQL, чтобы стало легко запрашивать и находить сообщения.

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

Введите описание изображения здесь
Введите описание изображения здесь

Можете ли вы предложить лучший метод для этой ситуации? Это не очень хороший метод, когда у нас есть несколько тысяч сообщений, нам нужен метод фильтрации сообщений на стороне БД.

3 ответа

  1. Это классический случай для системы правил, и, скорее всего, не должен быть реализован в базе данных. Я собрал библиотеку java (Rulette), которая делает в значительной степени это.

    По существу, вы бы настроили его, создав таблицу rule_system и вставив в нее запись, а также создав таблицу ввода правила с вашими записями (уровень, ошибка, оценка). По вашим выборкам, уровень и ошибка, кажется, «VALUE» типы в то время как «оценка», кажется, «диапазон» тип.

    Теперь вы можете создать таблицу правил (‘player_rules {id, level, error, score}’), чтобы настроить все ваши правила и сопоставить их с записями выходной таблицы (‘player_message {id, message}’).

    Хорошо идти!!

    RuleSystem rs = new RuleSystem("player-rule-system");
    Rule r = rs.getRule(new HasMap<>(){"level":level, "error: : error, "score" : score});
    
  2. Я создал быстрый ERD, чтобы продемонстрировать, как я изначально проектировал его:
    Введите описание изображения здесь

    Что означают все эти столбцы и таблицы?

    имя свойства

    Это содержит список всего, что может иметь значение проверено против него.

    • property_id-первичный ключ
    • property_name-текстовое значение элемента, в котором хранится значение. Примерами могут быть «ошибки», «уровень», «фрукты».

    оператор

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

    • operator_id-первичный ключ.
    • operator_symbol — символ, используемый при проверке значения. Я не уверен, что фактический символ является лучшим значением для хранения здесь, но он может работать. Примеры были бы «==», «>», «>>=».

    rule_message

    Сохраняет отображаемое сообщение.

    • rule_message_id-первичный ключ
    • сообщение-текст сообщения для отображения. Примерами могут быть «старший игрок», «рассмотреть возможность покупки фруктов».

    operator_property

    Это объединяющая таблица между всеми тремя другими таблицами и содержит ваши правила и логику.

    • property_operator_id-первичный ключ. Он известен как суррогатный ключ — вы можете исключить этот столбец, если хотите, и сделать PK (property_id, operator_id, rule_message_id), если это то, что вы предпочитаете.
    • property_id-используемая запись property_name (например, идентификатор » ошибки»)
    • operator_id-используемая запись оператора (например, ID для «==»)
    • rule_message_id — используемый rule_message (например, идентификатор » старшего игрока»)
    • check_value-значение, которое проверяется по свойству для оператора. Примеры: 6, 4, 12.

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

    SELECT rn.rule_message_id, rm.message
    FROM rule_message rm
    INNER JOIN operator_property op ON rm.rule_message_id = op.rule_message_id
    INNER JOIN property_Name pn ON op.property_id = pn.property_id
    INNER JOIN operator o ON op.operator_id = o.operator_id
    WHERE 1=1
    AND (
        pn.property_name = "errors"
        AND pn.operator_symbol = "=="
        AND op.check_value = 0
    )
    AND (
        pn.property_name = "level"
        AND pn.operator_symbol = "5"
        AND op.check_value = 5
    )
    

    В идеале этот запрос вернет 1 строку. Если он возвращает 0, то сообщения не применяются. Если он возвращает 2 или более, это означает, что он не вписывается в один из ваших критериев, поэтому ни одно из сообщений не применяется.

    Надеюсь, это поможет! Я уже писал статьи по определению баз данных, и лучший совет, который я могу дать вам, — это определить назначение данных, которое, похоже, у вас уже есть.

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

  3. Как правило, такой вид интерпретации правил не выполняется непосредственно в базе данных , и в конечном итоге это будет сделано в интерпретаторе, как вашcheck_rules_against_data, и это абсолютно нормально.

    Это довольно распространено, чтобы просто написать все правила непосредственно в одном или нескольких файлах php (окруженный, конечно, некоторым кодом, как if ($rule) { echo $message; }). Это, как правило, быстрее, чем динамически оценивать каждое правило каждый раз (и имейте в виду, что базе данных придется делать именно это). Как вы кодируете фильтров зависит от ваших потребностей, вы можете придерживаться своих правил в формате, вы можете просто показать полный PHP-код и позволить пользователю редактировать его, вы могли бы разделить их и использовать базы данных дизайн, чтобы, например, убедиться в том, что переменная существует (см., Например, моя большая rule_term-таблице или completeitpro ответ). Все это будет работать просто отлично.

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

    Ваши переменные выглядят так, как будто у вас будет тонна их , но все они целочисленные (поэтому владение coke не означает:Items[x]='COCA', ноcoca=1), поэтому вы можете поместить их и правила в таблицы, как это:

    переменная

    variableid | variablename | variabletype
    ----------------------------------------
    1          | errors       | 1
    2          | level        | 1 
    3          | score        | 1 
    

    пользовательская переменная

    userid     | variableid  | valueint  
    -------------------------------------
    1          | 1           | 0         
    1          | 2           | 6         
    1          | 3           | 10         
    2          | 1           | 3         
    2          | 3           | 10        
    3          | 1           | 0         
    3          | 2           | 6         
    3          | 3           | 10         
    4          | 1           | 0         
    4          | 2           | 5         
    

    правило

    ruleid | mincount | message
    ---------------------------
    1      | 2        | Senior player          -> AND (2 terms have to fit)
    2      | 1        | Border line player     -> OR (any 1 term can fit)
    

    rule_term

    ruleid | variableid | minvalueint | maxvalueint
    -----------------------------------------------
    1      | 1          | 0           | 0            -> error == 0
    1      | 2          | 6           | 9999         -> level > 5
    2      | 1          | 3           | 3            -> error == 3
    2      | 3          | 10          | 10           -> score == 10
    

    С помощью этих правил теперь можно предварительно выбрать правила, которые попали:

    select user_variable.userid, rule.ruleid, count(*) as cntfulfilled, 
           max(rule.mincount) as mincnt, max(rule.message) as message
    from rule_term
    join rule
    on rule_term.ruleid = rule.ruleid
    join user_variable 
    on rule_term.variableid = user_variable.variableid
    and rule_term.minvalueint <= user_variable.valueint 
    and rule_term.maxvalueint >= user_variable.valueint
    group by user_variable.userid, rule.ruleid
    having count(*) >= max(rule.mincount);
    

    Это должно учитывать для каждого пользователя и каждого правила, сколько подпунктов этого правила выполняются. Это должно быть, если я не ошибаюсь:

    userid | ruleid | cntfulfilled | mincnt | message
    --------------------------------------------------
    1      | 1      | 2            | 2      | Senior player
    1      | 2      | 1            | 1      | Border line player
    2      | 2      | 2            | 1      | Border line player
    3      | 1      | 2            | 2      | Senior player
    

    Чтобы выразитьAND, mincntдолжно быть число всех подтем , ибоOR, оно будет 1. Чтобы построить правила с помощью простого ANDилиOR, это уже будет полный тест.

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

    удлиненный rule_termстол:

    ruleid | pos | cond | var.id | min | max
    --------------------------------------------
    3      | 1   | 1    | 0      | 0   | 0     -> (
    3      | 2   | 0    | 1      | 1   | 1     -> error == 1
    3      | 3   | 4    | 2      | 5   | 5     -> AND level == 5
    3      | 4   | 2    | 0      | 0   | 0     -> )
    3      | 5   | 5    | 3      | 10  | 10    -> OR score == 10
    

    где я использовал cond=1: (, cond=2:), cond=3: не, cond=4: и, cond=5: или. (Есть лучшие способы кодирования, например, выразить только логику и сгруппировать ее во вложенные ANDподгруппы, но это ничего не улучшит здесь).

    Это позволит вам по-прежнему предварительно выбирать правила, которые могут соответствовать, чтобы получить правила , которые вы должны проанализировать позже в php (вы не можете использовать mincnt больше, так как mincnt будет 1, даже если толькоerror == 1, а не только когда score == 10).

    Вы можете добавить больше вещей в него можно добавить строковую переменную типов (добавить столбец valuestr В user_variable и rule_term и настройки соединения) или флаг «не», и вы можете добавить более сложные copnditions к вашему присоединиться, если вы не способны выразить их в несколько рядов в rule_term-таблицы (например, объединить 2 переменные и проверить для 2 переменных в двойном присоединиться).

    Это немного сложнее, но вы можете использовать левые соединения и некоторую дополнительную логику для сравнения переменных, которых нет (например, если вы не хотите устанавливать переменную cocaдля всех, только для пользователей, у которых есть (или был) coke.

    Если вы хотите использовать горизонтальные переменные (фиксированное количество переменных, каждое в столбце), вы должны сделать то же самое для условий правила (столбец min/max для каждой переменной) и настроить соединения, чтобы проверить каждый столбец.

    Это просто общая идея, и у вас, очевидно, есть тонны альтернатив, чтобы сделать это, и лучший вариант и оптимизация будет во многом зависеть от ваших фактических потребностей, и тратить больше времени на размышления о вашем databasedesign (или о том, как генерировать динамические файлы php) позже уменьшит разочарование (много) или увеличит скорость (много). И напомню еще раз, протестируйте опцию генерации динамических php файлов — обычно это будет намного быстрее.