Come trovi il conteggio delle righe per tutte le tue tabelle in Postgres


395

Sto cercando un modo per trovare il conteggio delle righe per tutte le mie tabelle in Postgres. So di poterlo fare una tabella alla volta con:

SELECT count(*) FROM table_name;

ma mi piacerebbe vedere il conteggio delle righe per tutti i tavoli e quindi ordinare in base a quello per avere un'idea di quanto siano grandi tutti i miei tavoli.

Risposte:


582

Esistono tre modi per ottenere questo tipo di conteggio, ognuno con i propri compromessi.

Se si desidera un conteggio vero, è necessario eseguire l'istruzione SELECT come quella utilizzata su ciascuna tabella. Questo perché PostgreSQL mantiene le informazioni sulla visibilità della riga nella riga stessa, non altrove, quindi qualsiasi conteggio accurato può essere relativo solo ad alcune transazioni. Stai ottenendo un conteggio di ciò che quella transazione vede nel momento in cui viene eseguita. Potresti automatizzare questo per l'esecuzione su ogni tabella nel database, ma probabilmente non hai bisogno di quel livello di precisione o vuoi aspettare così a lungo.

Il secondo approccio rileva che il raccoglitore di statistiche tiene traccia di circa quante righe sono "attive" (non eliminate o obsolete da aggiornamenti successivi) in qualsiasi momento. Questo valore può essere disattivato un po 'sotto un'attività pesante, ma è generalmente una buona stima:

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

Ciò può anche mostrarti quante righe sono morte, il che è di per sé un numero interessante da monitorare.

Il terzo modo è notare che il comando ANALYZE di sistema, che viene eseguito regolarmente dal processo autovacuum a partire da PostgreSQL 8.3 per aggiornare le statistiche delle tabelle, calcola anche una stima di riga. Puoi prendere quello in questo modo:

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;

Quale di queste domande è meglio usare è difficile da dire. Normalmente prendo questa decisione in base alla presenza di ulteriori informazioni utili che voglio usare anche all'interno di pg_class o all'interno di pg_stat_user_tables. Ai fini del conteggio di base solo per vedere quanto sono grandi le cose in generale, entrambi dovrebbero essere abbastanza precisi.


2
Per amor di completamenti, per favore aggiungi questo per la prima opzione (grazie 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 Quale versione è stata introdotta n_live_tup? Il mio database Redshift non ha quella colonna. È un derivato di Postgres 8.0.2.
Iain Samuel McLean Elder il

1
La query "secondo approccio" (utilizzo pg_stat_user_tables) ha restituito per lo più zeri n_live_tupperché ANALYZEnon era mai stata eseguita. Anziché eseguire ANALYZEsu ogni schema / tabella e attendere per sempre una risposta, ho prima verificato i risultati usando "terzo approccio" e quello (usando pg_class) ha restituito conteggi molto accurati.
Brian D,

@BrianD, è possibile eseguire l'analisi a livello di database utilizzando l'utilità analizzata come "analizzatob -d nomedb"
Eralper

69

Ecco una soluzione che non richiede funzioni per ottenere un conteggio accurato per ogni tabella:

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_xmleseguirà la query SQL passata e restituirà un XML con il risultato (il conteggio delle righe per quella tabella). L'esterno xpath()estrarrà quindi le informazioni sul conteggio da quel file XML e le convertirà in un numero

La tabella derivata non è davvero necessaria, ma rende xpath()un po 'più semplice la comprensione, altrimenti l'intero query_to_xml()dovrebbe essere passato alla xpath()funzione.


3
Molto intelligente. È un peccato che non ci sia query_to_jsonb().
klin,

@a_horse_with_no_name, fornirà problemi di prestazioni su tavoli affollati ed enormi durante l'esecuzione?
Spike

@Spike: problemi di prestazioni rispetto a cosa? Il collo di bottiglia delle prestazioni principali è in esecuzione select count(*)su ogni tavolo.
a_horse_with_no_name

@a_horse_with_no_name, eseguendo la funzione x_path su 100 milioni di record.
Spike

@Spike: la xpath()funzione viene applicata solo a una singola riga - il risultato dicount(*)
a_horse_with_no_name

24

Per ottenere stime, vedere la risposta di Greg Smith .

Per ottenere conteggi esatti, le altre risposte finora sono afflitte da alcuni problemi, alcuni dei quali seri (vedi sotto). Ecco una versione che si spera sia migliore:

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;

Prende un nome di schema come parametro o publicse non viene specificato alcun parametro.

Per lavorare con un elenco specifico di schemi o un elenco proveniente da una query senza modificare la funzione, può essere chiamato da una query come questa:

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;

Questo produce un output a 3 colonne con lo schema, la tabella e il conteggio delle righe.

Ora ecco alcuni problemi nelle altre risposte che questa funzione evita:

  • I nomi di tabelle e schemi non devono essere iniettati in SQL eseguibile senza essere quotati, con quote_idento con la format()funzione più moderna con la sua %Istringa di formato. In caso contrario, una persona malintenzionata potrebbe nominare la propria tabella tablename;DROP TABLE other_tableche è perfettamente valida come nome di tabella.

  • Anche senza l'iniezione SQL e problemi di caratteri divertenti, il nome della tabella può esistere in varianti che differiscono per caso. Se viene denominata una tabella ABCDe un'altra abcd, è SELECT count(*) FROM...necessario utilizzare un nome tra virgolette, altrimenti salterà ABCDe conterà abcddue volte. Il %Iformato di lo fa automaticamente.

  • information_schema.tableselenca i tipi compositi personalizzati oltre alle tabelle, anche quando table_type è 'BASE TABLE'(!). Di conseguenza, non possiamo iterare information_schema.tables, altrimenti rischiamo di avere select count(*) from name_of_composite_typee ciò fallirebbe. OTOH pg_class where relkind='r'dovrebbe sempre funzionare bene.

  • Il tipo di COUNT () è bigint, non int. Possono esistere tabelle con oltre 2,15 miliardi di righe (eseguire un conteggio (*) su di esse è una cattiva idea).

  • Non è necessario creare un tipo permanente per una funzione per restituire un gruppo di risultati con più colonne. RETURNS TABLE(definition...)è un'alternativa migliore.


18

Se non ti interessano i dati potenzialmente obsoleti, puoi accedere alle stesse statistiche utilizzate da Query Optimizer .

Qualcosa di simile a:

SELECT relname, n_tup_ins - n_tup_del as rowcount FROM pg_stat_all_tables;

@mlissner: se il tuo intervallo di vuoto automatico è troppo lungo o non hai eseguito un manuale ANALYZEsul tavolo, le statistiche possono scendere. È una questione di caricamento del database e di come è configurato il database (se le statistiche vengono aggiornate più frequentemente, le statistiche saranno più accurate, ma potrebbe ridurre le prestazioni di runtime). In definitiva, l'unico modo per ottenere dati precisi è eseguire select count(*) from tableper tutte le tabelle.
ig0774,

17

La risposta caotica e pratica per le persone che cercano di valutare il piano Heroku di cui hanno bisogno e che non vedono l'ora che il contatore lento di heroku si aggiorni:

In sostanza si desidera eseguire \dtin psql, copiare i risultati per il vostro editor di testo preferito (che sarà simile a questa:

 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

), quindi esegui una ricerca regex e sostituisci in questo modo:

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

per:

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

che ti darà qualcosa di molto simile a questo:

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
;

(Dovrai rimuovere l'ultimo unione aggiungere il punto e virgola alla fine manualmente)

Eseguilo psqle il gioco è fatto.

            ?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
[..]

Mi piace questa idea
GuilPejon,

In Atom, ho dovuto regex cercare e sostituire in questo modo: select '$1', count(*) from $1 union/g
chuck

Inoltre, il post dice: "Dovrai rimuovere l'unione e aggiungere il punto e virgola alla fine." Questo è un refuso. Devi rimuovere /g(mantenere union) e aggiungere un punto e virgola ( ;) alla fine. Non dimenticare di rimuovere l'ultimo unionprima del punto e virgola.
Chuck,

1
"Non dimenticare di rimuovere l'ultimo unionprima del punto e virgola" è quello che intendevo :) Aggiunta la parola "ultimo" per chiarire
Aur Saraf,

10

Non sono sicuro che una risposta in bash sia accettabile per te, ma 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

7
Alla sua essenza, questo si riduce allo stesso modo select count(*) from table_name;nel PO!
Noach Magedman,

8

Di solito non faccio affidamento sulle statistiche, specialmente in 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;

Questo è carino ma la prima query dovrebbe includere anche lo schema per il valore di rownum. Se sono presenti nomi in conflitto in schemi diversi, ciò non funzionerà come previsto. Quindi questa parte della query dovrebbe assomigliare di piùdsql2('select count(*) from livescreen.'||table_name) o meglio, potrebbe essere trasformata in una sua funzione.
jakub-olczyk,

6

Non ricordo l'URL da dove l'ho raccolto. Ma spero che questo ti possa aiutare:

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'esecuzione select count_em_all();dovrebbe farti contare le righe di tutti i tuoi tavoli.


1
È una buona idea citare nomi di colonne (come quote_ident(t_name.relname)) per garantire il supporto adeguato per nomi insoliti ("nome-colonna", per esempio).
gorsky,

Per rilasciarlo in seguito: DROP FUNCTION count_em_all ();
Aalex Gabi,

Hai ricevuto un errore: seleziona count_em_all (); ERRORE: errore di sintassi vicino o al "gruppo" LINEA 1: SELEZIONA COUNT () COME "conta" DAL gruppo ^ QUERY: SELEZIONA COUNT () COME "conta" DAL gruppo CONTESTO: Funzione PL / pgSQL count_em_all () riga 18 su FOR over Dichiarazione EXECUTE
Aalex Gabi,

Grande! Per selezionare e ordinare - SELECT * FROM count_em_all() as r ORDER BY r.num_rows DESC;
Ken4scholars

6

Due semplici passaggi:
(Nota: non è necessario modificare nulla: basta copiare e incollare)
1. creare una funzione

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. Eseguire questa query per ottenere il conteggio delle righe per tutte le tabelle

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;

oppure

Per ottenere i conteggi delle righe a livello di tabella

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

Ho fatto una piccola variazione per includere tutti i tavoli, anche per i tavoli non pubblici.

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; 

usare select count_em_all();per chiamarlo.

Spero che lo trovi utile. Paolo


ERRORE: "r.table_schema" non è una variabile nota
slashdottir

2

Questo ha funzionato per me

SELECT schemaname, relname, n_live_tup FROM pg_stat_user_tables ORDINA PER n_live_tup DESC;


1

Mi piace la risposta di Daniel Vérité . Ma quando non puoi usare un'istruzione CREATE puoi usare una soluzione bash o, se sei un utente Windows, una 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

Volevo il totale da tutte le tabelle + un elenco di tabelle con i loro conteggi. Un po 'come un grafico delle prestazioni in cui è stata spesa la maggior parte del tempo

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

Naturalmente LIMITin questa versione potresti mettere anche una clausola sui risultati in modo da ottenere il maggior numero di nautori di reato e un totale.

Una cosa che dovrebbe essere notato su questo è che è necessario lasciarlo riposare per un po 'dopo le importazioni all'ingrosso. Ho provato questo aggiungendo solo 5000 righe a un database su più tabelle usando dati di importazione reali. Ha mostrato 1800 record per circa un minuto (probabilmente una finestra configurabile)

Questo si basa sul lavoro https://stackoverflow.com/a/2611745/1548557 , quindi grazie e riconoscimento a quello per la query da utilizzare all'interno del CTE

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.