Wie finden Sie die Zeilenanzahl für alle Ihre Tabellen in Postgres?

395

Ich suche nach einer Möglichkeit, die Zeilenanzahl für alle meine Tabellen in Postgres zu ermitteln. Ich weiß, dass ich diesen einen Tisch nach dem anderen machen kann mit:

SELECT count(*) FROM table_name;

Aber ich möchte die Zeilenanzahl für alle Tabellen sehen und dann danach bestellen, um eine Vorstellung davon zu bekommen, wie groß alle meine Tabellen sind.

mmrobins
quelle

Antworten:

582

Es gibt drei Möglichkeiten, um diese Art der Zählung zu erhalten, jede mit ihren eigenen Kompromissen.

Wenn Sie eine echte Zählung wünschen, müssen Sie die SELECT-Anweisung wie die für jede Tabelle verwendete ausführen. Dies liegt daran, dass PostgreSQL die Informationen zur Zeilensichtbarkeit in der Zeile selbst und nicht irgendwo anders speichert, sodass eine genaue Zählung nur relativ zu einer Transaktion erfolgen kann. Sie erhalten eine Zählung dessen, was diese Transaktion zum Zeitpunkt ihrer Ausführung sieht. Sie könnten dies so automatisieren, dass es für jede Tabelle in der Datenbank ausgeführt wird, aber Sie benötigen wahrscheinlich nicht diese Genauigkeit oder möchten so lange warten.

Der zweite Ansatz stellt fest, dass der Statistiksammler ungefähr nachverfolgt, wie viele Zeilen zu einem beliebigen Zeitpunkt "live" sind (nicht gelöscht oder durch spätere Aktualisierungen veraltet). Dieser Wert kann bei starker Aktivität etwas abweichen, ist aber im Allgemeinen eine gute Schätzung:

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

Das kann Ihnen auch zeigen, wie viele Zeilen tot sind, was selbst eine interessante Zahl ist, die überwacht werden muss.

Die dritte Möglichkeit besteht darin, zu beachten, dass der Systembefehl ANALYZE, der ab PostgreSQL 8.3 regelmäßig vom Autovakuumprozess ausgeführt wird, um die Tabellenstatistik zu aktualisieren, auch eine Zeilenschätzung berechnet. Sie können das so greifen:

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;

Welche dieser Abfragen besser zu verwenden ist, ist schwer zu sagen. Normalerweise treffe ich diese Entscheidung basierend darauf, ob es weitere nützliche Informationen gibt, die ich auch innerhalb von pg_class oder innerhalb von pg_stat_user_tables verwenden möchte. Für grundlegende Zählzwecke sollte nur genau genug sein, um zu sehen, wie groß die Dinge im Allgemeinen sind.

Greg Smith
quelle
2
Für Vervollständigungen fügen Sie dies bitte für die erste Option hinzu (danke geht an @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 Welche Version eingeführt n_live_tup? In meiner Redshift-Datenbank fehlt diese Spalte. Es ist eine Ableitung von Postgres 8.0.2.
Iain Samuel McLean Elder
1
Die 'Second Approach'-Abfrage (using pg_stat_user_tables) hat n_live_tupfür mich meistens Nullen zurückgegeben , da sie ANALYZEnoch nie ausgeführt wurde. Anstatt ANALYZEjedes Schema / jede Tabelle auszuführen und ewig auf eine Antwort zu warten, überprüfte ich zuerst die Ergebnisse mit dem 'dritten Ansatz' und dieser (mit pg_class) lieferte sehr genaue Zählungen.
Brian D
@BrianD, es ist möglich, die Analyse auf Datenbankebene mit dem Dienstprogramm analyseb als "analyseb -d dbname"
auszuführen
69

Hier ist eine Lösung, für die keine Funktionen erforderlich sind, um eine genaue Zählung für jede Tabelle zu erhalten:

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_xmlführt die übergebene SQL-Abfrage aus und gibt ein XML mit dem Ergebnis zurück (die Zeilenanzahl für diese Tabelle). Das Äußerexpath() extrahiert dann die Zählinformationen aus dieser XML und konvertiert sie in eine Zahl

Die abgeleitete Tabelle ist nicht wirklich notwendig, macht das aber xpath()etwas verständlicher - sonst query_to_xml()müsste das Ganze an die xpath()Funktion übergeben werden.

ein Pferd ohne Name
quelle
3
Sehr schlau. Schade, dass es keine gibt query_to_jsonb().
klin
@a_horse_with_no_name, gibt es während der Ausführung Leistungsprobleme bei ausgelasteten und großen Tabellen?
Spike
@Spike: Leistungsprobleme im Vergleich zu was? Der größte Leistungsengpass besteht darin, dass select count(*)auf jedem Tisch ein Fehler auftritt.
a_horse_with_no_name
@a_horse_with_no_name, indem die Funktion x_path für 100 Millionen Datensätze ausgeführt wird.
Spike
@Spike: Die xpath()Funktion wird nur auf eine einzelne Zeile angewendet - das Ergebnis descount(*)
a_horse_with_no_name
24

Schätzungen finden Sie in Greg Smiths Antwort .

Um genaue Zahlen zu erhalten, sind die anderen Antworten bisher mit einigen Problemen behaftet, von denen einige schwerwiegend sind (siehe unten). Hier ist eine Version, die hoffentlich besser ist:

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;

Es wird ein Schemaname als Parameter verwendet oder publicwenn kein Parameter angegeben wird.

Um mit einer bestimmten Liste von Schemas oder einer Liste zu arbeiten, die aus einer Abfrage stammt, ohne die Funktion zu ändern, kann sie innerhalb einer Abfrage wie folgt aufgerufen werden:

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;

Dies erzeugt eine 3-Spalten-Ausgabe mit dem Schema, der Tabelle und der Zeilenanzahl.

Hier sind einige Probleme in den anderen Antworten, die diese Funktion vermeidet:

  • Tabellen- und Schemanamen sollten nicht ohne Anführungszeichen in ausführbares SQL eingefügt werden, weder mit quote_identnoch mit der moderneren format()Funktion mit ihrer %IFormatzeichenfolge. Andernfalls kann eine böswillige Person ihre Tabelle benennen, die als Tabellenname tablename;DROP TABLE other_tablevollkommen gültig ist.

  • Auch ohne Probleme mit der SQL-Injection und den lustigen Zeichen kann der Tabellenname in Varianten vorhanden sein, die sich von Fall zu Fall unterscheiden. Wenn eine Tabelle benannt ist ABCDund eine andere abcd, SELECT count(*) FROM...muss der Name in Anführungszeichen gesetzt werden, da er sonst zweimal übersprungen ABCDund gezählt abcdwird. Das %Iof-Format erledigt dies automatisch.

  • information_schema.tableslistet zusätzlich zu Tabellen benutzerdefinierte zusammengesetzte Typen auf, auch wenn table_type 'BASE TABLE'(!) ist. Infolgedessen können wir nicht weiter iterieren information_schema.tables, sonst riskieren wir es select count(*) from name_of_composite_typeund das würde fehlschlagen. OTOH pg_class where relkind='r'sollte immer gut funktionieren.

  • Der Typ von COUNT () ist bigintnicht int. Möglicherweise sind Tabellen mit mehr als 2,15 Milliarden Zeilen vorhanden (eine Zählung (*) ist jedoch eine schlechte Idee).

  • Es muss kein permanenter Typ erstellt werden, damit eine Funktion eine Ergebnismenge mit mehreren Spalten zurückgibt. RETURNS TABLE(definition...)ist eine bessere Alternative.

Daniel Vérité
quelle
18

Wenn Ihnen möglicherweise veraltete Daten nichts ausmachen, können Sie auf dieselben Statistiken zugreifen, die vom Abfrageoptimierer verwendet werden .

Etwas wie:

SELECT relname, n_tup_ins - n_tup_del as rowcount FROM pg_stat_all_tables;
ig0774
quelle
@mlissner: Wenn Ihr Autovakuumintervall zu lang ist oder Sie kein Handbuch ANALYZEfür die Tabelle ausgeführt haben, können die Statistiken weit davon entfernt sein. Es ist eine Frage der Datenbanklast und der Konfiguration der Datenbank (wenn die Statistiken häufiger aktualisiert werden, sind die Statistiken genauer, können jedoch die Laufzeitleistung beeinträchtigen). Letztendlich besteht die einzige Möglichkeit, genaue Daten zu erhalten, darin, select count(*) from tablefür alle Tabellen zu arbeiten.
ig0774
17

Die hackige, praktische Antwort für Leute, die versuchen zu bewerten, welchen Heroku-Plan sie benötigen und nicht darauf warten können, dass Herokus langsamer Zeilenzähler aktualisiert wird:

Im Grunde wollen Sie laufen \dtin psql, kopieren Sie die Ergebnisse zu Ihrem bevorzugten Texteditor (es wird wie folgt aussehen:

 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

), führen Sie dann eine Regex-Suche durch und ersetzen Sie diese wie folgt:

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

zu:

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

was Ihnen etwas sehr Ähnliches ergeben wird:

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
;

(Sie müssen das letzte entfernen unionund das Semikolon am Ende manuell hinzufügen.)

Führen Sie es ein psqlund Sie sind fertig.

            ?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
quelle
Ich mag diese Idee
GuilPejon
In Atom musste ich wie folgt neu suchen und ersetzen: select '$1', count(*) from $1 union/g
Chuck
Außerdem heißt es in dem Beitrag: "Sie müssen die Vereinigung entfernen und am Ende das Semikolon hinzufügen." Dies ist ein Tippfehler. Sie müssen ganz am Ende ein Semikolon ( ) entfernen /g(behalten union) und hinzufügen ;. Vergessen Sie nicht, das letzte unionvor dem Semikolon zu entfernen .
Chuck
1
"Vergiss nicht, das letzte unionvor dem Semikolon zu entfernen ", meinte ich :) Das Wort "last" wurde hinzugefügt, um dies zu verdeutlichen
Aur Saraf
10

Ich bin mir nicht sicher, ob eine Antwort in Bash für Sie akzeptabel ist, aber 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
quelle
7
Im Wesentlichen läuft dies select count(*) from table_name;im OP auf dasselbe hinaus !
Noach Magedman
8

Ich verlasse mich normalerweise nicht auf Statistiken, besonders 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;
Yuri Levinsky
quelle
Dies ist nett, aber die erste Abfrage sollte auch das Schema für den Rownum-Wert enthalten. Wenn in verschiedenen Schemas widersprüchliche Namen vorhanden sind, funktioniert dies nicht wie erwartet. Dieser Teil der Abfrage sollte also eher so aussehen dsql2('select count(*) from livescreen.'||table_name)oder besser, er könnte in eine eigene Funktion umgewandelt werden.
Jakub-Olczyk
6

Ich erinnere mich nicht an die URL, unter der ich diese gesammelt habe. Aber hoffe das sollte dir helfen:

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; 

Durch Ausführen select count_em_all();sollten Sie die Zeilenanzahl aller Ihrer Tabellen erhalten.

Gnanam
quelle
1
Es ist eine gute Idee, Spaltennamen (wie quote_ident(t_name.relname)) anzugeben, um die ordnungsgemäße Unterstützung ungewöhnlicher Namen (z. B. "Spaltenname") sicherzustellen.
Gorsky
So löschen Sie es anschließend: DROP FUNCTION count_em_all ();
Aalex Gabi
Ich habe einen Fehler erhalten: select count_em_all (); FEHLER: Syntaxfehler bei oder in der Nähe von "Gruppe" Zeile 1: SELECT COUNT () AS "count" FROM group ^ QUERY: SELECT COUNT () AS "count" FROM group CONTEXT: PL / pgSQL-Funktion count_em_all () Zeile 18 bei FOR over EXECUTE-Anweisung
Aalex Gabi
Großartig! Auswählen und sortieren - SELECT * FROM count_em_all() as r ORDER BY r.num_rows DESC;
Ken4scholars
6

Einfache zwei Schritte:
(Hinweis: Sie müssen nichts ändern - einfach kopieren, einfügen)
1. Funktion erstellen

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. Führen Sie diese Abfrage aus, um die Zeilenanzahl für alle Tabellen abzurufen

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;

oder

Um Zeilenzählungen tabellarisch abzurufen

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
quelle
5

Ich habe eine kleine Variation vorgenommen, um alle Tabellen einzuschließen, auch für nicht öffentliche Tabellen.

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; 

verwenden select count_em_all();, um es zu nennen.

Ich hoffe, Sie finden das nützlich. Paul

Paul
quelle
FEHLER: "r.table_schema" ist keine bekannte Variable
slashdottir
2

Das hat bei mir funktioniert

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

Pradeep Maurya
quelle
1

Ich mag die Antwort von Daniel Vérité . Wenn Sie jedoch keine CREATE-Anweisung verwenden können, können Sie entweder eine Bash-Lösung oder, wenn Sie ein Windows-Benutzer sind, eine Powershell-Lösung verwenden:

# 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
quelle
0

Ich wollte die Summe aller Tabellen + eine Liste der Tabellen mit ihrer Anzahl. Ein bisschen wie ein Leistungsdiagramm, in dem die meiste Zeit verbracht wurde

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

Sie können natürlich auch LIMITin dieser Version eine Klausel zu den Ergebnissen setzen, damit Sie die größte erhaltenn Straftäter sowie insgesamt erhalten.

Eine Sache, die dabei beachtet werden sollte, ist, dass Sie es nach Massenimporten eine Weile stehen lassen müssen. Ich habe dies getestet, indem ich einer Datenbank über mehrere Tabellen hinweg 5000 Zeilen mit echten Importdaten hinzugefügt habe. Es zeigte 1800 Datensätze für ungefähr eine Minute (wahrscheinlich ein konfigurierbares Fenster)

Dies basiert auf der Arbeit von https://stackoverflow.com/a/2611745/1548557. Vielen Dank und Anerkennung für die Abfrage, die innerhalb des CTE verwendet werden soll

MrMesees
quelle