web-dev-qa-db-ja.com

(Oracle)ページネーションクエリを使用する場合、結果の総数を取得するにはどうすればよいですか?

Oracle 10gと次のパラダイムを使用して、15ページの結果のページを一度に取得しています(ユーザーが検索結果の2ページを表示しているときに、レコード16〜30が表示されます)。

select * 
  from 
( select rownum rnum, a.*
    from (my_query) a
   where rownum <= 30 )
where rnum > 15;

現在、my_queryの結果の総数を取得するために、別のSQLステートメントを実行して「my_query」で「select count」を実行する必要があります(ユーザーに表示してそれを使用して把握できるようにするため)総ページ数など)。

2番目のクエリを使用せずに、つまり上記のクエリから取得することによって、結果の総数を取得する方法はありますか? 「max(rownum)」を追加しようとしましたが、機能していないようです(グループにキーワードrownumを使用するのが好きではないことを示すエラー[ORA-01747]が表示されます)。

別のSQLステートメントで実行するのではなく、元のクエリからこれを取得したいという私の理論的根拠は、 "my_query"は負荷の高いクエリであるため、2回実行するのではなく(一度カウントを取得し、1回取得して)データのページ)必要がない場合;ただし、単一のクエリ内から結果の数を取得する(同時に、必要なデータのページを取得する)ために思いつく可能性のある解決策は、可能であれば、追加のオーバーヘッドがあったとしても、それほど追加すべきではありません。お知らせ下さい。

これはまさに私が行おうとしているものであり、ROWNUMをグループ化するのが嫌いなので、ORA-01747エラーが表示されます。注:max(ROWNUM)を使用しない別の解決策があるが、それ以外の解決策がある場合でも、それで問題ありません。このソリューションは、何が機能するかについての私の最初の考えでした。

 SELECT * FROM (SELECT r.*, ROWNUM RNUM, max(ROWNUM)
 FROM (SELECT t0.ABC_SEQ_ID AS c0, t0.FIRST_NAME, t0.LAST_NAME, t1.SCORE
 FROM ABC t0, XYZ t1
 WHERE (t0.XYZ_ID = 751) AND 
 t0.XYZ_ID = t1.XYZ_ID 
 ORDER BY t0.RANK ASC) r WHERE ROWNUM <= 30 GROUP BY r.*, ROWNUM) WHERE RNUM > 15

--------- EDIT --------注、最初のコメントに基づいて、機能しているように見える以下を試しました。ただし、他のソリューションと比較してパフォーマンスがどの程度かはわかりません(自分の要件を満たしながら最高のパフォーマンスを発揮するソリューションを探しています)。たとえば、これを実行すると、16秒かかります。 COUNT(*)OVER()RESULT_COUNTを取り出すと、わずか7秒かかります。

    SELECT * FROM (SELECT r.*, ROWNUM RNUM, ) 
    FROM (SELECT COUNT(*) OVER () RESULT_COUNT, 
          t0.ABC_SEQ_ID AS c0, t0.FIRST_NAME, t1.SCORE 
    FROM ABC t0, XYZ t1 
    WHERE (t0.XYZ_ID = 751) AND t0.XYZ_ID = t1.XYZ_ID 
    ORDER BY t0.RANK ASC) r WHERE ROWNUM <= 30) WHERE RNUM > 1

EXPLAIN PLANは、SORT(ORDER BY STOP KEY)を実行することから、WINDOW(SORT)を実行するように変更されます。

前:

SELECT STATEMENT () 
 COUNT (STOPKEY)    
  VIEW ()   
   SORT (ORDER BY STOPKEY)  
    NESTED LOOPS () 
     TABLE ACCESS (BY INDEX ROWID)  XYZ
      INDEX (UNIQUE SCAN)   XYZ_ID
     TABLE ACCESS (FULL)    ABC

後:

SELECT STATEMENT () 
 COUNT (STOPKEY)    
  VIEW ()   
   WINDOW (SORT)    
    NESTED LOOPS () 
     TABLE ACCESS (BY INDEX ROWID)  XYZ
      INDEX (UNIQUE SCAN)   XYZ_ID
     TABLE ACCESS (FULL)    ABC
19
BestPractices

「単一」クエリで必要なすべての情報を取得するには、クエリをこのようなものに変更する必要があると思います。

_SELECT *
FROM (SELECT r.*, ROWNUM RNUM, COUNT(*) OVER () RESULT_COUNT 
      FROM (SELECT t0.ABC_SEQ_ID AS c0, t0.FIRST_NAME, t1.SCORE
            FROM ABC t0, XYZ t1
            WHERE (t0.XYZ_ID = 751) 
            AND t0.XYZ_ID = t1.XYZ_ID 
            ORDER BY t0.RANK ASC) R)
WHERE RNUM between 1 and 15 
_

その理由は、COUNT(*) OVER()ウィンドウ関数がWHERE句の後に評価されるため、レコードの総数ではなく、_ROWNUM <= 30_条件を満たすレコードの数が得られるためです。

このクエリのパフォーマンスを受け入れることができない場合、または2つの別々のクエリを実行する場合は、おそらく彼のFrustratedWithFormsDesignerによって提案されたようなソリューションについて考える必要があります/レコード数のキャッシュに関するコメント。

データベースを定期的に使用している場合は、 SQL Cookbook のコピーを入手することをお勧めします。役立つヒントが満載の逸品です。

23
Elliot Vargas

ただの提案:

Google "約13,000,000の結果のうち1-1"アプローチ-COUNT(*)を元のクエリのクイックサンプルとして実行することを検討できます。ここでは、指定されたXYZに対して最大で1つのABCがあると仮定しました:

SELECT *
FROM (SELECT r.*, ROWNUM RNUM, 
      (SELECT COUNT(*) * 100
       FROM ABC SAMPLE(1) t0
       WHERE (t0.XYZ_ID = 751)
      ) RESULT_COUNT 
  FROM (SELECT t0.ABC_SEQ_ID AS c0, t0.FIRST_NAME, t1.SCORE
        FROM ABC t0, XYZ t1
        WHERE (t0.XYZ_ID = 751) 
        AND t0.XYZ_ID = t1.XYZ_ID 
        ORDER BY t0.RANK ASC) R)
WHERE RNUM between 1 and 15 

明らかに、サンプルは非常に不正確で変動しやすいため、これが適切かどうかは要件によって異なります。

2
Jeffrey Kemp
WITH
base AS
(
    SELECT ROWNUM RNUM, A.*
    FROM (SELECT * FROM some_table WHERE some_condition) A
)
SELECT FLOOR(((SELECT COUNT(*) FROM base) / 15) + 1) TOTAL_PAGES_TO_FETCH, 
       ((ROWNUM - MOD(ROWNUM, 15)) / 15) + 1 PAGE_TO_FETCH,
       B.*
FROM base B

このクエリは、フェッチする必要があるページのグループ数を計算し、データを1つのクエリとしてフェッチします。

結果セットから、一度に15行を処理します。最後の行のセットは、15より短くなる場合があります。

1
EvilTeach

いいえ、クエリを2回実行するか、クエリを1回実行してall行を取得およびキャッシュしてからでないと、表示を開始する前に行をカウントできません。特にクエリが高価であるか、潜在的に多くの行を返す場合は、どちらも望ましくありません。

Oracle独自のApplication Express(Apex)ツールは、ページ付けオプションの選択肢を提供します。

  1. 最も効率的なのは、「さらに」行があるかどうかを示すだけです。これを行うには、現在のページの最大値より1行だけフェッチします(例:行16〜30を表示するページの場合は31行)。
  2. または、「67の16-30」または「200を超える16-30」を示す可能性のある限られた数を表示できます。つまり、最大201行(この例では)がフェッチされます。これはオプション1ほど効率的ではありませんが、オプション3より効率的です。
  3. または、実際に「13,945の16-30」を表示できます。これを行うには、Apexは13,945をすべてフェッチする必要がありますが、行15〜30を除くすべてを破棄する必要があります。これは最も遅く、最も効率の悪い方法です。

オプション3(好み)の疑似PL/SQLは次のようになります。

l_total := 15;
for r in 
  ( select * 
      from 
    ( select rownum rnum, a.*
        from (my_query) a
    )
    where rnum > 15
  )
loop
   l_total := l_total+1;
   if runum <= 30 then
      print_it;
   end if;
end loop;
show_page_info (15, 30, l_total);
1
Tony Andrews

これは機能しますか?

select * 
  from 
( select rownum rnum, a.*, b.total
    from (my_query) a,   (select count(*) over () total from my_query) b
   where rownum <= 30 )
where rnum > 15;

別の解決策は、ABC.XYZ_IDの各値のカウントを維持するマテリアライズドビューを作成することです。これにより、テーブルの行を挿入/更新/削除するプロセスにカウントを取得する負担がかかります。

1
Jeffrey Kemp

EvilTeachの答えを基にするには:

WITH
base AS
(
    SELECT (ROWNUM - 1) RNUM, A.*
    FROM (SELECT * FROM some_table WHERE some_condition) A
)
SELECT V.* FROM (
  SELECT FLOOR(((SELECT COUNT(*) FROM base) / 15) + 1) TOTAL_PAGES_TO_FETCH, 
         ((RNUM - MOD(RNUM, 15)) / 15) + 1 PAGE_TO_FETCH,
         B.*
  FROM base B
) V
WHERE V.PAGE_TO_FETCH = xx

ここで、XXは必要なページです。

上記のソリューションには、最初のページがPAGE_SIZE-1の結果を返す原因となった元のコードに対する小さなバグ修正が含まれています。

0
Spyros