否定任意where子句条件(包括null测试)

时间:2009-07-14 00:52:41

标签: sql oracle null where-clause

我想有效地检查一个表是否包含任何匹配<条件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”子句的第一个查询是比第二个快得多 - 它一旦找到一个匹配的记录就可以停止,而且不必进行两次单独的扫描。)

如果没有充分利用空值,我怎样才能有效地做到这一点?

5 个答案:

答案 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}}),但它可能使其与索引一起使用。当然,如果列已经使用了最大精度/长度,则无法进行此操作。