题
假设您有一个具有3列A,B和C的表规则。当数据进入系统时,我想知道规则表的任何行是否匹配我的数据,所有数据匹配。明显的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 * 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
不幸的是,我没有足够的示例数据来找到我们的哪种方法可以更好地表现。在开始创建随机规则之前,我想我在这里问是否有更好的方法。
笔记: 数据挖掘技术和列约束在这里不可行。必须在进入系统时检查数据,以便可以立即标记通过/失败。并且,用户控制规则的添加或删除,因此我无法将规则转换为列约束或其他数据定义语句。
最后一件事,最后我需要数据未能通过的所有规则的列表。该解决方案在第一次故障时无法中止。
谢谢。
解决方案
您提供的第一个查询非常完美。我真的怀疑添加您所说的列会给您带来更多速度,因为无论如何都会检查每个条目的无效属性,因为与无效的每个比较都会产生faster。所以我想 x=y
被扩展到 x IS NOT NULL AND x=y
内部。也许其他人可以澄清这一点。
我能想到的所有其他优化都将涉及预计或缓存。您可以创建匹配某些规则的[临时]表,也可以添加其他保留匹配规则的列。
其他提示
排太多/规则吗?如果不是这种情况(这是主观的,但要少于10,000),则可以为所有列创建索引。
这将大大提高速度,索引不会占用太多空间。
如果您不打算制作巨大的规则表,那么我敢打赌,只要您索引所有列,您的方法就可以了。
为什么不按值制作规则表的索引?那么你也能
SELECT myvalue FROM RULES_A
听起来您真正拥有的是规则和规则集。以这种方式进行建模,不仅可以使此特定的编码变得更简单,而且还可以在决定需要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语句可能会更加动态,并且不必在添加其他列时增长。
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必须搜索索引以进行匹配,然后再次搜索nulls。因此,我们两次使用索引,但切勿使用整个索引。
但是,使用Cocece,您拥有在每个列值上执行Cocece函数的开销。更快的速度可能取决于您有多少规则,每行中有多少列以及使用的索引(如果有)。
是否更可读性是一个意见问题。