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

Рассмотрим следующие переменные, сгенерированные игрок Сервис анализатора:

    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. ...

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

Другой пример: рассмотрим следующие переменные, сгенерированные питание Сервис анализатора:

    fruit = 2;
coca = 6;
...

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

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

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

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

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

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

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

0

Решение

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

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

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

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

переменная

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

user_variable

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: NOT, cond = 4: AND, cond = 5: OR. (Существуют более эффективные способы кодирования, например, выразить только логику и сгруппировать ее во вложенные AND-подгруппы, но это ничего не улучшит здесь).

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

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

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

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

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

0

Другие решения

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

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

Теперь вы можете создать таблицу правил (‘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});
0

Я создал быструю ERD, чтобы продемонстрировать, как я изначально его разработал:
введите описание изображения здесь

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

Имя свойства

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

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

оператор

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

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

rule_message

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

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

operator_property

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

  • property_operator_id — первичный ключ. Он известен как суррогатный ключ — вы можете исключить этот столбец, если хотите, и сделать PK (property_id, operator_id, rule_message_id), если это то, что вы предпочитаете.
  • property_id — используемая запись имя-свойства (например, идентификатор для «ошибок»)
  • operator_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 или более, это означает, что он не вписывался в один из ваших критериев, поэтому ни одно из сообщений не применимо.

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

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

0
По вопросам рекламы [email protected]