构建 SQL 逻辑引擎






4.29/5 (6投票s)
本文介绍如何完全用 SQL 构建一个逻辑引擎。在我们的示例中,逻辑引擎是一个程序,它接收一组变量和相应的值,评估一组预定义的条件,并根据哪些条件为真来构建一个结果集。
引言
一个强大的编程语言是可以用少量代码完成大量工作的语言。虽然不如 C++ 或 Java 等语言健壮,但 SQL 起着非常特殊的作用,并且效率极高。如今,很少有应用程序能像现代关系数据库管理系统 (RDBMS) 那样针对原始速度进行优化。
本文介绍如何完全用 SQL 构建一个逻辑引擎。在我们的示例中,逻辑引擎是一个程序,它接收一组变量和相应的值,评估一组预定义的条件,并根据哪些条件为真来构建一个结果集。
您可以测试 工作示例,或者下载上面的 Visual Studio 2005 项目和 MSSQL 数据库。

背景
我构建过几种不同类型的逻辑引擎,其中大多数需要大量开销来加载、构建和解析文本语句。很久以前,一个项目要求构建一个逻辑引擎,根据产品选定的选项来生成物料清单 (BOM)。BOM 将根据预定义的规则生成,虽然这些规则非常简单,但会有数千条,理论上应该能够为任何可能的选项组合生成 BOM。
基于这些要求,我提出了一个想法:将规则存储在数据库中,并让关系数据库引擎执行所有条件评估。在 SQL 中执行逻辑评估有几个好处。首先,它最大限度地减少了数据层和业务层之间的开销,消除了检索大型数据集并将其解析为对象的需要。它还使我们能够以极快的速度执行评估。
数据库结构与算法
此图显示了我们将用于存储规则的数据库的简化版本。每条规则将由一个包含多个条件的条件组表示。规则的形式为:(v1=1) and (v2=2) and (v3=3)
。每个语句 variable=value
都存储为条件表中的一条记录。条件组还将与材料关联,如果条件评估为真,则应将这些材料包含在最终的物料清单中。

此程序将接收一组变量,评估预定义的条件,并根据哪些条件为真来生成结果集,在本例中是物料清单。此程序的算法是:
- 找出所有为假的单个条件
- 获取这些条件的条件组列表
- 获取不在该列表中的所有条件组列表
- 检索与该真条件组列表关联的项目
步骤 1:找出所有为假的单个条件
我们的第一步是找出为假的条件。我们的条件语句由存储在数据库自身记录中的单个条件组成。因为每个附加条件总是用 and
运算符复合语句,所以我们可以推断,如果语句中的任何一个条件是假的,那么整个语句就是假的。这引导我们进行第一步,即根据一组变量状态查询所有为假的单个条件。
哪些条件不匹配任何变量/值对?
变量 : 值
v1 : 1
v2 : 2
v3 : 3
每个条件都有两个我们需要与数据集进行比较的语句,一个用于变量,一个用于值:(var=v1) and (val=1)
。要使条件为真,两个部分都必须匹配任何一个变量。我们可以使用表达式测试语句 v1=1
:If (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 秒。这大约是我能想到的最快的解决方案。您可以亲自尝试 示例,或下载项目。
此解决方案的一个缺点是,您仅限于使用 and
、equal
和 not 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日:初始发布