假设您有一个包含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
不幸的是,我没有足够的样本数据来找到我们哪种方法会表现得更好。在我开始创建随机规则之前,我想我会问这里是否有更好的方法。
注意:数据挖掘技术和列约束在这里不可行。必须在进入系统时检查数据,因此可以立即标记通过/失败。并且,用户控制规则的添加或删除,因此我无法将规则转换为列约束或其他数据定义语句。
最后一件事,最后我需要一个数据无法通过的所有规则的列表。解决方案不能在第一次失败时中止。
感谢。
答案 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函数。哪个更快可能取决于您拥有的规则数量,每行中的列数以及使用的索引(如果有)。
它是否更具可读性是一个意见问题。