2010-05-25 15 views
17

Sto usando Oracle 10g e il seguente paradigma per ottenere una pagina di 15 risultati come una volta (in modo che quando l'utente sta visualizzando la pagina 2 di un risultato di ricerca, vedono i record 16-30).(Oracle) Come ottenere il numero totale di risultati quando si utilizza una query di impaginazione?

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

In questo momento mi sto dover eseguire un'istruzione SQL separato per fare un "select count" on "my_query" al fine di ottenere il numero totale di risultati per my_query (in modo che io possa mostrare al utente e utilizzarlo per calcolare il numero totale di pagine, ecc.).

C'è un modo per ottenere il numero totale di risultati senza farlo tramite una seconda query, cioè ottenendo da sopra query? Ho provato ad aggiungere "max (rownum)", ma non sembra funzionare (ottengo un errore [ORA-01747] che sembra indicare che non mi piace avere la parola chiave rownum nel gruppo di).

Il mio razionale per volere ottenere questo dalla query originale piuttosto che farlo in un'istruzione SQL separata è che "my_query" è una query costosa quindi preferisco non eseguirlo due volte (una volta per ottenere il conteggio, e una volta per ottenere la pagina di dati) se non devo; ma qualunque sia la soluzione che riesco a ottenere per ottenere il numero di risultati da una singola query (e allo stesso tempo ottenere la pagina di dati che mi serve) non dovrebbe aggiungere molto, se possibile, a un eventuale overhead aggiuntivo. Si prega di avvisare.

Ecco esattamente ciò che sto cercando di fare per il quale ricevo un errore ORA-01747 perché ritengo che non mi piaccia avere ROWNUM nel gruppo. Nota, se c'è un'altra soluzione che non usa max (ROWNUM), ma qualcos'altro, va benissimo anche lui. Questa soluzione è stata la mia prima riflessione su cosa potrebbe funzionare.

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 nota, in base al primo commento che ho provato la seguente che sembra funzionare. Non so però come si comporta bene rispetto ad altre soluzioni (sto cercando la soluzione che soddisfa il mio requisito ma che offre il meglio). Ad esempio, quando lo eseguo ci vogliono 16 secondi. Quando prendo il COUNT (*) OVER() RESULT_COUNT ci vogliono appena 7 secondi:

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 

La spiegare i cambiamenti del piano di fare una sorta (ORDER BY TASTO STOP) per fare una finestra (SORT).

Prima:

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 

Dopo:

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

L'altro giorno, ho visto qualcuno usare 'selezionare a. *, Contare (*) su() da un ...' per ottenere il conteggio totale in ogni riga.Trucco accurato, ma non so esattamente come applicarlo qui :( – FrustratedWithFormsDesigner

+2

Non sono sicuro di come funzioni questa query, ma se è qualcosa di relativamente statico, potresti ottenere il conteggio una volta e metterlo in una cache in una variabile applicativa, e poi ri-contare solo quando cambia la query? Non so se c'è un modo per ottenere il conteggio di un set di risultati senza effettivamente contare tutti i record. – FrustratedWithFormsDesigner

+0

Cosa fai quando hai più suggerimenti che funzionerebbero allo stesso modo di beh? Quale dovresti contrassegnare in overflow come risposta? – BestPractices

risposta

19

io penso che si debba modificare la query per qualcosa di simile per ottenere tutte le informazioni desiderate su un query "singola".

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 

La ragione è che la funzione COUNT(*) OVER() finestra viene valutata dopo la clausola WHERE, quindi non dando il conteggio totale di record ma il conteggio di record che soddisfano la condizione ROWNUM <= 30.

Se non potete accettare le prestazioni ot questa query, o di eseguire query separate 2, forse si dovrebbe pensare a una soluzione come quella proposta da FrustratedWithFormsDesigner nella sua/il suo commento sulla memorizzazione nella cache il conteggio dei record.

Se si lavora regolarmente con i database, si consiglia di ottenere una copia di SQL Cookbook. È un libro eccezionale con molti consigli utili.

+0

Questo è diverso dall'SQL che ho postato nella sezione modificata? – BestPractices

+0

La differenza è che si ha una clausola where nella prima query interna (quella con la funzione window per calcolare il conteggio totale). La clausola where viene valutata prima della funzione della finestra e quindi la query sta effettivamente contando il numero di record il cui numero di riga è inferiore o uguale a 30. –

+0

Grazie; Ho trasposto il mio SQL effettivo per impilare l'overflow e ho inserito il conteggio (*) sopra() nel posto sbagliato. Ho aggiornato l'OP con l'SQL corretto. – BestPractices

1

funziona?

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

Grazie, ma anche se ha funzionato, sarebbe stato necessario eseguire "my_query" due volte, che è quello che sto cercando di impedire di fare. – BestPractices

+0

@BestPractices: potresti includere 'count (*) over() total' nella SELECT di' my_que ry'? – FrustratedWithFormsDesigner

+0

post originale aggiornato per rispondere alla tua domanda in dettaglio (funziona, ma funziona in modo indesiderato) – BestPractices

0

No, non può farlo senza né di eseguire la query due volte, o l'esecuzione di una volta e il recupero e la memorizzazione nella cache tutti le righe di contarli prima di iniziare a visualizzarli. Né è auspicabile, soprattutto se la tua query è costosa o potenzialmente restituisce un sacco di righe.

di Oracle proprio Application Express (Apex) strumento offre una scelta di opzioni di impaginazione:

  1. Il più efficiente indica semplicemente se o non ci sono "più" righe. Per fare ciò recupera solo una riga in più rispetto al massimo corrente della pagina (ad esempio 31 righe per la pagina che mostra le righe 16-30).
  2. Oppure è possibile visualizzare un conteggio limitato che può mostrare "16-30 di 67" o "16-30 di oltre 200". Ciò significa che recupera fino a 201 righe (in questo esempio). Questo non è efficiente come l'opzione 1, ma più efficiente dell'opzione 3.
  3. Oppure è possibile visualizzare "16-30 di 13.945". Per fare questo Apice è necessario recuperare tutti i 13.945 ma scartare tutti tranne le righe 15-30. Questo è il metodo più lento e meno efficiente.

Lo pseudo-PL/SQL per l'opzione 3 (la vostra preferenza) sarebbe:

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
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 

Questa query calcolerà quanti gruppi di pagine è necessario recuperare e recuperare i dati come una query.

Dal set di risultati, elaborare 15 righe alla volta. L'ultima serie di righe, può essere più breve di 15.

+0

questo non funziona per quello che devo fare – BestPractices

+1

Che aspetto manca? – EvilTeach

+0

sintassi necessaria correzione –

1

Solo un suggerimento:

Si potrebbe prendere in considerazione il Google "1-10 di circa 13.000.000 risultati" approccio - eseguire il COUNT (*) come un rapido esempio sulla query originale. Ho assunto qui che c'è al massimo un XYZ per un dato 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 

Ovviamente, il campione sarà molto imprecise e variabili, quindi dipende dai requisiti se questo sia opportuno o meno.

+0

Un altro suggerimento molto buono ... questo potrebbe funzionare per noi per alcuni dei nostri rapporti che hanno 50.000 risultati (se possiamo chiedere al cliente di approvare la modifica del requisito). – BestPractices

1

Un'altra soluzione potrebbe essere quella di creare una vista materializzata che mantenga i conteggi per ciascun valore di ABC.XYZ_ID - in questo modo si spinge l'onere di ottenere il conteggio dei processi che inseriscono/aggiornano/eliminano le righe nella tabella.

+0

Buon suggerimento – BestPractices

0

di costruire su risposta di 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 

dove XX è la pagina che si desidera.

La soluzione di cui sopra include una piccola correzione del codice originale che ha causato la restituzione della prima pagina a PAGE_SIZE - 1 risultati.

Problemi correlati