4

我想有效地检查一个表是否包含任何匹配 <condition A> 并且不匹配 <condition 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)

问题是可怕的空值。假设 <condition A> 是name = 'Aaron'而 <condition B> 是age = 21。该查询将正确识别任何年龄不等于 21 的 Aarons,但它无法识别任何年龄为空的 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”子句的第一个查询是比第二个快得多 - 它可以在找到一个匹配的记录后立即停止,并且不必进行两次单独的扫描)。

我怎样才能有效地做到这一点,而不会对空值感到困惑?

4

5 回答 5

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约束的相应记录,因此我希望一个好的引擎能够很好地调整以尽可能地优化可用索引所允许的习语。

于 2009-07-14T04:37:54.337 回答
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 表达式替换列名更容易,并且它应该具有相同的最终结果。

于 2009-07-14T12:42:55.910 回答
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 为空)还是(...且 x 不为空)?

于 2009-07-14T01:54:41.523 回答
0

如果您确实有 id 字段,请尝试:

select count(*) from dual where exists (select * from people where (cond a) and zzz.id not in (select id from people where (cond b)));

于 2009-07-14T04:52:53.743 回答
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 有点傻),并且它是一个超出列数据类型的精度(例如,9999for number(3)),但可以使其与索引一起使用。当然,如果列已经使用最大精度/长度,这是不可能的。

于 2009-07-14T12:35:12.647 回答