1

假设您有一个表 RULES,其中包含 3 列 A、B 和 C。当数据进入系统时,我想知道 RULES 表的任何行是否与我的数据匹配,条件是 RULES 表中的相应列是否为空,所有数据匹配。显而易见的 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)

所以如果我有规则:

规则ABC
1 50 空 空
2 51 xyz 空
3 51 空 123
4 空 xyz 456

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

问题:有没有更好的方法来做到这一点?只有 3 个字段,这没问题。但实际的表将有 15 列,我担心 SQL 的扩展性如何。

推测:我想出的另一种 SQL 语句涉及向表中添加一个额外的列,并计算有多少字段不为空。(因此在示例中,规则 1-4 的此列值分别为 1、2、2 和 2。)使用此“col_count”列,选择可以是:

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

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

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

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

谢谢。

4

5 回答 5

1

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

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

于 2009-04-14T19:55:00.133 回答
0

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

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

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

于 2009-04-14T19:54:01.290 回答
0

为什么不按值对规则表进行索引?那么你就可以

SELECT myvalue FROM RULES_A
于 2009-04-14T19:57:42.467 回答
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 语句可以更加动态,并且在您添加其他列时不必增长。

于 2009-04-14T20:29:25.680 回答
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 必须在索引中搜索匹配项,然后再次搜索空值。所以我们使用了两次索引,但从不使用整个索引。

但是使用合并,您需要对每个列值执行合并函数的开销。哪个更快可能取决于您有多少规则、每行中有多少列以及使用的索引(如果有)。

它是否更具可读性是一个见仁见智的问题。

于 2009-04-14T20:59:49.273 回答