(Oracle), Как получают общее количество результатов при использовании запроса разбиения на страницы?

Я использую Oracle 10 г и следующую парадигму для получения страницы 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 (так, чтобы я мог показать его пользователю и использовать его для выяснения общего количества страниц, и т.д.).

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

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

Вот точно, что я пытаюсь сделать, для которого я получаю РТЫ 01 747 ошибок, потому что я полагаю, что им не нравлюсь я имеющий ROWNUM в группе. Отметьте, Если существует другое решение, которое не использует макс. (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

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

Объяснить план изменяется от выполнения ВИДА (КЛЮЧ ОСТАНОВКИ ORDER BY), чтобы сделать ОКНО (ВИД).

Прежде:

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
18
задан BestPractices 26 May 2010 в 20:11
поделиться

6 ответов

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

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 .

Если вы не можете согласиться с производительностью этого запроса или выполнения двух отдельных запросов, возможно, вам следует подумать о решении, подобном предложенному FrustratedWithFormsDesigner в его / ее комментарии о кешировании количества записей. .

Если вы регулярно работаете с базами данных, я рекомендую вам получить копию SQL Cookbook . Это исключительная книга с множеством полезных советов.

23
ответ дан 30 November 2019 в 08:15
поделиться

Работает ли это?

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;
1
ответ дан 30 November 2019 в 08:15
поделиться

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

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

  1. Самый эффективный просто указывает, есть ли «больше» строк. Для этого он выбирает только на одну строку больше, чем максимум текущей страницы (например, 31 строка для страницы, показывающей строки 16-30).
  2. Или вы можете показать ограниченное количество, которое может показывать «16-30 из 67» или «16-30 из более чем 200». Это означает, что выполняется выборка до 201 (в этом примере) строки. Это не так эффективно, как вариант 1, но более эффективно, чем вариант 3.
  3. Или вы действительно можете показать «16-30 из 13 945». Для этого Apex должен получить все 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
ответ дан 30 November 2019 в 08:15
поделиться
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
ответ дан 30 November 2019 в 08:15
поделиться

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

Вы можете рассмотреть подход Google "1-10 из примерно 13 000 000 результатов" - запустить COUNT(*) в качестве быстрой выборки по исходному запросу. Я предположил, что для данного ABC существует не более одного XYZ:

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
ответ дан 30 November 2019 в 08:15
поделиться

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

1
ответ дан 30 November 2019 в 08:15
поделиться
Другие вопросы по тегам:

Похожие вопросы: