【问题标题】:Structure to store logical expressions into RDBMS将逻辑表达式存储到 RDBMS 中的结构
【发布时间】:2016-05-29 16:11:14
【问题描述】:

考虑以下变量是由播放器分析器服务生成的:

    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 分析器服务生成的:

    fruit = 2;
    coca = 6;
    ...

我们有一些规则和信息:

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

现在我们应该打印正确的消息了。

我应该如何将规则和消息保存在像 MySQL 这样的 RDBMS 中,以便查询和查找消息变得容易。

最糟糕的方法是将规则保存在一个列中,将消息保存在另一列中,然后加载每条记录以使用宿主编程语言进行测试。

您能针对这种情况提出一个更好的方法吗?当我们有几千条消息时,这不是一个好方法,我们需要一种在 DB 端过滤消息的方法。

【问题讨论】:

    标签: php mysql sql rdbms logical-operators


    【解决方案1】:

    我创建了一个快速 ERD 来演示我最初是如何设计它的:

    所有这些列和表是什么意思?

    属性名称

    这包含所有可以对其进行检查的值的列表。

    • property_id - 主键
    • property_name - 具有存储值的项目的文本值。例如“errors”、“level”、“fruit”。

    运营商

    包含用于每个属性的不同运算符的列表。

    • operator_id - 主键。
    • operator_symbol - 检查值时使用的符号。我不确定实际符号是否是存储在这里的最佳价值,但它可以工作。例如“==”、“>”、“>=”。

    rule_message

    存储正在显示的实际消息。

    • rule_message_id - 主键
    • message - 要显示的消息文本。例如“资深玩家”、“考虑购买水果”。

    operator_property

    这是所有其他三个表之间的连接表,包含您的规则和逻辑。

    • property_operator_id - 主键。它被称为代理键 - 如果您愿意,可以排除此列,如果您愿意,可以将 PK 设为 (property_id, operator_id, rule_message_id)。
    • property_id - 正在使用的 property_name 记录(例如“错误”的 ID)
    • operator_id - 正在使用的操作员记录(例如“==”的 ID)
    • rule_message_id - 正在使用的 rule_message(例如“高级玩家”的 ID)
    • 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 或更多,则意味着它不完全符合您的条件之一,因此没有任何消息适用。

    希望这会有所帮助!我以前有过written articles on desigining databases,我能给你的最好的建议是弄清楚数据的用途,看起来你已经有了。

    另外,如果你能想到更好的表格名称,那就去吧 - 这只是一个快速的设计来说明这一点。

    【讨论】:

      【解决方案2】:

      这种规则解释一般不会直接在数据库中完成,最终还是会在你check_rules_against_data这样的解释器中完成,那绝对没问题。

      将所有规则直接写在一个或多个 php 文件中是很常见的(当然还有一些代码,例如if ($rule) { echo $message; })。它通常比每次动态评估每个规则要快(请记住,数据库也必须这样做)。如何对过滤器进行编码取决于您的需要;您可以坚持您的规则格式,您可以只显示完整的 php 代码并让用户对其进行编辑,您可以将它们拆分并使用数据库设计,例如验证变量是否存在(例如,参见下面我的扩展rule_term-table 或 completeitpro 的答案)。所有这些都可以正常工作。

      如果你愿意,或者如果你想测试它,你可以在你的数据库中做一些预选。有很多方法可以做到这一点,并且有很多方法可以针对特殊情况对其进行优化,这在很大程度上取决于您实际想要做什么,所以我只描述一种方法,给您一个想法。

      你的变量看起来你会有很多,但它们都是整数(所以拥有可乐并不意味着: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)
      

      规则术语

      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
      

      要表达ANDmincnt应该是所有子项的数量,对于OR,它将是1。用普通ANDOR构建规则,这已经是完整的了测试。

      对于更复杂的规则,您必须能够在 php 中重新创建规则以将其放入您的检查功能中。你可以例如将其编码在如下表中:

      扩展rule_term-table:

      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-subgroups 中,但它不会在这里改进任何东西)。

      这将允许您仍然预先选择可能适合的规则,以获取您必须在 php 中分析的规则(您不能再使用 mincnt,因为 mincnt 将是 1,即使只是 error == 1,而不仅仅是 @987654341 @)。

      您可以向其中添加更多内容:您可以添加字符串变量类型(将列 valuestr 添加到 user_variablerule_term 并调整连接)或“NOT”标志,您可以添加更多如果您能够在 rule_term-table 中的行中表达它们(例如,组合 2 个变量并在双连接中检查 2 个变量),则对您的连接有复杂的条件。

      这有点困难,但您可能希望使用左连接和一些额外的逻辑来比较不存在的变量(例如,如果您不想为每个人设置变量 coca,只为那些有(或有)可乐。

      如果您想使用水平变量(固定数量的变量,每个变量在一列中),您应该对规则术语(每个变量的最小/最大值列)执行相同的操作并调整连接以检查每个列。

      这只是一个一般性的想法,显然您有很多替代方案可以做到这一点,最佳选择和优化很大程度上取决于您的实际需求,并花更多时间考虑您的数据库设计(或如何生成动态php 文件)稍后会减少挫败感(很多)或提高速度(很多)。我会再次提醒您,测试生成动态 php 文件的选项 - 这通常会快很多。

      【讨论】:

        【解决方案3】:

        这是规则系统的经典案例,可能不应该在数据库中实现。我整理了一个 java 库 (Rulette),它几乎可以做到这一点。

        基本上,您可以通过创建一个 rule_system 表并向其中插入一个条目来设置它,然后使用您的条目(级别、错误、分数)创建一个规则输入表。根据您的示例,level 和 error 似乎是“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});
        

        【讨论】:

          猜你喜欢
          • 2015-04-01
          • 2010-10-02
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-01-28
          • 1970-01-01
          • 1970-01-01
          • 2014-06-06
          相关资源
          最近更新 更多