2010-05-25 19 views
17

Estoy usando Oracle 10g y el siguiente paradigma para obtener una página de 15 resultados por vez (de modo que cuando el usuario mira la página 2 de un resultado de búsqueda, vean los registros 16-30).(Oracle) ¿Cómo obtener el número total de resultados al usar una consulta de paginación?

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

En este momento estoy tener que ejecutar una instrucción SQL por separado a un "select count" en "my_query" con el fin de obtener el número total de resultados de my_query (para que pueda mostrar a la usuario y utilícelo para calcular el número total de páginas, etc.).

¿Hay alguna forma de obtener el número total de resultados sin hacer esto mediante una segunda consulta, es decir, obteniéndolo de la consulta anterior? He intentado agregar "max (rownum)", pero parece que no funciona (aparece un error [ORA-01747] que parece indicar que no me gusta tener la palabra clave rownum en el grupo).

Mi razonamiento para querer obtener esto de la consulta original en lugar de hacerlo en una declaración SQL separada es que "my_query" es una consulta cara, así que prefiero no ejecutarla dos veces (una para obtener el conteo y una vez para obtener la página de datos) si no tengo que hacerlo; pero cualquier solución que se me ocurra para obtener el número de resultados de una sola consulta (y al mismo tiempo obtener la página de datos que necesito) no debería agregar mucho, si es posible, una sobrecarga adicional. Por favor avise.

Esto es exactamente lo que estoy tratando de hacer para lo cual recibo un error ORA-01747 porque creo que no me gusta que tenga ROWNUM en el grupo. Nota, si hay otra solución que no usa max (ROWNUM), sino otra cosa, también está perfectamente bien. Esta solución fue mi primer pensamiento en cuanto a lo que podría funcionar.

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 

--------- -------- EDITAR Nota, basado en el primer comentario He intentado lo siguiente que aparece a trabajar. Sin embargo, no sé qué tan bien funciona frente a otras soluciones (estoy buscando la solución que satisfaga mis necesidades pero que rinda mejor). Por ejemplo, cuando ejecuto esto, toma 16 segundos. Cuando llevo a cabo el COUNT (*) OVER() RESULT_COUNT se tarda sólo 7 segundos:

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 explicar los cambios de planes de hacer una especie (ORDER BY TECLA STOP) para hacer una ventana (SORT).

Antes:

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 

Después:

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

El otro día, vi a alguien usar 'select a. *, Count (*) over() from a ...' para obtener el recuento total en cada fila.Buen truco, pero no sé exactamente cómo aplicarlo aquí :( – FrustratedWithFormsDesigner

+2

No estoy seguro de cómo funciona esta consulta, pero si es algo relativamente estático, podrías obtener el conteo una vez y guardarlo en una variable de aplicación, y luego solo volver a contar cuando cambia la consulta. No sé si hay una forma de obtener el conteo de un conjunto de resultados sin contar realmente todos los registros. – FrustratedWithFormsDesigner

+0

¿Qué haces cuando tienes múltiples sugerencias que funcionarían igual que Bueno, ¿a cuál se supone que debes marcar en el desbordamiento de la pila como la respuesta? – BestPractices

Respuesta

19

yo creo que hay que modificar su consulta a algo como esto para obtener toda la información que desea en una consulta "única".

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 razón es que la función de ventana de COUNT(*) OVER() se evalúa después de la cláusula WHERE, por lo tanto, no dar el número total de registros pero el recuento de registros que cumplen la condición ROWNUM <= 30.

Si usted no puede aceptar el rendimiento ot esta consulta, o de ejecutar 2 consultas separadas, tal vez debería pensar en una solución como la propuesta por FrustratedWithFormsDesigner en su/su comentario sobre el almacenamiento en caché el recuento de registros.

Si trabaja con bases de datos de forma regular, le recomiendo obtener una copia de SQL Cookbook. Es un libro excepcional con muchos consejos útiles.

+0

¿Es esto diferente que el SQL que publiqué en la sección editada? – BestPractices

+0

La diferencia es que tiene una cláusula where en la primera consulta interna (la que tiene la función de ventana para calcular el recuento total). La cláusula where se evalúa antes de la función de ventana y, por lo tanto, la consulta en realidad está contando el número de registros cuyo número de fila es menor o igual a 30. –

+0

Gracias; Transpuse mi SQL actual al desbordamiento de pila y coloqué el recuento (*) sobre() en el lugar equivocado. He actualizado el OP con el SQL correcto. – BestPractices

1

hace este trabajo?

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

Gracias, pero incluso si funcionó, requeriría ejecutar "my_query" dos veces, que es lo que trato de evitar tener que hacer. – BestPractices

+0

@BestPractices: podría incluir 'count (*) over() total' en el SELECT de' my_que ry'? – FrustratedWithFormsDesigner

+0

publicación original actualizada para responder a su pregunta en detalle (funciona, pero funciona indeseablemente) – BestPractices

0

No, no puede hacerlo sin que ninguno de ejecutar la consulta dos veces, o correr una vez y ir a buscar y almacenamiento en caché todos las filas de contarlos antes de empezar a mostrarlos. Ninguno es deseable, especialmente si su consulta es costosa o potencialmente devuelve muchas filas.

propia Oracle Application Express de herramienta (Apex) ofrece una selección de opciones de paginación:

  1. El más eficiente simplemente indica si hay o no hay filas "más". Para ello, solo obtiene una fila más que el máximo de la página actual (por ejemplo, 31 filas para la página que muestra las filas 16-30).
  2. O puede mostrar un conteo limitado que puede mostrar "16-30 de 67" o "16-30 de más de 200". Esto significa que recupera hasta 201 (en este ejemplo) filas. Esto no es tan eficiente como la opción 1, pero es más eficiente que la opción 3.
  3. O puede, de hecho, mostrar "16-30 de 13.945". Para hacer esto, Apex tiene que buscar los 13.945 pero descartar todos los renglones 15-30. Este es el método más lento y menos eficiente.

El pseudo-PL/SQL para la opción 3 (su preferencia) sería:

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 

Esta consulta calculará cuántos grupos de páginas tendrá que recuperar y buscará los datos como una consulta.

Del conjunto de resultados, procese 15 filas a la vez. El último conjunto de filas, puede ser inferior a 15.

+0

esto no funciona para lo que necesito hacer – BestPractices

+1

¿Qué aspecto falta? – EvilTeach

+0

sintaxis necesaria corrección –

1

Sólo una sugerencia:

Se podría considerar el Google "1-10 de aproximadamente 13.000.000 resultados" enfoque - ejecutar el COUNT (*) como una muestra rápida sobre la consulta original. He asumido aquí que hay a lo sumo un XYZ para un determinado 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 

Obviamente, la muestra será bastante impreciso y variable, así que depende de los requisitos de si esto es apropiado o no.

+0

Otra muy buena sugerencia ... esto puede funcionar para algunos de nuestros informes que tienen 50,000 resultados (si podemos lograr que el cliente apruebe cambiar el requisito). – BestPractices

1

Otra solución sería crear una vista materializada que mantenga los recuentos para cada valor de ABC.XYZ_ID - de esa manera usted empuja la carga de obtener el recuento de los procesos que insertan/actualizan/eliminan filas en la tabla.

+0

Buena sugerencia – BestPractices

0

para construir sobre la respuesta de 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 

donde XX es la página que desea.

La solución anterior incluye una pequeña corrección de errores en el código original que provocó que la primera página devuelva PAGE_SIZE - 1 resultados.

Cuestiones relacionadas