Como você encontra a contagem de linhas para todas as suas tabelas no Postgres

395

Estou procurando uma maneira de encontrar a contagem de linhas para todas as minhas tabelas no Postgres. Eu sei que posso fazer esta tabela de cada vez com:

SELECT count(*) FROM table_name;

mas eu gostaria de ver a contagem de linhas de todas as tabelas e, em seguida, pedir por isso para ter uma idéia do tamanho de todas as minhas tabelas.

mmrobins
fonte

Respostas:

582

Existem três maneiras de obter esse tipo de contagem, cada uma com suas próprias vantagens e desvantagens.

Se você deseja uma contagem verdadeira, é necessário executar a instrução SELECT como a que você usou em cada tabela. Isso ocorre porque o PostgreSQL mantém as informações de visibilidade da linha na própria linha, e não em nenhum outro lugar; portanto, qualquer contagem precisa pode ser relativa apenas a alguma transação. Você está obtendo uma contagem do que essa transação vê no momento em que é executada. Você pode automatizar isso para executar em todas as tabelas do banco de dados, mas provavelmente não precisa desse nível de precisão ou deseja esperar tanto tempo.

A segunda abordagem observa que o coletor de estatísticas rastreia aproximadamente quantas linhas estão "ativas" (não excluídas ou obsoletas por atualizações posteriores) a qualquer momento. Esse valor pode ser um pouco baixo em atividades pesadas, mas geralmente é uma boa estimativa:

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

Isso também pode mostrar quantas linhas estão mortas, o que é um número interessante para monitorar.

A terceira maneira é observar que o comando ANALYZE do sistema, que é executado pelo processo de autovacuum regularmente a partir do PostgreSQL 8.3 para atualizar as estatísticas da tabela, também calcula uma estimativa de linha. Você pode pegar esse assim:

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;

Qual dessas consultas é melhor usar é difícil dizer. Normalmente, tomo essa decisão com base em informações mais úteis que também quero usar dentro de pg_class ou dentro de pg_stat_user_tables. Para propósitos básicos de contagem, apenas para ver em que medida as coisas são grandes em geral, devem ser suficientemente precisas.

Greg Smith
fonte
2
Para fins de conclusão, adicione isso para a primeira opção (obrigado: @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
11
@ Greg Smith Qual versão introduzida n_live_tup? Meu banco de dados Redshift não possui essa coluna. É um derivado do Postgres 8.0.2.
Iain Samuel McLean Elder
11
A consulta da 'segunda abordagem' (usando pg_stat_user_tables) retornou principalmente zeros n_live_tuppara mim porque ANALYZEnunca havia sido executada. Em vez de executar ANALYZEem todos os esquemas / tabelas e esperar eternamente por uma resposta, verifiquei primeiro os resultados usando a 'terceira abordagem' e essa (usando pg_class) retornou contagens muito precisas.
Brian D
@BrianD, é possível executar a análise no nível do banco de dados usando o utilitário Analisado como "Analisadob -d
nome_do_DB
69

Aqui está uma solução que não requer funções para obter uma contagem precisa para cada tabela:

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_xmlexecutará a consulta SQL passada e retornará um XML com o resultado (a contagem de linhas para essa tabela). O externo xpath()extrairá as informações de contagem desse xml e as converterá em um número

A tabela derivada não é realmente necessária, mas facilita xpath()um pouco a compreensão - caso contrário, o todo query_to_xml()precisaria ser passado para a xpath()função.

um cavalo sem nome
fonte
3
Muito esperto. É uma pena que não há query_to_jsonb().
Klin
@a_horse_with_no_name, isso causará algum problema de desempenho em tabelas ocupadas e enormes durante a execução?
Spike
@ Spike: problemas de desempenho em comparação com o que? O principal gargalo de desempenho está ocorrendo select count(*)em todas as tabelas.
A_horse_with_no_name 8/04/19
@a_horse_with_no_name, executando a função x_path em 100 milhões de registros.
Spike
@Spike: a xpath()função só é aplicado a uma única fileira - o resultado de acount(*)
a_horse_with_no_name
24

Para obter estimativas, consulte a resposta de Greg Smith .

Para obter contagens exatas, as outras respostas até agora são atormentadas por alguns problemas, alguns deles sérios (veja abaixo). Aqui está uma versão que é esperançosamente melhor:

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;

Leva um nome de esquema como parâmetro ou publicse nenhum parâmetro for fornecido.

Para trabalhar com uma lista específica de esquemas ou uma lista proveniente de uma consulta sem modificar a função, ela pode ser chamada de dentro de uma 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;

Isso produz uma saída de 3 colunas com o esquema, a tabela e a contagem de linhas.

Agora, aqui estão alguns problemas nas outras respostas que essa função evita:

  • Os nomes de tabelas e esquemas não devem ser injetados no SQL executável sem serem citados, com quote_identou com a format()função mais moderna com sua %Isequência de formato. Caso contrário, uma pessoa mal-intencionada pode nomear sua tabela, tablename;DROP TABLE other_tableque é perfeitamente válida como um nome de tabela.

  • Mesmo sem os problemas de injeção de SQL e caracteres engraçados, o nome da tabela pode existir em variantes diferentes por caso. Se uma tabela for nomeada ABCDe outra abcd, ela SELECT count(*) FROM...deverá usar um nome entre aspas, caso contrário, ela será ignorada ABCDe contada abcdduas vezes. O %Iformato de faz isso automaticamente.

  • information_schema.tableslista tipos compostos personalizados, além de tabelas, mesmo quando table_type for 'BASE TABLE'(!). Como conseqüência, não podemos interagir information_schema.tables, caso contrário, corremos o risco de ter select count(*) from name_of_composite_typee isso falharia. OTOH pg_class where relkind='r'deve sempre funcionar bem.

  • O tipo de COUNT () é bigintnão int. Podem existir tabelas com mais de 2,15 bilhões de linhas (executar uma contagem (*) nelas é uma má idéia).

  • Um tipo permanente não precisa ser criado para que uma função retorne um conjunto de resultados com várias colunas. RETURNS TABLE(definition...)é uma alternativa melhor.

Daniel Vérité
fonte
18

Se você não se importar com dados potencialmente obsoletos, poderá acessar as mesmas estatísticas usadas pelo otimizador de consultas .

Algo como:

SELECT relname, n_tup_ins - n_tup_del as rowcount FROM pg_stat_all_tables;
ig0774
fonte
@mlissner: se o seu intervalo de autovacuum for muito longo ou você não tiver rodado um manual ANALYZEsobre a mesa, as estatísticas poderão sair muito ruins. É uma questão de carga do banco de dados e como o banco de dados é configurado (se as estatísticas forem atualizadas com mais frequência, as estatísticas serão mais precisas, mas poderão reduzir o desempenho do tempo de execução). Por fim, a única maneira de obter dados precisos é executar select count(*) from tableem todas as tabelas.
ig0774
17

A resposta prática e hacky para as pessoas que tentam avaliar qual plano Heroku elas precisam e mal podem esperar para o contador de linhas lentas da heroku se atualizar:

Basicamente você deseja executar \dtem psql, copiar os resultados para o seu editor de texto favorito (ele será parecido com este:

 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

), execute uma pesquisa regex e substitua assim:

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

para:

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

o que produzirá algo muito parecido com isso:

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
;

(Você precisará remover o último unione adicionar o ponto e vírgula no final manualmente)

Execute-o psqle pronto.

            ?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
fonte
Eu gosto desta idéia
GuilPejon
Em Atom, eu tive que regex de busca e substituir assim: select '$1', count(*) from $1 union/g
mandril
Além disso, a postagem diz: "Você precisará remover a união e adicionar o ponto e vírgula no final". Este é um erro de digitação. Você precisa remover /g(manter union) e adicionar um ponto-e-vírgula ( ;) no final. Não se esqueça de remover o último unionantes do ponto e vírgula.
mandril
11
"Não se esqueça de remover o último unionantes do ponto e vírgula" é o que eu quis dizer :) Adicionado a palavra "passado" para esclarecer
Aur Saraf
10

Não tenho certeza se uma resposta no bash é aceitável para você, mas 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
fonte
7
Na sua essência, isso se resume ao mesmo select count(*) from table_name;no OP!
Noach Magedman
8

Normalmente, não confio em estatísticas, especialmente no 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
fonte
Isso é bom, mas a primeira consulta também deve incluir o esquema para o valor do rownum. Se houver nomes conflitantes em esquemas diferentes, isso não funcionará conforme o esperado. Portanto, essa parte da consulta deve parecer mais dsql2('select count(*) from livescreen.'||table_name)ou melhor, podendo ser transformada em uma função própria.
Jkub-olczyk
6

Não lembro o URL de onde o coletei. Mas espero que isso ajude você:

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; 

A execução select count_em_all();deve levar a contagem de linhas de todas as suas tabelas.

Gnanam
fonte
11
É uma boa idéia citar nomes de colunas (como quote_ident(t_name.relname)) para garantir suporte adequado a nomes incomuns ("nome da coluna", por exemplo).
Gorsky
Para largá-lo depois: DROP FUNCTION count_em_all ();
Aalex Gabi 12/02
Ocorreu um erro: selecione count_em_all (); ERRO: erro de sintaxe no ou perto do "grupo" LINHA 1: SELECIONE A CONTAGEM () COMO "conte" do grupo ^ QUERY: SELECIONE A CONTAGEM () COMO "conte" do grupo CONTEXTO: função PL / pgSQL count_em_all () linha 18 em FOR over Declaração EXECUTE
Aalex Gabi 12/02
Ótimo! Para selecionar e classificar - # SELECT * FROM count_em_all() as r ORDER BY r.num_rows DESC;
Ken4scholars
6

Dois passos simples:
(Nota: não é necessário alterar nada - basta copiar e colar)
1. criar função

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. Execute esta consulta para obter a contagem de linhas para todas as tabelas

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

Para obter linhas, conta em tabela

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

Fiz uma pequena variação para incluir todas as tabelas, também para tabelas não 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; 

use select count_em_all();para chamá-lo.

Espero que você ache isso útil. Paulo

Paulo
fonte
ERRO: "r.table_schema" não é uma variável conhecida
slashdottir
2

Isso funcionou para mim

SELECT nome do esquema, relname, n_live_tup FROM pg_stat_user_tables ORDER BY n_live_tup DESC;

Pradeep Maurya
fonte
1

Eu gosto da resposta de Daniel Vérité . Mas quando você não pode usar uma instrução CREATE, pode usar uma solução bash ou, se você é um usuário do Windows, uma solução 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
fonte
0

Eu queria o total de todas as tabelas + uma lista de tabelas com suas contagens. Um pouco como um gráfico de desempenho de onde mais tempo foi gasto

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

Obviamente, você também pode colocar uma LIMITcláusula nos resultados desta versão para obter os maiores ninfratores e também o total.

Uma coisa que deve ser notada sobre isso é que você precisa deixá-lo descansar um pouco após a importação em massa. Testei isso adicionando 5000 linhas a um banco de dados em várias tabelas usando dados de importação reais. Mostrou 1800 registros por aproximadamente um minuto (provavelmente uma janela configurável)

Isso é baseado no https://stackoverflow.com/a/2611745/1548557 trabalho, portanto, obrigado e reconhecimento pela consulta a ser usada no CTE

MrMesees
fonte