我想有效地检查一个表是否包含任何匹配<条件A>的行。并且不匹配<条件B>,其中条件是任意的。
在Oracle中,这个几乎有效:
select count(*) from dual
where exists (
select * from people
where (<condition A>)
and not (<condition B>)
);
-- returns zero if all rows that match <condition A> also match <condition B>
-- (well, almost)
问题是可怕的空值。让我们说&lt;条件A&gt;是 name ='Aaron'和&lt;条件B&gt;是年龄= 21 。该查询将正确识别年龄不等于21的任何Aarons,但无法识别年龄为null的任何Aarons。
这是一个正确的解决方案,但在包含数百万条记录的表格上可能需要一段时间:
select (
select count(*) from people
where (<condition A>)
) - (
select count(*) from people
where (<condition A>)
and (<condition B>)
) from dual;
-- returns zero if all rows that match <condition A> also match <condition B>
-- (correct, but it is s l o w...)
不幸的是,这两个条件将是任意的,复杂的,变化的,并且通常不受我的控制。它们是从用户搜索的应用程序的持久性框架生成的,当我们尝试使用我们的用户保持索引时,很多时候它们会导致大表扫描(这就是为什么带有“exists”子句的第一个查询是比第二个快得多 - 它一旦找到一个匹配的记录就可以停止,而且不必进行两次单独的扫描。)
如果没有充分利用空值,我怎样才能有效地做到这一点?
答案 0 :(得分:1)
假设您的表有一个主键id
,一种可能的方法是:
select count(*)
from people p1
left join people p2
on (p1.id = p2.id
and (p2.<condition A>)
and (p2.<contition B>))
where p1.<condition A>
and p2.id IS NULL
您确实需要对条件进行一些简单预处理(根据需要使用p1.
或p2.
添加每个列名称),但这比正确否定条件更容易您提到的NULL
问题。
LEFT JOIN sometable ON whatever WHERE ... AND sometable.id IS NULL
是一种流行的表达方式“sometable
中没有符合whatever
约束条件的相应记录,所以我希望能很好地调整好的引擎来优化可用指数允许的习惯用语。
答案 1 :(得分:1)
如果对于每个可空列都可以提出一个永远无效的虚拟值,那么你可以这样做:
select count(*) from dual
where exists (
select * from (
select NVL(name, 'No Name') name, NVL(age, -1) age from people
)
where (<condition A>)
and not (<condition B>)
);
您可能希望在这些表达式上创建基于函数的索引。
这比在运行时解析条件并尝试用NVL表达式替换列名更容易,它应该具有相同的最终结果。
答案 2 :(得分:0)
如果条件完全任意,我认为你无能为力。是否有可能根据某些规则在某个时刻“重写”条件?
我相信如果你这样做:
... where not (age = 21) ....
内部翻译成:
... where (age != 21) ...
你得到的记录太少,因为它与空值不匹配,对吗?
但如果你这样做:
... where not (age = 21 and age is not null) ....
内部翻译成:
... where (age != 21 or age is null) ....
然后你会得到预期的结果。 (右?)
那么你可以强制你的条件中的所有比较包括一个空测试,在表单中(...或x为null)或(...和x不为null)?
答案 3 :(得分:0)
如果您有id字段,请尝试:
从双重中选择计数(*) 存在的地方( 选择*来自人 哪里(cond a) 和zzz.id没有(从人们中选择id(cond b)) );
答案 4 :(得分:0)
一种解决方案是首先删除比较参数中的任何空值,即将字符串附加到值或用该列的不可能值替换空值。以第一个为例:
select x, y
from foo
join bar on bar.a||'x' = foo.a||'x' /* Replace "=" with "<>" for opposite result */
;
替换空值:
select x, y
from foo
join bar on nvl(bar.a, 'x') = nvl(foo.a, 'x') -- Ditto
;
现在,第二个选项更难(至少在Oracle 9.2中),因为你必须确保替换值与它替换的列的数据类型相同(NVL有点傻),并且它是一个列数据类型精度之外的值(例如,9999
的{{1}}),但它可能使其与索引一起使用。当然,如果列已经使用了最大精度/长度,则无法进行此操作。