Manera rápida de descubrir el recuento de filas de una tabla en PostgreSQL

107

Necesito saber el número de filas en una tabla para calcular un porcentaje. Si el recuento total es mayor que alguna constante predefinida, usaré el valor constante. De lo contrario, usaré el número real de filas.

Puedo usar SELECT count(*) FROM table. Pero si mi valor constante es 500,000 y tengo 5,000,000,000 filas en mi tabla, contar todas las filas desperdiciará mucho tiempo.

¿Es posible dejar de contar tan pronto como se supere mi valor constante?

Solo necesito el número exacto de filas siempre que esté por debajo del límite dado. De lo contrario, si el recuento está por encima del límite, utilizo el valor límite y quiero la respuesta lo más rápido posible.

Algo como esto:

SELECT text,count(*), percentual_calculus()  
FROM token  
GROUP BY text  
ORDER BY count DESC;
Renato Dinhani
fuente
5
¿No podría simplemente intentar seleccionar las primeras n filas donde n = constante + 1 ? Si devuelve más que su constante, ¿sabe que debe usar su constante, y si no es así, es bueno?
gddc
¿Tiene una identidad o un campo de incremento automático en la tabla
Sparky
1
@Sparky: No se garantiza que los PK respaldados por secuencia sean contiguos, las filas se pueden eliminar o podría haber brechas causadas por transacciones abortadas.
mu es demasiado corto
Su actualización parece contradecir su pregunta original ... ¿necesita saber el número exacto de filas, o solo necesita saber el número exacto si está por debajo de un umbral?
Flimzy
1
@ RenatoDinhaniConceição: ¿Puede explicar el problema exacto que está tratando de resolver? Creo que mi respuesta a continuación resuelve lo que inicialmente dijo que era su problema. La actualización hace que parezca que desea contar (*) así como muchos otros campos. Sería útil si pudiera explicar exactamente lo que está tratando de hacer. Gracias.
Ritesh

Respuestas:

224

Se sabe que el recuento de filas en tablas grandes es lento en PostgreSQL. Para obtener un número preciso, debe realizar un recuento completo de filas debido a la naturaleza de MVCC . Hay una manera de acelerar esto drásticamente si el recuento no tiene que ser exacto como parece ser en su caso.

En lugar de obtener el recuento exacto ( lento con tablas grandes):

SELECT count(*) AS exact_count FROM myschema.mytable;

Obtienes una estimación cercana como esta ( extremadamente rápido ):

SELECT reltuples::bigint AS estimate FROM pg_class where relname='mytable';

Qué tan cerca esté la estimación depende de si corres lo ANALYZEsuficiente. Suele estar muy cerca.
Consulte las preguntas frecuentes de la Wiki de PostgreSQL .
O la página wiki dedicada para el rendimiento de conteo (*) .

Mejor todavía

El artículo en el PostgreSQL Wiki se fue un poco descuidado . Ignoró la posibilidad de que pueda haber varias tablas con el mismo nombre en una base de datos, en diferentes esquemas. Para dar cuenta de eso:

SELECT c.reltuples::bigint AS estimate
FROM   pg_class c
JOIN   pg_namespace n ON n.oid = c.relnamespace
WHERE  c.relname = 'mytable'
AND    n.nspname = 'myschema'

O mejor aun

SELECT reltuples::bigint AS estimate
FROM   pg_class
WHERE  oid = 'myschema.mytable'::regclass;

Más rápido, más simple, más seguro, más elegante. Consulte el manual sobre tipos de identificadores de objetos .

Úselo to_regclass('myschema.mytable')en Postgres 9.4+ para evitar excepciones para nombres de tablas no válidos:


TABLESAMPLE SYSTEM (n) en Postgres 9.5+

SELECT 100 * count(*) AS estimate FROM mytable TABLESAMPLE SYSTEM (1);

Como comentó @a_horse , la cláusula recién agregada para el SELECTcomando podría ser útil si las estadísticas en pg_classno están lo suficientemente actualizadas por alguna razón. Por ejemplo:

  • No autovacuumcorrer.
  • Inmediatamente después de un gran INSERTo DELETE.
  • TEMPORARYtablas (que no están cubiertas por autovacuum).

Esto solo mira una selección aleatoria de bloques n % ( 1en el ejemplo) y cuenta las filas en ella. Una muestra más grande aumenta el costo y reduce el error, su elección. La precisión depende de más factores:

  • Distribución del tamaño de las filas. Si un bloque dado tiene filas más anchas de lo habitual, el recuento es más bajo de lo habitual, etc.
  • Tuplas muertas o un FILLFACTORespacio ocupado por bloque. Si se distribuye de manera desigual en la tabla, es posible que la estimación sea incorrecta.
  • Errores generales de redondeo.

En la mayoría de los casos, la estimación de pg_classserá más rápida y precisa.

Respuesta a la pregunta real

Primero, necesito saber el número de filas en esa tabla, si el recuento total es mayor que alguna constante predefinida,

Y si ...

... es posible en el momento en que el conteo pase mi valor constante, se detendrá el conteo (y no esperará a terminar el conteo para informar que el conteo de filas es mayor).

Si. Puede usar una subconsulta conLIMIT :

SELECT count(*) FROM (SELECT 1 FROM token LIMIT 500000) t;

Postgres realmente deja de contar más allá del límite dado, obtiene un recuento exacto y actual para hasta n filas (500000 en el ejemplo) y n de lo contrario. Sin pg_classembargo, no tan rápido como la estimación .

Erwin Brandstetter
fuente
8
Finalmente actualicé la página Wiki de Postgres con la consulta mejorada.
Erwin Brandstetter
5
Con 9.5, debería ser posible obtener una estimación rápida utilizando la tablesamplecláusula: por ejemploselect count(*) * 100 as cnt from mytable tablesample system (1);
a_horse_with_no_name
1
@JeffWidman: Todas estas estimaciones pueden ser mayores que el recuento real de filas por varias razones. No menos importante, es posible que hayan ocurrido eliminaciones mientras tanto.
Erwin Brandstetter
2
@ErwinBrandstetter se da cuenta de que esta pregunta es antigua, pero si envuelve la consulta en una subconsulta, ¿el límite seguirá siendo eficiente o se ejecutará toda la subconsulta y luego se limitará en la consulta externa? SELECT count(*) FROM (Select * from (SELECT 1 FROM token) query) LIMIT 500000) limited_query;(Lo pregunto porque estoy tratando de obtener un recuento de una consulta arbitraria que ya podría tener una cláusula de límite)
Nicholas Erdenberger
1
@NicholasErdenberger: Eso depende de la subconsulta. Es posible que Postgres deba considerar más filas que el límite de todos modos (como con ORDER BY somethingmientras no puede usar un índice o con funciones agregadas). Aparte de eso, solo se procesa el número limitado de filas de la subconsulta.
Erwin Brandstetter
12

Hice esto una vez en una aplicación de postgres ejecutando:

EXPLAIN SELECT * FROM foo;

Luego, examina la salida con una expresión regular o una lógica similar. Para un SELECT * simple, la primera línea de salida debería verse así:

Seq Scan on uids  (cost=0.00..1.21 rows=8 width=75)

Puede usar el rows=(\d+)valor como una estimación aproximada del número de filas que se devolverán, luego solo haga el valor real SELECT COUNT(*)si la estimación es, digamos, menos de 1,5 veces su umbral (o cualquier número que considere que tiene sentido para su aplicación).

Según la complejidad de su consulta, este número puede volverse cada vez menos preciso. De hecho, en mi aplicación, a medida que agregamos uniones y condiciones complejas, se volvió tan inexacto que fue completamente inútil, incluso para saber cómo dentro de una potencia de 100 cuántas filas habríamos devuelto, por lo que tuvimos que abandonar esa estrategia.

Pero si su consulta es lo suficientemente simple como para que Pg pueda predecir con un margen de error razonable cuántas filas devolverá, puede funcionar para usted.

Flimzy
fuente
2

Referencia extraída de este Blog.

Puede utilizar a continuación para consultar y encontrar el recuento de filas.

Usando pg_class:

 SELECT reltuples::bigint AS EstimatedCount
    FROM   pg_class
    WHERE  oid = 'public.TableName'::regclass;

Usando pg_stat_user_tables:

SELECT 
    schemaname
    ,relname
    ,n_live_tup AS EstimatedCount 
FROM pg_stat_user_tables 
ORDER BY n_live_tup DESC;
Anvesh
fuente
Solo tenga en cuenta que necesita ANALIZAR AL VACÍO sus tablas para que este método funcione.
William Abma
1

En Oracle, puede utilizar rownumpara limitar el número de filas devueltas. Supongo que también existe una construcción similar en otros SQL. Entonces, para el ejemplo que dio, podría limitar el número de filas devueltas a 500001 y aplicar un count(*)entonces:

SELECT (case when cnt > 500000 then 500000 else cnt end) myCnt
FROM (SELECT count(*) cnt FROM table WHERE rownum<=500001)
Ritesh
fuente
1
SELECT count (*) cnt FROM table siempre devolverá una sola fila. No estoy seguro de cómo LIMIT agregará algún beneficio allí.
Chris Bednarski
@ChrisBednarski: Verifiqué la versión de Oracle de mi respuesta en una base de datos de Oracle. Funciona muy bien y resuelve lo que pensé que era el problema de OP (0.05 s count(*)con rownum, 1 s sin el uso de rownum). Sí, SELECT count(*) cnt FROM tablesiempre devolverá 1 fila, pero con la condición LIMIT, devolverá "500001" cuando el tamaño de la tabla sea superior a 500000 y <tamaño> cuando el tamaño de la tabla sea <= 500000.
Ritesh
2
Su consulta de PostgreSQL es una completa tontería. Sintáctica y lógicamente incorrecto. Corríjala o elimínela.
Erwin Brandstetter
@ErwinBrandstetter: eliminado, no me di cuenta de que PostgreSQL era tan diferente.
Ritesh
@allrite: sin duda, su consulta de Oracle funciona bien. Sin embargo, LIMIT funciona de manera diferente. En un nivel básico, limita el número de filas devueltas al cliente, no el número de filas consultadas por el motor de la base de datos.
Chris Bednarski
0

¿Qué tan ancha es la columna de texto?

Con GROUP BY no hay mucho que pueda hacer para evitar un escaneo de datos (al menos un escaneo de índice).

Yo lo recomiendo:

  1. Si es posible, cambie el esquema para eliminar la duplicación de datos de texto. De esta manera, el recuento ocurrirá en un campo de clave externa estrecho en la tabla 'muchos'.

  2. Alternativamente, crear una columna generada con un HASH del texto, luego AGRUPAR POR la ​​columna de hash. Nuevamente, esto es para disminuir la carga de trabajo (escanee a través de un índice de columna estrecho)

Editar:

Tu pregunta original no coincidía con tu edición. No estoy seguro de si sabe que COUNT, cuando se usa con GROUP BY, devolverá el recuento de elementos por grupo y no el recuento de elementos en toda la tabla.

Chris Bednarski
fuente
0

Puede obtener el recuento mediante la siguiente consulta (sin * ni ningún nombre de columna).

select from table_name;
SuperNova
fuente
2
Esto no parece ser más rápido que count(*).
Soleado
-3

Para SQL Server (2005 o superior), un método rápido y confiable es:

SELECT SUM (row_count)
FROM sys.dm_db_partition_stats
WHERE object_id=OBJECT_ID('MyTableName')   
AND (index_id=0 or index_id=1);

Los detalles sobre sys.dm_db_partition_stats se explican en MSDN

La consulta agrega filas de todas las partes de una tabla (posiblemente) particionada.

index_id = 0 es una tabla desordenada (Heap) e index_id = 1 es una tabla ordenada (índice agrupado)

Aquí se detallan métodos aún más rápidos (pero poco fiables) .

DrKoch
fuente