是否有一种检查n列规则的好方法?

时间:2009-04-14 19:35:08

标签: sql rule-engine

假设您有一个包含3列A,B和C列的表RULES。当数据进入系统时,我想知道RULES表的任何行是否与我的数据匹配,条件是如果规则中的相应列table为null,所有数据都匹配。显而易见的SQL是:

SELECT * FROM RULES
WHERE (A = :a OR A IS NULL)
  AND (B = :b OR B IS NULL)
  AND (C = :c OR C IS NULL)

所以,如果我有规则:

RULE    A        B        C
1       50       NULL     NULL
2       51       xyz      NULL
3       51       NULL     123
4       NULL     xyz      456

(50,xyz,456)的输入将匹配规则1和4。

问题:有更好的方法吗?只有3个字段,这没问题。但实际的表格将有15列,我担心SQL的扩展程度。

猜测:我想出的另一个SQL语句是向表中添加一个额外的列,其中包含多少字段不为空的计数。 (因此在示例中,规则1-4的此列值分别为1,2,2和2.)使用此“col_count”列,select可以是:

SELECT * FROM RULES
WHERE (CASE WHEN A = :a THEN 1 ELSE 0 END)
    + (CASE WHEN B = :b THEN 1 ELSE 0 END)
    + (CASE WHEN C = :c THEN 1 ELSE 0 END)
    = COL_COUNT

不幸的是,我没有足够的样本数据来找到我们哪种方法会表现得更好。在我开始创建随机规则之前,我想我会问这里是否有更好的方法。

注意:数据挖掘技术和列约束在这里不可行。必须在进入系统时检查数据,因此可以立即标记通过/失败。并且,用户控制规则的添加或删除,因此我无法将规则转换为列约束或其他数据定义语句。

最后一件事,最后我需要一个数据无法通过的所有规则的列表。解决方案不能在第一次失败时中止。

感谢。

5 个答案:

答案 0 :(得分:1)

您提供的第一个查询非常完美。我真的怀疑添加你所说的列会给你更多的速度,因为无论如何都要检查每个条目的NOT NULL属性,因为每次比较为NULL都会产生错误。所以我猜想x=y会在内部扩展到x IS NOT NULL AND x=y。也许其他人可以澄清这一点。

我能想到的所有其他优化都涉及预先计算或缓存。您可以创建匹配特定规则的[临时]表,或者添加包含匹配规则的其他列。

答案 1 :(得分:0)

行/规则太多了吗?如果不是这种情况(这是主观的,但是说少于10,000),你可以为所有列创建索引。

这会显着提高速度,索引也不会占用太多空间。

如果你不打算制作一个庞大的规则表,那么我打赌你的方法是可以的,只要你索引所有列。

答案 2 :(得分:0)

为什么不按值来制作规则表的索引?那你可以

SELECT myvalue FROM RULES_A

答案 3 :(得分:0)

听起来你真正拥有的是规则和规则集。以这种方式建模不仅会使这种特定的编码变得更加简单,而且当您决定需要16列时,也可以使模型可扩展。

例如:

CREATE TABLE Rules (
    rule_id         INT         NOT NULL,
    rule_category   CHAR(1)     NOT NULL, -- This is like your column idea
    rule_int_value  INT         NULL,
    rule_str_value  VARCHAR(20) NULL,
    CONSTRAINT PK_Rules PRIMARY KEY CLUSTERED (rule_id),
    CONSTRAINT CK_Rules_one_value CHECK (rule_int_value IS NULL OR rule_str_value IS NULL)
)

CREATE TABLE Rule_Sets (
    rule_set_id INT NOT NULL,
    rule_id     INT NOT NULL,
    CONSTRAINT PK_Rule_Sets PRIMARY KEY CLUSTERED (rule_set_id, rule_id)
)

一些符合您给定规则的数据:

INSERT INTO Rules (rule_id, rule_category, rule_int_value, rule_str_value)
VALUES (1, 'A', 50, NULL)
INSERT INTO Rules (rule_id, rule_category, rule_int_value, rule_str_value)
VALUES (2, 'A', 51, NULL)
INSERT INTO Rules (rule_id, rule_category, rule_int_value, rule_str_value)
VALUES (3, 'B', NULL, 'xyz')
INSERT INTO Rules (rule_id, rule_category, rule_int_value, rule_str_value)
VALUES (4, 'C', 123, NULL)
INSERT INTO Rules (rule_id, rule_category, rule_int_value, rule_str_value)
VALUES (5, 'C', 456, NULL)

INSERT INTO Rule_Sets (rule_set_id, rule_id) VALUES (1, 1)
INSERT INTO Rule_Sets (rule_set_id, rule_id) VALUES (2, 2)
INSERT INTO Rule_Sets (rule_set_id, rule_id) VALUES (2, 3)
INSERT INTO Rule_Sets (rule_set_id, rule_id) VALUES (3, 2)
INSERT INTO Rule_Sets (rule_set_id, rule_id) VALUES (3, 4)
INSERT INTO Rule_Sets (rule_set_id, rule_id) VALUES (4, 3)
INSERT INTO Rule_Sets (rule_set_id, rule_id) VALUES (4, 5)

测试脚本,确认您期望的相同答案:

DECLARE
    @a  INT,
    @b  VARCHAR(20),
    @c  INT

SET @a = 50
SET @b = 'xyz'
SET @c = 456

SELECT DISTINCT
    rule_set_id AS failed_rule_set_id
FROM
    Rule_Sets RS
WHERE
    NOT EXISTS (SELECT * FROM Rules R WHERE R.rule_id = RS.rule_id AND @a = R.rule_int_value) AND
    NOT EXISTS (SELECT * FROM Rules R WHERE R.rule_id = RS.rule_id AND @b = R.rule_str_value) AND
    NOT EXISTS (SELECT * FROM Rules R WHERE R.rule_id = RS.rule_id AND @c = R.rule_int_value)

如果您可以以基于集合的形式而不是单个参数来呈现输入数据,那么最终的SQL语句可以更加动态,并且不必随着添加其他列而增长。

答案 4 :(得分:0)

SELECT * FROM RULES
 WHERE (A = :a OR A IS NULL)
   AND (B = :b OR B IS NULL)
   AND (C = :c OR C IS NULL);

根据您的RBDMS,这可能会或可能不会更有效,但不是很多:

SELECT * FROM RULES
 WHERE coalesce(A, :a) = :a
   AND coalesce(B, :b) = :b 
   AND coalesce(C, :c) = :c ;

在MySQL中(您的RBDMS可能以不同的方式执行此操作),如果存在适用的索引,此查询允许index扫描而不是ref_or_null扫描。如果索引涵盖所有列,则它允许使用整个索引(实际上,如果索引覆盖所有列,则索引 表)。

使用您的查询,ref_or_null访问权限而不是index访问权限,并且仅使用多列索引中的第一列。使用ref_or_null,MySQL必须在索引中搜索匹配项,然后再次搜索空值。所以我们使用索引两次,但从不使用整个索引。

但是通过coalesce,您可以在每个列值上执行coalesce函数。哪个更快可能取决于您拥有的规则数量,每行中的列数以及使用的索引(如果有)。

它是否更具可读性是一个意见问题。