¿Cómo encuentra el recuento de filas para todas sus tablas en Postgres


395

Estoy buscando una manera de encontrar el recuento de filas para todas mis tablas en Postgres. Sé que puedo hacer esta tabla a la vez con:

SELECT count(*) FROM table_name;

pero me gustaría ver el recuento de filas para todas las tablas y luego ordenar por eso para tener una idea de qué tan grandes son todas mis tablas.

Respuestas:


582

Hay tres formas de obtener este tipo de conteo, cada una con sus propias compensaciones.

Si desea un recuento verdadero, debe ejecutar la instrucción SELECT como la que utilizó en cada tabla. Esto se debe a que PostgreSQL mantiene la información de visibilidad de la fila en la fila misma, no en ningún otro lugar, por lo que cualquier recuento exacto solo puede ser relativo a alguna transacción. Obtiene un recuento de lo que ve esa transacción en el momento en que se ejecuta. Puede automatizar esto para que se ejecute en cada tabla de la base de datos, pero probablemente no necesite ese nivel de precisión o no quiera esperar tanto.

El segundo enfoque señala que el recopilador de estadísticas rastrea aproximadamente cuántas filas están "activas" (no eliminadas u obsoletas por actualizaciones posteriores) en cualquier momento. Este valor puede estar un poco apagado bajo actividad intensa, pero generalmente es una buena estimación:

SELECT schemaname,relname,n_live_tup 
  FROM pg_stat_user_tables 
  ORDER BY n_live_tup DESC;

Eso también puede mostrar cuántas filas están muertas, lo cual es un número interesante para monitorear.

La tercera forma es tener en cuenta que el comando ANALIZAR del sistema, que se ejecuta regularmente mediante el proceso de vacío automático a partir de PostgreSQL 8.3 para actualizar las estadísticas de la tabla, también calcula una estimación de fila. Puedes agarrarlo así:

SELECT 
  nspname AS schemaname,relname,reltuples
FROM pg_class C
LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace)
WHERE 
  nspname NOT IN ('pg_catalog', 'information_schema') AND
  relkind='r' 
ORDER BY reltuples DESC;

Es difícil decir cuál de estas consultas es mejor usar. Normalmente tomo esa decisión en función de si hay más información útil que también quiero usar dentro de pg_class o dentro de pg_stat_user_tables. Para propósitos básicos de conteo solo para ver cuán grandes son las cosas en general, cualquiera de los dos debe ser lo suficientemente preciso.


2
Para completar, agregue esto para la primera opción (gracias a @a_horse_with_no_name):with tbl as (SELECT table_schema,table_name FROM information_schema.tables where table_name not like 'pg_%' and table_schema in ('public')) select table_schema, table_name, (xpath('/row/c/text()', query_to_xml(format('select count(*) as c from %I.%I', table_schema, table_name), false, true, '')))[1]::text::int as rows_n from tbl ORDER BY 3 DESC;
estani

1
@ Greg Smith ¿Qué versión introdujo n_live_tup? Mi base de datos Redshift carece de esa columna. Es un derivado de Postgres 8.0.2.
Iain Samuel McLean Élder

1
La consulta de 'segundo enfoque' (usando pg_stat_user_tables) me devolvió la mayoría de los ceros n_live_tupporque ANALYZEnunca se había ejecutado. En lugar de ejecutar ANALYZEen cada esquema / tabla y esperar una respuesta para siempre, primero verifiqué los resultados usando 'tercer enfoque' y ese (usando pg_class) devolvió conteos muy precisos.
Brian D el

@BrianD, es posible ejecutar el análisis a nivel de base de datos utilizando la utilidad analyb como "analyb -d dbname"
Eralper

69

Aquí hay una solución que no requiere funciones para obtener un recuento preciso para cada tabla:

select table_schema, 
       table_name, 
       (xpath('/row/cnt/text()', xml_count))[1]::text::int as row_count
from (
  select table_name, table_schema, 
         query_to_xml(format('select count(*) as cnt from %I.%I', table_schema, table_name), false, true, '') as xml_count
  from information_schema.tables
  where table_schema = 'public' --<< change here for the schema you want
) t

query_to_xmlejecutará la consulta SQL pasada y devolverá un XML con el resultado (el recuento de filas para esa tabla). El exterior xpath()extraerá la información de conteo de ese xml y la convertirá en un número.

La tabla derivada no es realmente necesaria, pero hace que sea xpath()un poco más fácil de entender; de lo contrario, todo se query_to_xml()tendría que pasar a la xpath()función.


3
Muy inteligente. Es una pena que no haya query_to_jsonb().
klin

@a_horse_with_no_name, ¿dará algún problema de rendimiento en tablas ocupadas y enormes durante la ejecución?
Spike

@Spike: ¿problemas de rendimiento en comparación con qué? El principal cuello de botella de rendimiento es ejecutar un select count(*)en cada mesa.
a_horse_with_no_name

@a_horse_with_no_name, ejecutando la función x_path contra 100 millones de registros.
Spike

@Spike: la xpath()función solo se aplica a una sola fila - el resultado decount(*)
a_horse_with_no_name

24

Para obtener estimaciones, vea la respuesta de Greg Smith .

Para obtener recuentos exactos, las otras respuestas hasta ahora están plagadas de algunos problemas, algunos de ellos serios (ver más abajo). Aquí hay una versión que es de esperar mejor:

CREATE FUNCTION rowcount_all(schema_name text default 'public')
  RETURNS table(table_name text, cnt bigint) as
$$
declare
 table_name text;
begin
  for table_name in SELECT c.relname FROM pg_class c
    JOIN pg_namespace s ON (c.relnamespace=s.oid)
    WHERE c.relkind = 'r' AND s.nspname=schema_name
  LOOP
    RETURN QUERY EXECUTE format('select cast(%L as text),count(*) from %I.%I',
       table_name, schema_name, table_name);
  END LOOP;
end
$$ language plpgsql;

Toma un nombre de esquema como parámetro, o publicsi no se proporciona ningún parámetro.

Para trabajar con una lista específica de esquemas o una lista que proviene de una consulta sin modificar la función, se puede invocar desde una consulta como esta:

WITH rc(schema_name,tbl) AS (
  select s.n,rowcount_all(s.n) from (values ('schema1'),('schema2')) as s(n)
)
SELECT schema_name,(tbl).* FROM rc;

Esto produce una salida de 3 columnas con el esquema, la tabla y el recuento de filas.

Ahora, aquí hay algunos problemas en las otras respuestas que esta función evita:

  • Los nombres de tablas y esquemas no deben inyectarse en SQL ejecutable sin ser citados, ya sea con quote_idento con la format()función más moderna con su %Icadena de formato. De lo contrario, una persona malintencionada puede nombrar su tabla, tablename;DROP TABLE other_tableque es perfectamente válida como nombre de tabla.

  • Incluso sin la inyección de SQL y los problemas de caracteres divertidos, el nombre de la tabla puede existir en variantes que difieren según el caso. Si se nombra una tabla ABCDy otra abcd, SELECT count(*) FROM...debe usar un nombre entre comillas; de lo contrario, se saltará ABCDy contará abcddos veces. El %Iformato de hace esto automáticamente.

  • information_schema.tablesenumera los tipos compuestos personalizados además de las tablas, incluso cuando table_type es 'BASE TABLE'(!). Como consecuencia, no podemos repetir information_schema.tables, de lo contrario corremos el riesgo de tener select count(*) from name_of_composite_typey eso fallaría. OTOH pg_class where relkind='r'siempre debería funcionar bien.

  • El tipo de COUNT () es bigint, no int. Pueden existir tablas con más de 2.15 mil millones de filas (sin embargo, ejecutar un conteo (*) es una mala idea).

  • No es necesario crear un tipo permanente para que una función devuelva un conjunto de resultados con varias columnas. RETURNS TABLE(definition...)Es una mejor alternativa.


18

Si no le importan los datos potencialmente obsoletos, puede acceder a las mismas estadísticas utilizadas por el optimizador de consultas .

Algo como:

SELECT relname, n_tup_ins - n_tup_del as rowcount FROM pg_stat_all_tables;

@mlissner: si su intervalo de vacío automático es demasiado largo o no ha ejecutado un manual ANALYZEsobre la mesa, las estadísticas pueden desviarse. Es una cuestión de carga de la base de datos y cómo se configura la base de datos (si las estadísticas se actualizan con más frecuencia, las estadísticas serán más precisas, pero podrían reducir el rendimiento del tiempo de ejecución). En definitiva, la única forma de obtener datos precisos es ejecutar select count(*) from tabletodas las tablas.
ig0774

17

La respuesta arrogante y práctica para las personas que intentan evaluar qué plan de Heroku necesitan y no pueden esperar a que se actualice el contador lento de heroku:

Básicamente desea ejecutar \dten psql, copiar los resultados a su editor de texto favorito (que se verá así:

 public | auth_group                     | table | axrsosvelhutvw
 public | auth_group_permissions         | table | axrsosvelhutvw
 public | auth_permission                | table | axrsosvelhutvw
 public | auth_user                      | table | axrsosvelhutvw
 public | auth_user_groups               | table | axrsosvelhutvw
 public | auth_user_user_permissions     | table | axrsosvelhutvw
 public | background_task                | table | axrsosvelhutvw
 public | django_admin_log               | table | axrsosvelhutvw
 public | django_content_type            | table | axrsosvelhutvw
 public | django_migrations              | table | axrsosvelhutvw
 public | django_session                 | table | axrsosvelhutvw
 public | exercises_assignment           | table | axrsosvelhutvw

), luego ejecute una búsqueda de expresiones regulares y reemplace así:

^[^|]*\|\s+([^|]*?)\s+\| table \|.*$

a:

select '\1', count(*) from \1 union/g

lo que te dará algo muy similar a esto:

select 'auth_group', count(*) from auth_group union
select 'auth_group_permissions', count(*) from auth_group_permissions union
select 'auth_permission', count(*) from auth_permission union
select 'auth_user', count(*) from auth_user union
select 'auth_user_groups', count(*) from auth_user_groups union
select 'auth_user_user_permissions', count(*) from auth_user_user_permissions union
select 'background_task', count(*) from background_task union
select 'django_admin_log', count(*) from django_admin_log union
select 'django_content_type', count(*) from django_content_type union
select 'django_migrations', count(*) from django_migrations union
select 'django_session', count(*) from django_session
;

(Deberá eliminar el último uniony agregar el punto y coma al final manualmente)

Ejecútalo psqly listo.

            ?column?            | count
--------------------------------+-------
 auth_group_permissions         |     0
 auth_user_user_permissions     |     0
 django_session                 |  1306
 django_content_type            |    17
 auth_user_groups               |   162
 django_admin_log               |  9106
 django_migrations              |    19
[..]

Me gusta esta idea
GuilPejon

En Atom, que tenía que regex búsqueda y reemplazo de esta manera: select '$1', count(*) from $1 union/g
mandril

Además, la publicación dice: "Deberá eliminar la unión y agregar el punto y coma al final". Este es un error tipográfico. Debe eliminar /g(conservar union) y agregar un punto y coma ( ;) al final. No olvides eliminar el último unionantes del punto y coma.
Chuck

1
"No olvides eliminar el último unionantes del punto y coma" es lo que quise decir :) Agregué la palabra "último" para aclararlo
Aur Saraf

10

No estoy seguro de si una respuesta en bash es aceptable para usted, pero FWIW ...

PGCOMMAND=" psql -h localhost -U fred -d mydb -At -c \"
            SELECT   table_name
            FROM     information_schema.tables
            WHERE    table_type='BASE TABLE'
            AND      table_schema='public'
            \""
TABLENAMES=$(export PGPASSWORD=test; eval "$PGCOMMAND")

for TABLENAME in $TABLENAMES; do
    PGCOMMAND=" psql -h localhost -U fred -d mydb -At -c \"
                SELECT   '$TABLENAME',
                         count(*) 
                FROM     $TABLENAME
                \""
    eval "$PGCOMMAND"
done

77
En esencia, ¡esto se reduce a lo mismo select count(*) from table_name;en el OP!
Noach Magedman

8

Por lo general, no confío en las estadísticas, especialmente en PostgreSQL.

SELECT table_name, dsql2('select count(*) from '||table_name) as rownum
FROM information_schema.tables
WHERE table_type='BASE TABLE'
    AND table_schema='livescreen'
ORDER BY 2 DESC;
CREATE OR REPLACE FUNCTION dsql2(i_text text)
  RETURNS int AS
$BODY$
Declare
  v_val int;
BEGIN
  execute i_text into v_val;
  return v_val;
END; 
$BODY$
  LANGUAGE plpgsql VOLATILE
  COST 100;

Esto es bueno, pero la primera consulta también debe incluir el esquema para el valor rownum. Si hay nombres en conflicto en diferentes esquemas, esto no funcionará como se esperaba. Entonces, esta parte de la consulta debería parecerse más dsql2('select count(*) from livescreen.'||table_name)o mejor, podría convertirse en una función propia.
jakub-olczyk

6

No recuerdo la URL de donde recolecté esto. Pero espero que esto te ayude:

CREATE TYPE table_count AS (table_name TEXT, num_rows INTEGER); 

CREATE OR REPLACE FUNCTION count_em_all () RETURNS SETOF table_count  AS '
DECLARE 
    the_count RECORD; 
    t_name RECORD; 
    r table_count%ROWTYPE; 

BEGIN
    FOR t_name IN 
        SELECT 
            c.relname
        FROM
            pg_catalog.pg_class c LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
        WHERE 
            c.relkind = ''r''
            AND n.nspname = ''public'' 
        ORDER BY 1 
        LOOP
            FOR the_count IN EXECUTE ''SELECT COUNT(*) AS "count" FROM '' || t_name.relname 
            LOOP 
            END LOOP; 

            r.table_name := t_name.relname; 
            r.num_rows := the_count.count; 
            RETURN NEXT r; 
        END LOOP; 
        RETURN; 
END;
' LANGUAGE plpgsql; 

La ejecución select count_em_all();debería obtener el recuento de filas de todas sus tablas.


1
Es una buena idea citar los nombres de columna (como quote_ident(t_name.relname)) para garantizar el soporte adecuado para nombres inusuales ("nombre de columna", por ejemplo).
gorsky

Para soltarlo después: DROP FUNCTION count_em_all ();
Aalex Gabi

Recibió un error: seleccione count_em_all (); ERROR: error de sintaxis en o cerca del "grupo" LÍNEA 1: SELECCIONE EL CONTEO () COMO "conteo" DESDE el grupo ^ CONSULTA: SELECCIONE EL CONTEO () COMO "conteo" DESDE EL CONTEXTO del grupo: PL / pgSQL función count_em_all () línea 18 en FOR over Declaración
EJECUTADA

¡Excelente! Para seleccionar y ordenar - SELECT * FROM count_em_all() as r ORDER BY r.num_rows DESC;
Ken4scholars

6

Dos pasos simples:
(Nota: no es necesario cambiar nada, solo copie y pegue)
1. Crear función

create function 
cnt_rows(schema text, tablename text) returns integer
as
$body$
declare
  result integer;
  query varchar;
begin
  query := 'SELECT count(1) FROM ' || schema || '.' || tablename;
  execute query into result;
  return result;
end;
$body$
language plpgsql;

2. Ejecute esta consulta para obtener el recuento de filas para todas las tablas

select sum(cnt_rows) as total_no_of_rows from (select 
  cnt_rows(table_schema, table_name)
from information_schema.tables
where 
  table_schema not in ('pg_catalog', 'information_schema') 
  and table_type='BASE TABLE') as subq;

o

Para obtener filas cuenta por tabla

select
  table_schema,
  table_name, 
  cnt_rows(table_schema, table_name)
from information_schema.tables
where 
  table_schema not in ('pg_catalog', 'information_schema') 
  and table_type='BASE TABLE'
order by 3 desc;

5

Hice una pequeña variación para incluir todas las tablas, también para tablas no públicas.

CREATE TYPE table_count AS (table_schema TEXT,table_name TEXT, num_rows INTEGER); 

CREATE OR REPLACE FUNCTION count_em_all () RETURNS SETOF table_count  AS '
DECLARE 
    the_count RECORD; 
    t_name RECORD; 
    r table_count%ROWTYPE; 

BEGIN
    FOR t_name IN 
        SELECT table_schema,table_name
        FROM information_schema.tables
        where table_schema !=''pg_catalog''
          and table_schema !=''information_schema''
        ORDER BY 1,2
        LOOP
            FOR the_count IN EXECUTE ''SELECT COUNT(*) AS "count" FROM '' || t_name.table_schema||''.''||t_name.table_name
            LOOP 
            END LOOP; 

            r.table_schema := t_name.table_schema;
            r.table_name := t_name.table_name; 
            r.num_rows := the_count.count; 
            RETURN NEXT r; 
        END LOOP; 
        RETURN; 
END;
' LANGUAGE plpgsql; 

solía select count_em_all();llamarlo.

Espero que encuentres esto útil. Pablo


ERROR: "r.table_schema" no es una variable conocida
slashdottir

2

Esto funciono para mi

SELECCIONE schemaname, relname, n_live_tup FROM pg_stat_user_tables ORDER BY n_live_tup DESC;


1

Me gusta la respuesta de Daniel Vérité . Pero cuando no puede usar una instrucción CREATE, puede usar una solución bash o, si es un usuario de Windows, una de PowerShell:

# You don't need this if you have pgpass.conf
$env:PGPASSWORD = "userpass"

# Get table list
$tables = & 'C:\Program Files\PostgreSQL\9.4\bin\psql.exe' -U user -w -d dbname -At -c "select table_name from information_schema.tables where table_type='BASE TABLE' AND table_schema='schema1'"

foreach ($table in $tables) {
    & 'C:\path_to_postresql\bin\psql.exe' -U root -w -d dbname -At -c "select '$table', count(*) from $table"
}

0

Quería el total de todas las tablas + una lista de tablas con sus recuentos. Un poco como un gráfico de rendimiento donde se pasó la mayor parte del tiempo

WITH results AS ( 
  SELECT nspname AS schemaname,relname,reltuples
    FROM pg_class C
    LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace)
    WHERE 
      nspname NOT IN ('pg_catalog', 'information_schema') AND
      relkind='r'
     GROUP BY schemaname, relname, reltuples
)

SELECT * FROM results
UNION
SELECT 'all' AS schemaname, 'all' AS relname, SUM(reltuples) AS "reltuples" FROM results

ORDER BY reltuples DESC

Por supuesto, también podría poner una LIMITcláusula sobre los resultados en esta versión para obtener los ndelincuentes más grandes , así como un total.

Una cosa que debe tenerse en cuenta al respecto es que debe dejarlo reposar durante un tiempo después de las importaciones a granel. Probé esto simplemente agregando 5000 filas a una base de datos en varias tablas usando datos de importación reales. Mostró 1800 registros durante aproximadamente un minuto (probablemente una ventana configurable)

Esto se basa en https://stackoverflow.com/a/2611745/1548557 trabajo, así que gracias y reconocimiento por la consulta que se utilizará dentro del CTE

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.