Comment trouvez-vous le nombre de lignes pour toutes vos tables dans Postgres

395

Je cherche un moyen de trouver le nombre de lignes pour toutes mes tables dans Postgres. Je sais que je peux faire cette table à la fois avec:

SELECT count(*) FROM table_name;

mais je voudrais voir le nombre de lignes pour toutes les tables, puis ordonner par là pour avoir une idée de la taille de toutes mes tables.

mmrobins
la source

Réponses:

582

Il existe trois façons d'obtenir ce type de décompte, chacune avec ses propres compromis.

Si vous voulez un vrai nombre, vous devez exécuter l'instruction SELECT comme celle que vous avez utilisée pour chaque table. Cela est dû au fait que PostgreSQL conserve les informations de visibilité des lignes dans la ligne elle-même, pas ailleurs, donc tout décompte précis ne peut être relatif qu'à une transaction. Vous obtenez un décompte de ce que cette transaction voit au moment où elle s'exécute. Vous pouvez automatiser cela pour qu'il s'exécute sur chaque table de la base de données, mais vous n'avez probablement pas besoin de ce niveau de précision ou vous voulez attendre aussi longtemps.

La deuxième approche note que le collecteur de statistiques suit à peu près combien de lignes sont "en direct" (non supprimées ou obsolètes par les mises à jour ultérieures) à tout moment. Cette valeur peut être légèrement inférieure en cas d'activité intense, mais est généralement une bonne estimation:

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

Cela peut également vous montrer combien de lignes sont mortes, ce qui est en soi un nombre intéressant à surveiller.

La troisième méthode consiste à noter que la commande système ANALYZE, qui est exécutée régulièrement par le processus de vide automatique à partir de PostgreSQL 8.3 pour mettre à jour les statistiques de table, calcule également une estimation de ligne. Vous pouvez saisir celui-ci comme ceci:

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;

Laquelle de ces requêtes est préférable d'utiliser est difficile à dire. Normalement, je prends cette décision selon qu'il y a des informations plus utiles que je veux également utiliser à l'intérieur de pg_class ou à l'intérieur de pg_stat_user_tables. À des fins de comptage de base, juste pour voir à quel point les choses sont grandes en général, l'une ou l'autre devrait être suffisamment précise.

Greg Smith
la source
2
Pour des raisons de finitions, veuillez ajouter ceci pour la première option (merci à @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 Quelle version a été introduite n_live_tup? Ma base de données Redshift n'a pas cette colonne. C'est un dérivé de Postgres 8.0.2.
Iain Samuel McLean Elder
1
La requête «seconde approche» (en utilisant pg_stat_user_tables) a renvoyé principalement des zéros n_live_tuppour moi car elle ANALYZEn'avait jamais été exécutée. Plutôt que de courir ANALYZEsur chaque schéma / table et d'attendre indéfiniment une réponse, j'ai d'abord vérifié les résultats en utilisant la «troisième approche» et celle-ci (en utilisant pg_class) a retourné des comptes très précis.
Brian D
@BrianD, il est possible d'exécuter l'analyse au niveau de la base de données en utilisant l'utilitaire
analyzb
69

Voici une solution qui ne nécessite pas de fonctions pour obtenir un décompte précis pour chaque table:

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_xmlexécutera la requête SQL passée et renverra un XML avec le résultat (le nombre de lignes pour cette table). L'extérieurxpath() extraira ensuite les informations de comptage de ce xml et les convertira en un nombre

La table dérivée n'est pas vraiment nécessaire, mais la rend xpath()un peu plus facile à comprendre - sinon le tout query_to_xml()devrait être passé à la xpath()fonction.

un cheval sans nom
la source
3
Très intelligent. Dommage qu'il n'y en ait pas query_to_jsonb().
klin
@a_horse_with_no_name, cela donnera-t-il un problème de performances sur les tables occupées et énormes lors de l'exécution?
Spike
@Spike: des problèmes de performances par rapport à quoi? Le principal goulot d'étranglement des performances est l'exécution d'un select count(*)sur chaque table.
a_horse_with_no_name
@a_horse_with_no_name, en exécutant la fonction x_path sur 100 millions d'enregistrements.
Spike
@Spike: la xpath()fonction n'est appliquée qu'à une seule ligne - le résultat de lacount(*)
a_horse_with_no_name
24

Pour obtenir des estimations, voir la réponse de Greg Smith .

Pour obtenir des chiffres exacts, les autres réponses à ce jour sont en proie à certains problèmes, certains graves (voir ci-dessous). Voici une version qui, espérons-le, est meilleure:

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;

Il prend un nom de schéma comme paramètre, ou publicsi aucun paramètre n'est donné.

Pour travailler avec une liste spécifique de schémas ou une liste provenant d'une requête sans modifier la fonction, elle peut être appelée à partir d'une requête comme celle-ci:

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;

Cela produit une sortie à 3 colonnes avec le schéma, la table et le nombre de lignes.

Voici maintenant quelques problèmes dans les autres réponses que cette fonction évite:

  • Les noms de table et de schéma ne doivent pas être injectés dans SQL exécutable sans être cités, avec quote_identou avec la format()fonction plus moderne avec sa %Ichaîne de format. Sinon, une personne malveillante peut nommer sa table, tablename;DROP TABLE other_tablece qui est parfaitement valide en tant que nom de table.

  • Même sans les problèmes d'injection SQL et de caractères amusants, le nom de la table peut exister dans des variantes différentes selon la casse. Si une table est nommée ABCDet une autre abcd, le SELECT count(*) FROM...doit utiliser un nom entre guillemets sinon il sautera ABCDet comptera abcddeux fois. Le %Iformat de fait cela automatiquement.

  • information_schema.tablesrépertorie les types composites personnalisés en plus des tables, même lorsque table_type vaut 'BASE TABLE'(!). En conséquence, nous ne pouvons pas répéter information_schema.tables, sinon nous risquons d'avoir select count(*) from name_of_composite_typeet cela échouerait. OTOH pg_class where relkind='r'devrait toujours fonctionner correctement.

  • Le type de COUNT () ne l'est bigintpas int. Des tables avec plus de 2,15 milliards de lignes peuvent exister (cependant, exécuter un décompte (*) est une mauvaise idée).

  • Il n'est pas nécessaire de créer un type permanent pour qu'une fonction renvoie un jeu de résultats avec plusieurs colonnes. RETURNS TABLE(definition...)est une meilleure alternative.

Daniel Vérité
la source
18

Si cela ne vous dérange pas les données potentiellement périmées, vous pouvez accéder aux mêmes statistiques utilisées par l'optimiseur de requêtes .

Quelque chose comme:

SELECT relname, n_tup_ins - n_tup_del as rowcount FROM pg_stat_all_tables;
ig0774
la source
@mlissner: Si votre intervalle de vide automatique est trop long ou si vous n'avez pas exécuté de manuel ANALYZEsur la table, les statistiques peuvent être très éloignées. Il s'agit de la charge de la base de données et de la configuration de la base de données (si les statistiques sont mises à jour plus fréquemment, les statistiques seront plus précises, mais cela pourrait réduire les performances d'exécution). En fin de compte, la seule façon d'obtenir des données précises est de s'exécuter select count(*) from tablepour toutes les tables.
ig0774
17

La réponse hacky et pratique pour les gens qui essaient d'évaluer le plan Heroku dont ils ont besoin et ne peuvent pas attendre que le compteur de lignes lentes de Heroku se rafraîchisse:

Fondamentalement , vous voulez exécuter \dtdans psql, copier les résultats dans votre éditeur de texte favori (il ressemblera à ceci:

 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

), puis lancez une recherche d'expression régulière et remplacez comme ceci:

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

à:

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

ce qui vous donnera quelque chose de très similaire à ceci:

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
;

(Vous devrez supprimer le dernier unionet ajouter manuellement le point-virgule à la fin)

Exécutez-le psqlet vous avez terminé.

            ?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
[..]
Aur Saraf
la source
J'aime cette idée
GuilPejon
Dans Atom, j'ai dû regex rechercher et remplacer comme ceci: select '$1', count(*) from $1 union/g
chuck
En outre, le message indique: "Vous devrez supprimer l'union et ajouter le point-virgule à la fin." Ceci est une faute de frappe. Vous devez supprimer /g(conserver union) et ajouter un point-virgule ( ;) à la toute fin. N'oubliez pas de supprimer le dernier unionavant le point-virgule.
chuck
1
"N'oubliez pas de supprimer le dernier unionavant le point-virgule", c'est ce que je voulais dire :) Ajout du mot "dernier" pour clarifier
Aur Saraf
10

Je ne sais pas si une réponse en bash vous convient, mais 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
Stew-au
la source
7
Dans son essence, cela se résume à la même chose select count(*) from table_name;dans l'OP!
Noach Magedman
8

Je ne me fie généralement pas aux statistiques, en particulier dans 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;
Yuri Levinsky
la source
C'est bien, mais la première requête doit également inclure le schéma de la valeur rownum. S'il y a des noms en conflit dans différents schémas, cela ne fonctionnera pas comme prévu. Cette partie de la requête devrait donc ressembler davantage dsql2('select count(*) from livescreen.'||table_name)ou mieux à une fonction qui pourrait être transformée.
jakub-olczyk
6

Je ne me souviens pas de l'URL d'où j'ai collecté cela. Mais j'espère que cela devrait vous aider:

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; 

L'exécution select count_em_all();devrait vous faire compter le nombre de lignes de toutes vos tables.

Gnanam
la source
1
C'est une bonne idée de citer des noms de colonnes (comme quote_ident(t_name.relname)) pour assurer une prise en charge appropriée des noms inhabituels ("nom-colonne", par exemple).
gorsky
Pour le supprimer ensuite: DROP FUNCTION count_em_all ();
Aalex Gabi
Vous avez une erreur: sélectionnez count_em_all (); ERREUR: erreur de syntaxe au niveau ou à proximité du "groupe" LIGNE 1: SÉLECTIONNEZ COUNT () COMME "compte" DU groupe ^ QUERY: CHOISISSEZ COUNT () COMME "compte" DU groupe CONTEXTE: Fonction PL / pgSQL count_em_all () ligne 18 à FOR over
Déclaration
Génial! Pour sélectionner et trier - SELECT * FROM count_em_all() as r ORDER BY r.num_rows DESC;
Ken4scholars
6

Deux étapes simples:
(Remarque: pas besoin de changer quoi que ce soit - il suffit de copier-coller)
1. créer une fonction

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. Exécutez cette requête pour obtenir le nombre de lignes pour toutes les tables

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;

ou

Pour obtenir le nombre de lignes dans le tableau

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;
Raju Sah
la source
5

J'ai fait une petite variation pour inclure toutes les tables, également pour les tables non publiques.

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; 

utiliser select count_em_all();pour l'appeler.

J'espère que vous trouverez cela utile. Paul

Paul
la source
ERREUR: "r.table_schema" n'est pas une variable connue
slashdottir
2

Cela a fonctionné pour moi

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

Pradeep Maurya
la source
1

J'aime la réponse de Daniel Vérité . Mais lorsque vous ne pouvez pas utiliser une instruction CREATE, vous pouvez utiliser une solution bash ou, si vous êtes un utilisateur Windows, une 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"
}
CFreitas
la source
0

Je voulais le total de toutes les tables + une liste des tables avec leurs nombres. Un peu comme un tableau des performances de l'endroit où le plus de temps a été passé

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

Vous pouvez bien sûr aussi mettre une LIMITclause sur les résultats dans cette version pour obtenir le plus grandn délinquants ainsi qu'un total.

Une chose à noter à ce sujet est que vous devez le laisser reposer pendant un certain temps après les importations en vrac. J'ai testé cela en ajoutant simplement 5000 lignes à une base de données sur plusieurs tables à l'aide de données d'importation réelles. Il a montré 1800 enregistrements pendant environ une minute (probablement une fenêtre configurable)

Ceci est basé sur https://stackoverflow.com/a/2611745/1548557 travail, donc merci et reconnaissance à cela pour la requête à utiliser au sein du CTE

MrMesees
la source