(Oracle) Как получить общее количество результатов при использовании запроса разбивки на страницы? - PullRequest
18 голосов
/ 25 мая 2010

Я использую Oracle 10g и следующую парадигму, чтобы получить страницу из 15 результатов за раз (так что, когда пользователь просматривает страницу 2 результата поиска, он видит записи 16-30).

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

Прямо сейчас мне нужно запустить отдельный оператор SQL, чтобы выполнить «выбор подсчета» для «my_query», чтобы получить общее количество результатов для my_query (чтобы я мог показать его пользователю и использовать его выяснить общее количество страниц и т. д.).

Есть ли способ получить общее количество результатов, не делая этого с помощью второго запроса, то есть, получая его из запроса выше? Я попытался добавить "max (rownum)", но, похоже, он не работает (я получаю ошибку [ORA-01747], которая, кажется, указывает на то, что мне не нравится иметь ключевое слово rownum в группе).

Мое обоснование желания получить это из исходного запроса, а не делать это в отдельном операторе SQL, заключается в том, что «my_query» является дорогим запросом, поэтому я бы не стал его выполнять дважды (один раз, чтобы получить счетчик, и один раз чтобы получить страницу данных), если мне не нужно; но какое бы решение я ни нашел, чтобы получить количество результатов в одном запросе (и в то же время получить нужную мне страницу данных), не следует добавлять много, если возможно, дополнительные накладные расходы. Пожалуйста, сообщите.

Вот именно то, что я пытаюсь сделать, для чего я получаю ошибку ORA-01747, потому что я считаю, что мне не нравится иметь ROWNUM в группе. Обратите внимание: если есть другое решение, которое не использует 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

--------- РЕДАКТИРОВАТЬ -------- Обратите внимание, на основании первого комментария я попробовал следующее, которое, кажется, работает. Я не знаю, насколько хорошо он работает по сравнению с другими решениями (я ищу решение, которое удовлетворяет моим требованиям, но работает лучше). Например, когда я запускаю это, это занимает 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

План объяснения меняется с выполнения 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

Ответы [ 8 ]

20 голосов
/ 25 мая 2010

Я думаю, что вы должны изменить свой запрос на что-то вроде этого, чтобы получить всю информацию, которую вы хотите получить по «одному» запросу.

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 . Это исключительная книга с множеством полезных советов.

2 голосов
/ 26 мая 2010

Просто предложение:

Вы можете рассмотреть подход Google " 1-10 из примерно 13 000 000 результатов " - запустите COUNT (*) в качестве быстрой выборки поверх исходного запроса.Я предположил, что для данного 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 

существует не более одного XYZ. Очевидно, что выборка будет довольно неточной и переменной, поэтому это зависит от требований, является ли этоподходит или нет.

1 голос
/ 26 мая 2010

Другим решением было бы создание материализованного представления, которое ведет подсчет для каждого значения ABC.XYZ_ID - таким образом вы возлагаете бремя на получение подсчета на процессы, которые вставляют / обновляют / удаляют строки в таблице.

1 голос
/ 25 мая 2010
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

Этот запрос вычислит, сколько групп страниц вам нужно будет извлечь, и извлечет данные как один запрос.

Из набора результатов обрабатывать 15 строк одновременно. Самый последний набор строк может быть короче 15.

1 голос
/ 25 мая 2010

Нет, вы не можете сделать это, не выполнив дважды запрос или не выполнив его один раз и не извлекая и не кэшируя все строки, чтобы подсчитать их, прежде чем начать их отображать. Это нежелательно, особенно если ваш запрос дорогой или потенциально возвращает много строк.

Собственный инструмент Oracle Express (Apex) предлагает выбор параметров нумерации страниц:

  1. Наиболее эффективный просто указывает, есть или нет «больше» строк. Для этого он выбирает только на одну строку больше, чем максимум текущей страницы (например, 31 строка для страницы, показывающей строки 16-30).
  2. Или вы можете показать ограниченное число, которое может показывать «16-30 из 67» или «16-30 из более чем 200». Это означает, что можно получить до 201 (в данном примере) строк. Это не так эффективно, как вариант 1, но более эффективно, чем вариант 3.
  3. Или вы действительно можете показать «16-30 из 13,945». Для этого Апекс должен извлечь все 13 945, но сбросить все, кроме строк 15-30. Это самый медленный, наименее эффективный метод.

Псевдо-PL / SQL для варианта 3 (ваше предпочтение) будет:

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 голос
/ 25 мая 2010

Это работает?

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;
0 голосов
/ 21 ноября 2018

Для Oracle 12c это будет работать:

select a.*, count(*)over() from mytable a offset 10 fetch first 10 rows only 
0 голосов
/ 17 февраля 2016

Чтобы построить ответ 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 результат.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...