Paginación con Oracle

97

No estoy tan familiarizado con Oracle como me gustaría. Tengo unos 250k registros y quiero mostrarlos 100 por página. Actualmente tengo un procedimiento almacenado que recupera todos los cuartos de millón de registros en un conjunto de datos utilizando un adaptador de datos, un conjunto de datos y el método dataadapter.Fill (conjunto de datos) en los resultados del proceso almacenado. Si tengo "Número de página" y "Número de registros por página" como valores enteros que puedo pasar como parámetros, ¿cuál sería la mejor manera de recuperar esa sección en particular? Digamos, si paso 10 como número de página y 120 como número de páginas, de la declaración de selección me daría del 1880 al 1200, o algo así, mis matemáticas en mi cabeza podrían estar equivocadas.

Estoy haciendo esto en .NET con C #, pensé que eso no es importante, si puedo hacerlo bien en el lado de SQL, entonces debería estar bien.

Actualización: pude usar la sugerencia de Brian y está funcionando muy bien. Me gustaría trabajar en alguna optimización, pero las páginas aparecen en 4 a 5 segundos en lugar de un minuto, y mi control de paginación pudo integrarse muy bien con mis nuevos procesos almacenados.

Stephenbayer
fuente

Respuestas:

144

Algo como esto debería funcionar: Del Blog de Frans Bouma

SELECT * FROM
(
    SELECT a.*, rownum r__
    FROM
    (
        SELECT * FROM ORDERS WHERE CustomerID LIKE 'A%'
        ORDER BY OrderDate DESC, ShippingDate DESC
    ) a
    WHERE rownum < ((pageNumber * pageSize) + 1 )
)
WHERE r__ >= (((pageNumber-1) * pageSize) + 1)
Brian Schmitt
fuente
4
Sí, es una columna 'incorporada' que admite Oracle, siempre comienza en 1 y se incrementa para cada fila. Entonces, en este fragmento de código, si tiene 1000 filas, se aplica el orden de clasificación y luego a cada fila se le asigna un rownum. Las selecciones externas usan esos números de fila para ubicar la 'página' que está buscando según el tamaño de su página.
Brian Schmitt
9
Esto es bueno, pero terriblemente lento en selecciones grandes, solo verifique cuál será el tiempo para seleccionar 0 a 1000 y 500,000 a 501,000 ... Estaba usando este tipo de estructura de selección ahora estoy buscando una solución.
Newhouse
3
@ n3whous3 podrías probar esto - inf.unideb.hu/~gabora/pagination/results.html
jasonk
7
Me preguntaba por qué WHEREno se podían combinar dos AND, y luego encontré esto: orafaq.com/wiki/ROWNUM
Mengdi Gao
1
La paginación de Oracle arruina mi día.
Aetherus
134

Pregúntale a Tom sobre paginación y funciones analíticas muy, muy útiles.

Esto es un extracto de esa página:

select * from (
    select /*+ first_rows(25) */
     object_id,object_name,
     row_number() over
    (order by object_id) rn
        from all_objects)
    where rn between :n and :m
        order by rn;
Chobicus
fuente
7
En realidad, esta es una implementación mucho mejor, aunque es difícil de encontrar en esa publicación. Cuando tiene muchas páginas grandes, la otra respuesta también debe abarcar todas las filas de las páginas anteriores. En consultas complicadas, esto significa que las páginas posteriores funcionan peor que las páginas anteriores.
tallseth
@tallseth Tienes razón. Es difícil encontrarlo en esa página. Se agrega un extracto.
Chobicus
Esta es la respuesta correcta si desea cambiar dinámicamente su pedido.
chakeda
74

En aras de la integridad, para las personas que buscan una solución más moderna, en Oracle 12c hay algunas características nuevas que incluyen una mejor paginación y un manejo superior.

Paginación

La paginación se ve así:

SELECT *
FROM user
ORDER BY first_name
OFFSET 5 ROWS FETCH NEXT 10 ROWS ONLY;

Top N Records

Obtener los mejores récords se ve así:

SELECT *
FROM user
ORDER BY first_name
FETCH FIRST 5 ROWS ONLY

Observe cómo los dos ejemplos de consulta anteriores tienen ORDER BYcláusulas. Los nuevos comandos los respetan y se ejecutan sobre los datos ordenados.

No pude encontrar una buena página de referencia de Oracle para FETCHo, OFFSETpero esta página tiene una excelente descripción general de estas nuevas funciones.

Actuación

Como señala @wweicker en los comentarios a continuación, el rendimiento es un problema con la nueva sintaxis en 12c. No tenía una copia de 18c para probar si Oracle lo ha mejorado desde entonces.

Curiosamente, mis resultados reales se devolvieron un poco más rápido la primera vez que ejecuté las consultas en mi tabla (113 millones + filas) para el nuevo método:

  • Nuevo método: 0,013 segundos.
  • Método antiguo: 0,107 segundos.

Sin embargo, como mencionó @wweicker, el plan de explicación se ve mucho peor para el nuevo método:

  • Costo del nuevo método: 300,110
  • Costo del método antiguo: 30

La nueva sintaxis provocó un escaneo completo del índice en mi columna, que fue el costo total. Lo más probable es que las cosas empeoren mucho al limitar los datos no indexados.

Echemos un vistazo a la hora de incluir una única columna no indexada en el conjunto de datos anterior:

  • Tiempo / costo del nuevo método: 189.55 segundos / 998,908
  • Tiempo / costo del método antiguo: 1.973 segundos / 256

Resumen: utilícelo con precaución hasta que Oracle mejore este manejo. Si tiene un índice con el que trabajar, tal vez pueda utilizar el nuevo método.

Con suerte tendré una copia de 18c para jugar pronto y puedo actualizar

JoelC
fuente
Esta es una gran respuesta para los usuarios de 12c
Lalji Gajera
1
La sintaxis es más limpia, pero el rendimiento es peor ( dba-presents.com/index.php/databases/oracle/… )
wweicker
Es bueno saberlo, gracias @wweicker. Es de esperar que Oracle solucione pronto el rendimiento; aunque, conociendo a Oracle, ¡esto podría ser una esperanza lejana!
JoelC
La sintaxis es nueva y se transforma en llamadas regulares ROW_NUMBER / RANK. Relacionado ¿Cómo limito el número de filas devueltas por una consulta de Oracle después de realizar el pedido?
Lukasz Szozda
@JoelC ¿ha habido algún cambio en tu opinión?
Ryan
11

Solo quiero resumir las respuestas y comentarios. Hay varias formas de realizar una paginación.

Antes de Oracle 12c no existía la funcionalidad OFFSET / FETCH, así que eche un vistazo al documento técnico como sugirió @jasonk. Es el artículo más completo que encontré sobre diferentes métodos con una explicación detallada de las ventajas y desventajas. Tomaría una cantidad significativa de tiempo copiarlos y pegarlos aquí, así que no lo haré.

También hay un buen artículo de los creadores de jooq que explica algunas advertencias comunes con la paginación de Oracle y otras bases de datos. entrada de blog de jooq

Buenas noticias, desde Oracle 12c tenemos una nueva funcionalidad OFFSET / FETCH. Nuevas funciones de OracleMagazine 12c . Consulte "Paginación y consultas Top-N"

Puede verificar su versión de Oracle emitiendo la siguiente declaración

SELECT * FROM V$VERSION
Vadim Kirilchuk
fuente
7

Intente lo siguiente:

SELECT *
FROM
  (SELECT FIELDA,
    FIELDB,
    FIELDC,
    ROW_NUMBER() OVER (ORDER BY FIELDC) R
  FROM TABLE_NAME
  WHERE FIELDA = 10
  )
WHERE R >= 10
AND R   <= 15;

vía [tecnicume]

Furetto
fuente
0

En mi proyecto utilicé Oracle 12c y java . El código de paginación se ve así:

 public public List<Map<String, Object>> getAllProductOfferWithPagination(int pageNo, int pageElementSize, Long productOfferId, String productOfferName) {
    try {

        if(pageNo==1){
            //do nothing
        } else{
            pageNo=(pageNo-1)*pageElementSize+1;
        }
        System.out.println("algo pageNo: " + pageNo +"  pageElementSize: "+ pageElementSize+"  productOfferId: "+ productOfferId+"  productOfferName: "+ productOfferName);

        String sql = "SELECT * FROM ( SELECT * FROM product_offer po WHERE po.deleted=0 AND (po.product_offer_id=? OR po.product_offer_name LIKE ? )" +
             " ORDER BY po.PRODUCT_OFFER_ID asc) foo OFFSET ? ROWS FETCH NEXT ? ROWS ONLY ";

       return jdbcTemplate.queryForList(sql,new Object[] {productOfferId,"%"+productOfferName+"%",pageNo-1, pageElementSize});

    } catch (Exception e) {
        System.out.println(e);
        e.printStackTrace();
        return null;
    }
Ferdous Wahid
fuente