web-dev-qa-db-ja.com

WHERE NOT IN副選択句を使用してパフォーマンスを向上させる

次のクエリでは、顧客ごとにトランザクションをカウントする必要があります。 [編集]ただし、トランザクションが1年以上経過している顧客は、結果セットから完全に除外する必要があります。

クエリオプティマイザーは、顧客ごとに1回だけ存在を評価するほどスマートではないでしょうか。

--Count transactions on customers that are less than 1 year old

  SELECT t1.CUSTID,COUNT(*)
  FROM CUST_TRX t1
  WHERE NOT EXISTS ( 
    SELECT FIRST 1 1 
    FROM CUST_TRX t2 
    WHERE 
      t2.CUSTID=t1.CUSTID AND
      t2.DATED<CURRENT_DATE-365
    GROUP BY t2.CUSTID
  )
  GROUP BY t1.CUSTID

クエリプランに自然条件はありません。このクエリは、データベースがすべての顧客に対して実行するのではなく、トランザクションごとに存在句を実行しているかのように実行されます。サブクエリでGROUP BYを削除しても、パフォーマンスは同じです。

データベースからより良いパフォーマンスを得ることができるようにこれを行うより良い方法はありますか?可能であれば、CTEを回避して単純な選択クエリが機能することを願っています(他の課題が発生する可能性があります)。基準による他のグループ化のため(ここには表示されていません)、単純にMIN(DATED)をチェックすることはできません。本当に別のクエリを実行する必要があります。

3
jcalfee314

このようなクエリでは、_LEFT OUTER JOIN_スタイルチェックの代わりに_NOT EXISTS_スタイルチェックを実行する方が効率的ですこれは、多くの場合、フルインデックススキャン(または正しいインデックスが配置されています)が、メインテーブルに多くの行がある場合、これは、他の場合は結果として生じるであろう大量のインデックスシーク(メインテーブルから返される各行の参照テーブルに1つ)よりも安価です。一部のクエリプランナーは、この同等性を特定し、より適切な選択肢である代替プランを使用することについて非常に優れていますが、これはあなたのケースで起こったように聞こえません。

次のようなものを試してください:

_SELECT t1.CUSTID, COUNT(*)
FROM   CUST_TRX t1
LEFT OUTER JOIN
       CUST_TRX t2 
ON     t2.CUSTID=t1.CUSTID 
AND    t2.DATED<CURRENT_DATE-365
WHERE  t2.CUSTID IS NULL
GROUP BY t1.CUSTID
_

(注:私はfirebirdに慣れていないので、上記の構文には微調整が必​​要かもしれませんが、要点を説明する必要があります)

_WHERE t2.CUSTID IS NULL_がない場合、_t1_で一致する_t2_のすべての行が_t2_で一致するたびに1回出力され、_t2_で一致しないものはすべて出力されます1回出力されますが、そのオブジェクトから選択された列はNULLに設定されます。次に、WHERE句が一致を除外します。

DBエンジンの能力に応じて、特に参照オブジェクト(ここでフィルターを適用した場合の_CUST_TRX_)のデータ量が膨大な場合、これは大幅に少なくなる可能性があります_WHERE <something> NOT IN_または_WHERE NOT EXISTS_オプションよりも効率的であるため、メソッドを使用する前に、まず現実的なデータセットでベンチマークを行ってください。クエリプランナーが_WHERE NOT IN_の配置がこの方法でより効率的に実行できることに気付かない場合、MS SQL Serverでより効率的に機能することがよくあります。

また、これを行う場合は、コード(および/またはサポートドキュメント)にコメントを残して、より効率的であると期待される_WHERE <something> NOT IN_または_WHERE NOT EXISTS_と同等のものとしてこれを実行していることを伝えます。覚えておくと、経験豊富なSQL担当者がパターンを認識しますが、コードを見ている他の人は意図や理由をすぐに理解できず、わかりやすくするために_WHERE NOT EXISTS_を使用するように戻すことができます。文。

4
David Spillett

「1年未満の顧客のトランザクションをカウントする」とは、次のことを意味します。

  1. 1年未満のすべての顧客トランザクションをカウントしますか?
  2. 1年未満の新規顧客のすべてのトランザクションをカウントしますか?

サンプルコードから、私は#1があなたが望むものであることを理解しています。その場合、本当に存在しない場所が本当に必要ですか?次のようなことをしていただけませんか:

SELECT t1.CUSTID, COUNT(*)
FROM CUST_TRX t1
WHERE t1.DATED>=CURRENT_DATE-365
GROUP BY t1.CUSTID
HAVING COUNT(*) > 0

私はFirebirdユーザーではありませんが、GROUP BY/HAVING構文を調べました。

[編集] 1年以上経過したトランザクションを持つ顧客を結果セットから除外します。

OK、これは行を集約して顧客を選択から除外する別の方法です。

SELECT A.CUSTID, A.HowMany
FROM (SELECT t1.CUSTID, COUNT(*) HowMany, MIN(t1.DATED) OldestTran
    FROM CUST_TRX t1
    GROUP BY t1.CUSTID
    HAVING COUNT(*) > 0 AND MIN(t1.DATED) >=CURRENT_DATE-365) AS A

[編集]わかりました。そのため、クエリはより複雑になり、1つのクエリで実現できます。

つまり、最初に投稿したパターンとよく似たパターンを使用する必要があるでしょう。 EXISTSはDISTINCTを意味し、多くの場合、SELECT DISTINCTからのJOINよりも高速であることに注意してください。ただし、さまざまな方法を試して、動作、タイミングなどを比較することができます。次に、最も気に入った方法を選択します。

0
RLF