4

<条件 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)

問題は恐ろしいヌル値です。<condition A> がname = 'Aaron'で、 <condition B> がage = 21であるとしましょう。このクエリは、年齢が 21 歳以外の Aaron を正しく識別しますが、年齢が null の Aaron を識別できません。

これは正しい解決策ですが、数百万のレコードを持つテーブルでは時間がかかる場合があります。

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...)

残念ながら、この 2 つの条件は恣意的で、複雑で、変化するものであり、通常は私の手に負えません。それらはユーザー検索からアプリケーションの永続化フレームワークから生成され、ユーザーに合わせてインデックスを維持しようとしますが、多くの場合、大きなテーブル スキャンが発生します (そのため、"exists" 句を含む最初のクエリは2 番目よりもはるかに高速です。一致するレコードが 1 つ見つかるとすぐに停止でき、2 つの別々のスキャンを実行する必要はありません)。

ヌルを気にせずにこれを効率的に行うにはどうすればよいですか?

4

5 に答える 5

1

テーブルに主キーがあると仮定すると、id考えられるアプローチの1つは次のとおりです。

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

すべての null 許容列に対して、決して有効であってはならないダミー値を考え出すことができる場合は、次のようにすることができます。

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

idフィールドがある場合は、次を試してください。

select count(*)from dual where presents(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

1つの解決策は、最初に比較のパラメーター内のnullを取り除くことです。つまり、値に文字列を追加するか、その列のnullを不可能な値に置き換えます。最初の例:

select x, y
  from foo
  join bar on bar.a||'x' = foo.a||'x' /* Replace "=" with "<>" for opposite result */
;

nullの置き換え:

select x, y
  from foo
  join bar on nvl(bar.a, 'x') = nvl(foo.a, 'x') -- Ditto
;

現在、2番目のオプションは(少なくともOracle 9.2では)より困難です。これは、置換値が置換する列と同じデータ型であり(NVLはそのように少しばかげている)、それが列のデータ型の精度(たとえば、9999for number(3))ですが、インデックスで機能させることができる場合があります。もちろん、列がすでに最大精度/長さを使用している場合、これは不可能です。

于 2009-07-14T12:35:12.647 に答える
0

完全に恣意的な条件だとどうしようもないと思います。いくつかのルールに基づいて、ある時点で条件を「書き直す」ことは可能ですか?

これを行うと、次のようになります。

... where not (age = 21) ....

これは内部的に次のように変換されます。

... where (age != 21) ...

null 値と一致しないため、レコードが少なすぎますよね?

しかし、これを行うと:

... where not (age = 21 and age is not null) ....

これは内部的に次のように変換されます。

... where (age != 21 or age is null) ....

その後、期待される結果が得られます。(右?)

では、(... または x が null) または (... and x が null ではない) のいずれかの形式で、条件内のすべての比較に強制的に null テストを含めることはできますか?

于 2009-07-14T01:54:41.523 に答える