65.9K
CodeProject 正在变化。 阅读更多。
Home

构建 SQL 逻辑引擎

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.29/5 (6投票s)

2008年9月22日

CPOL

6分钟阅读

viewsIcon

51308

downloadIcon

977

本文介绍如何完全用 SQL 构建一个逻辑引擎。在我们的示例中,逻辑引擎是一个程序,它接收一组变量和相应的值,评估一组预定义的条件,并根据哪些条件为真来构建一个结果集。

引言

一个强大的编程语言是可以用少量代码完成大量工作的语言。虽然不如 C++ 或 Java 等语言健壮,但 SQL 起着非常特殊的作用,并且效率极高。如今,很少有应用程序能像现代关系数据库管理系统 (RDBMS) 那样针对原始速度进行优化。

本文介绍如何完全用 SQL 构建一个逻辑引擎。在我们的示例中,逻辑引擎是一个程序,它接收一组变量和相应的值,评估一组预定义的条件,并根据哪些条件为真来构建一个结果集。

您可以测试 工作示例,或者下载上面的 Visual Studio 2005 项目和 MSSQL 数据库。

Sql Logic Engine Screenshot

背景

我构建过几种不同类型的逻辑引擎,其中大多数需要大量开销来加载、构建和解析文本语句。很久以前,一个项目要求构建一个逻辑引擎,根据产品选定的选项来生成物料清单 (BOM)。BOM 将根据预定义的规则生成,虽然这些规则非常简单,但会有数千条,理论上应该能够为任何可能的选项组合生成 BOM。

基于这些要求,我提出了一个想法:将规则存储在数据库中,并让关系数据库引擎执行所有条件评估。在 SQL 中执行逻辑评估有几个好处。首先,它最大限度地减少了数据层和业务层之间的开销,消除了检索大型数据集并将其解析为对象的需要。它还使我们能够以极快的速度执行评估。

数据库结构与算法

此图显示了我们将用于存储规则的数据库的简化版本。每条规则将由一个包含多个条件的条件组表示。规则的形式为:(v1=1) and (v2=2) and (v3=3)。每个语句 variable=value 都存储为条件表中的一条记录。条件组还将与材料关联,如果条件评估为真,则应将这些材料包含在最终的物料清单中。

Database Diagram

此程序将接收一组变量,评估预定义的条件,并根据哪些条件为真来生成结果集,在本例中是物料清单。此程序的算法是:

  1. 找出所有为假的单个条件
  2. 获取这些条件的条件组列表
  3. 获取不在该列表中的所有条件组列表
  4. 检索与该真条件组列表关联的项目

步骤 1:找出所有为假的单个条件

我们的第一步是找出为假的条件。我们的条件语句由存储在数据库自身记录中的单个条件组成。因为每个附加条件总是用 and 运算符复合语句,所以我们可以推断,如果语句中的任何一个条件是假的,那么整个语句就是假的。这引导我们进行第一步,即根据一组变量状态查询所有为假的单个条件。

哪些条件不匹配任何变量/值对?
变量 : 值
v1 : 1
v2 : 2
v3 : 3

每个条件都有两个我们需要与数据集进行比较的语句,一个用于变量,一个用于值:(var=v1) and (val=1)。要使条件为真,两个部分都必须匹配任何一个变量。我们可以使用表达式测试语句 v1=1If (var=v1) and (val=1) 则条件匹配。如果有多个变量要测试,我们则用 or 语句将它们串联起来。

(var=v1 and val=1) or
(var=v2 and val=2) or
(var=v3 and val=3)

如果条件记录匹配任何一个括号内的表达式,那么我们就找到了一个真条件。让我们通过用代表布尔值 T 或 F 的字母来简化此表达式,用字母代表语句 var=v1

(a and b) or
(c and d) or
(e and f

此语句给了我们为真的条件。但是,要检索一个假条件,我们必须反转布尔逻辑。一个假条件可以通过以下语句找到。此语句的意思是,如果每个括号内的表达式都为真 (var!=v1 or val!=1),那么它就未能匹配所有语句,因此条件为假。

(!a or !b) and 
(!c or !d) and
(!e or !f) 

我们的 SQL 语句的 where 子句如下所示:

WHERE(
    (varname<>'v1' OR val<>'1') AND
    (varname<>'v2' OR val<>'2') AND 
    (varname<>'v3' OR val<>'3')
)

我们要添加的最后一部分是能够创建“不等于”条件。这意味着,除了创建 v1='one' and ... 这样的条件外,我们还可以使用不等于运算符。例如:(v1=1) and (v2=2) and (v3!=3)

为了实现这一点,我们在条件记录上添加了一个 not 标志,当我们想要使用不等于时,该标志被设置为 true。这实际上反转了评估。当条件为假且 not 标志为真时,条件为真。这可以通过以下真值表表示:

条件 标志 结果
true true false
false false false
true false true
false true true

您可能会认出这看起来就像 XOR 门的真值表。所以,为了将 not 标志添加到我们的表达式中,我们只需用 XOR 语句修改之前的表达式。

WHERE (
    (varname<>'v1' OR val<>'1') AND
    (varname<>'v2' OR val<>'2') AND
    (varname<>'v3' OR val<>'3')
)
XOR (nflag=1)

如果这听起来很复杂,不用担心,它确实很复杂。但这是语句中最难的部分,如果您能理解布尔逻辑,其余部分就会迎刃而解。查询的这一部分驱动着我们整个语句,一旦我们得到了所有假条件的列表,其余就很简单了。

步骤 2、3 和 4

使用上面的 where 子句,我们对 distinct conditionGroupIds 运行查询,得到所有为假的条件组。我们将其用作子查询,以选择所有不在我们刚刚生成的列表中的条件组 ID。这会得到所有基于我们输入评估为真的条件组。最后一步是对 Materials 表进行连接,以构建一个与我们的真条件关联的材料列表。这是我们的最终查询:

 SELECT m.component, m.qty, m.uom, m.bubble
 FROM(
    SELECT id  FROM ConditionGroups
    WHERE id NOT IN(
        SELECT DISTINCT conditionGroupId
        FROM Conditions
        WHERE 
        (
            (
            (varname<>'v1' OR val<>'1') AND
            (varname<>'v2' OR val<>'2') AND
            (varname<>'v3' OR val<>'3')
            )
            XOR nflag = 0
        )
    )
) AS cg
INNER JOIN Materials AS m
ON m.conditionGroupId = cg.id

结论

这样,您就拥有了一个封装在相当简洁的 SQL 语句中的逻辑评估!这是一个高效、可扩展且通用的规则引擎,可用于多种不同的目的。运行在 MSSQL 数据库服务器上的示例链接在 0.00043 秒内执行。运行在我公司 DB2 服务器上的企业版本在冷启动后评估 3,294 条规则后,执行时间为 0.039 秒。这大约是我能想到的最快的解决方案。您可以亲自尝试 示例,或下载项目。

此解决方案的一个缺点是,您仅限于使用 andequalnot equal 运算符的非常简单的条件。您还必须为正在测试的每一组变量生成一个查询,这意味着将其作为存储过程运行很困难。尽管如此,我认为这个例子是一个针对常见问题的创新方法,并提供了对 SQL 强大功能的一些有趣见解!

关注点

如果您的 SQL 引擎不支持 XOR 运算符,您可以修改查询,如下所示。这不如其他语句简洁,但功能完全相同。

WHERE(
    (
        (varname<>'v1' OR val<>'1') AND
        (varname<>'v2' OR val<>'2') AND
        (varname<>'v3' OR val<>'3') AND
        nflag = 0
    ) OR (
        NOT(
        (varname<>'v1' OR val<>'1') AND
        (varname<>'v2' OR val<>'2') AND
        (varname<>'v3' OR val<>'3')
        ) AND nflag=1
    )
)

历史

  • 2008年9月22日:初始发布
© . All rights reserved.