Ejecute una consulta con LIMIT / OFFSET y también obtenga el número total de filas


90

Para fines de paginación, necesito ejecutar una consulta con las cláusulas LIMITy OFFSET. Pero también necesito un recuento de la cantidad de filas que devolvería esa consulta sin las cláusulas LIMITy OFFSET.

Quiero correr:

SELECT * FROM table WHERE /* whatever */ ORDER BY col1 LIMIT ? OFFSET ?

Y:

SELECT COUNT(*) FROM table WHERE /* whatever */

Al mismo tiempo. ¿Hay alguna forma de hacerlo, en particular una forma que permita a Postgres optimizarlo, de modo que sea más rápido que ejecutar ambos individualmente?


Respuestas:


168

Si. Con una función de ventana simple:

SELECT *, count(*) OVER() AS full_count
FROM   tbl
WHERE  /* whatever */
ORDER  BY col1
OFFSET ?
LIMIT  ?

Tenga en cuenta que el costo será sustancialmente más alto que sin el número total, pero generalmente más económico que dos consultas separadas. Postgres tiene que contar todas las filas de cualquier manera, lo que impone un costo que depende del número total de filas calificadas. Detalles:

Sin embargo , como señaló Dani , cuando OFFSETes al menos tan grande como el número de filas devueltas de la consulta base, no se devuelven filas. Entonces tampoco lo conseguimos full_count.

Si eso no es aceptable, una posible solución para devolver siempre el recuento completo sería con un CTE y un OUTER JOIN:

WITH cte AS (
   SELECT *
   FROM   tbl
   WHERE  /* whatever */
   )
SELECT *
FROM  (
   TABLE  cte
   ORDER  BY col1
   LIMIT  ?
   OFFSET ?
   ) sub
RIGHT  JOIN (SELECT count(*) FROM cte) c(full_count) ON true;

Obtiene una fila de valores NULL con el full_countif adjunto OFFSETes demasiado grande. De lo contrario, se agrega a cada fila como en la primera consulta.

Si una fila con todos los valores NULL es un posible resultado válido, debe verificar offset >= full_countpara eliminar la ambigüedad del origen de la fila vacía.

Esto aún ejecuta la consulta base solo una vez. Pero agrega más gastos generales a la consulta y solo paga si eso es menos que repetir la consulta base para el recuento.

Si los índices que soportan el orden de clasificación final están disponibles, podría ser útil incluirlos ORDER BYen el CTE (de forma redundante).


3
Tanto por LIMIT como por condiciones, tenemos filas para devolver, pero con el desplazamiento dado no devolvería ningún resultado. En esa situación, ¿cómo podríamos obtener el recuento de filas?
Dani Mathew

muy bien, gracias, funciona muy bien cuando usa paginación, tablas de datos, solo agregue esto al inicio de su sql y úselo, guarde una consulta adicional para el recuento total.
Ahmed Sunny

¿Podría dar más detalles sobre esto si el recuento se pudiera habilitar dinámicamente en la consulta mediante un parámetro de entrada? Tengo un requisito similar pero el usuario decide si quiere el recuento en línea o no.
julealgon

1
@julealgon: Inicie una nueva pregunta con los detalles definitorios. Siempre puede vincular a este para el contexto y agregar un comentario aquí para vincular (y llamar mi atención) si lo desea.
Erwin Brandstetter

1
@JustinL .: La sobrecarga agregada solo debería ser significativa para consultas base relativamente baratas. Además, Postgres 12 ha mejorado el rendimiento de CTE de varias formas. (Aunque este CTE sigue siendo el MATERIALIZEDpredeterminado, se hace referencia dos veces)
Erwin Brandstetter

1

editar: esta respuesta es válida al recuperar la tabla sin filtrar. Lo dejaré en caso de que pueda ayudar a alguien, pero es posible que no responda exactamente a la pregunta inicial.

La respuesta de Erwin Brandstetter es perfecta si necesita un valor exacto. Sin embargo, en tablas grandes a menudo solo se necesita una aproximación bastante buena. Postgres le brinda precisamente eso y será mucho más rápido ya que no necesitará evaluar cada fila:

SELECT *
FROM (
    SELECT *
    FROM tbl
    WHERE /* something */
    ORDER BY /* something */
    OFFSET ?
    LIMIT ?
    ) data
RIGHT JOIN (SELECT reltuples FROM pg_class WHERE relname = 'tbl') pg_count(total_count) ON true;

En realidad, no estoy seguro de si existe una ventaja para externalizar RIGHT JOINo tenerlo como en una consulta estándar. Merecería algunas pruebas.

SELECT t.*, pgc.reltuples AS total_count
FROM tbl as t
RIGHT JOIN pg_class pgc ON pgc.relname = 'tbl'
WHERE /* something */
ORDER BY /* something */
OFFSET ?
LIMIT ?

2
Acerca de la estimación del conteo rápido: stackoverflow.com/a/7945274/939860 Como dijiste: válido al recuperar la tabla completa, lo que se contradice con la WHEREcláusula de tus consultas. La segunda consulta es lógicamente incorrecta (recupera una fila por cada tabla en la base de datos) y es más cara cuando se corrige.
Erwin Brandstetter

-7

Es una mala práctica llamar dos veces la misma consulta para Just para obtener el número total de filas del resultado de retorno. Tomará tiempo de ejecución y desperdiciará los recursos del servidor.

Mejor, puede usar SQL_CALC_FOUND_ROWSen la consulta que le dirá a MySQL que obtenga el número total de filas junto con los resultados de la consulta de límite.

Ejemplo establecido como:

SELECT SQL_CALC_FOUND_ROWS employeeName, phoneNumber FROM employee WHERE employeeName LIKE 'a%' LIMIT 10;

SELECT FOUND_ROWS();

En la consulta anterior, simplemente agregue la SQL_CALC_FOUND_ROWSopción en el resto de la consulta requerida y ejecute la segunda línea, es decir, SELECT FOUND_ROWS()devuelve el número de filas en el conjunto de resultados devuelto por esa declaración.


1
La solución requiere postgres, no mysql.
MuffinMan

@MuffinMan, puedes usar lo mismo en mysql. Desde MYSQL 4.0, se está utilizando la opción SQL_CALC_FOUND_ROWS en la consulta. Pero de MYSQL 8.0 está obsoleto.
Mohd Rashid

Irrelevante. Esta pregunta fue respondida hace años. Si desea contribuir, publique una nueva pregunta con el mismo tema pero específica de MySQL.
MuffinMan

Sea siempre relevante
Ali Hussain

-14

No.

Quizás haya una pequeña ganancia que teóricamente podría obtener al ejecutarlos individualmente con suficiente maquinaria complicada debajo del capó. Pero, si desea saber cuántas filas coinciden con una condición, tendrá que contarlas en lugar de solo un subconjunto LIMITADO.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.