COUNT(*)をレコードの存在チェックのために安易に使うべからず | Archive Redo Blog

Archive Redo Blog

DBエンジニアのあれこれ備忘録

ある条件に合うレコードがテーブルに存在するかしないかを判断する場合に

SELECT COUNT(*) FROM ~ WHERE ~

というSQLを使うことがあります。

一般的によく使うSQLだし、意味はあっているのですが、パフォーマンスの観点から、以下のような場合はできれば使用を避けた方がよい。

・カウント対象件数が多い
・インデックスが効かない
・繰り返し実行することが多い

なぜ避けた方がよいかというと、レコードがあるかないかを知りたいだけなのに、わざわざレコード件数をカウントしてやる必要はないからです。

特に上記のような状況に当てはまる場合は、カウントにかなりの処理時間を要するため、パフォーマンス低下の要因となってしまいます。

では、どんな方法を用いて存在チェックを行うのがよいかというと、思いつくのは以下の3つの方法です。

カーソルを使う方法
以下のようにレコードを検索するカーソルを作成し、1件だけフェッチします。

DECLARE
    CURSOR cur IS
      SELECT ~ FROM ~ WHERE ~;
  BEGIN
    OPEN cur;
    FETCH cur INTO ~;
    IF cur%NOTFOUND THEN
      ~
    END IF;
    CLOSE cur;
  END;

1件だけしかフェッチしないので最低限の読み込みで済ませることができます。

※上記の例はOracleの場合。SQL Serverの場合、文法は異なります。
ROWNUMを使う方法
以下のようにROWNUM = 1という条件をつけて検索します。

SELECT ~ FROM ~ WHERE ~ AND ROWNUM = 1;

これも1件だけしかフェッチしないので最低限の読み込みで済ませることができます。

※ただし、Oracleでしか使えません。
EXISTS句を使う方法
以下のようにEXISTS ( サブクエリー ) とすることにより、サブクエリーでヒットするレコードが存在するかしないかを判断します。

SELECT ~ FROM ~ WHERE EXISTS ( SELECT * FROM ~ WHERE ~ );

これもたぶん1件だけしかフェッチしないと思われるので最低限の読み込みで済ませることができます。

SQL Serverの場合は、以下のようにIF文の中で使うこともできるので使い勝手がよい。

IF EXISTS ( SELECT * FROM ~ WHERE ~ )
  ~
GO

上記の3つの方法のうち、どれが一番パフォーマンスがよいかというと、簡単にテストしてみた限りではどうもほとんど変わらないようです。

その時々、あるいは好みによって使い分ければいいのではないでしょうか。

また、COUNT(*)と比べてどうかというと、これはデータ量や検索条件、インデックスなどによって大きく異なりますが、数十倍、数百倍の差が出ることも大いにありえます。

実行計画やSQLの統計情報などを見て、レコードの存在チェックで使っているCOUNT(*)で負荷がかかっているようであれば、見直してみるべきでしょう。

と言いながら、私も結構安易にCOUNT(*)使っちゃうんですがねぇ^^ゞ