Як знайти кількість рядків для всіх своїх таблиць у Postgres


395

Я шукаю спосіб знайти кількість рядків для всіх моїх таблиць у Postgres. Я знаю, що можу робити це по одній таблиці одночасно з:

SELECT count(*) FROM table_name;

але я хотів би побачити кількість рядків для всіх таблиць, а потім упорядкувати їх, щоб отримати уявлення про те, наскільки великі всі мої таблиці.

Відповіді:


582

Існує три способи отримати подібний підрахунок, кожен з яких має свої компроміси.

Якщо ви хочете справжнього рахунку, вам слід виконати оператор SELECT, як той, який ви використовували проти кожної таблиці. Це пояснюється тим, що PostgreSQL зберігає інформацію про видимість рядків у самій рядку, а не в іншому місці, тому будь-яка точна підрахунок може бути відносно певної транзакції. Ви отримуєте підрахунок того, що бачить ця транзакція в момент часу її виконання. Ви можете автоматизувати це для роботи з кожною таблицею в базі даних, але вам, мабуть, не потрібен такий рівень точності або ви хочете чекати так довго.

Другий підхід зазначає, що збирач статистики орієнтовно відстежує, скільки рядків є "в прямому ефірі" (не видаляються та не застаріваються пізнішими оновленнями) в будь-який час. Цю величину можна дещо вимкнути при великій активності, але, як правило, хороша оцінка

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

Це також може показати вам, скільки рядків загинуло, що саме по собі є цікавим числом для моніторингу.

Третій спосіб полягає в тому, що системна команда ANALYZE, яка автоматично виконується автовакуумним процесом, як у PostgreSQL 8.3 для оновлення статистики таблиці, також обчислює оцінку рядків. Ви можете схопити цю схожу:

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;

Який із цих запитів краще використовувати, важко сказати. Зазвичай я приймаю це рішення, грунтуючись на тому, чи є корисніша інформація, яку я також хочу використовувати всередині pg_class або всередині pg_stat_user_tables. Для основних цілей підрахунку просто, щоб побачити, наскільки великі речі взагалі, або повинні бути досить точними.


2
Для завершення, будь ласка, додайте це для першого варіанту (спасибі йде на @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 Яка версія представлена n_live_tup? У моїй базі даних Redshift немає цього стовпця. Це похідне від Postgres 8.0.2.
Ієн Самуель Маклін Старший

1
Запит "другого підходу" (використовуючи pg_stat_user_tables) повертав n_live_tupдля мене переважно нулі , оскільки ANALYZEйого ніколи не виконували. Замість того, щоб працювати ANALYZEна кожній схемі / таблиці і чекати вічно відповіді, я спочатку перевірив результати, використовуючи «третій підхід», і той (використовуючи pg_class) повернув дуже точні підрахунки.
Брайан Д

@BrianD, можна виконати аналіз на рівні бази даних, використовуючи утиліту
analyb

69

Ось рішення, яке не вимагає функцій, щоб отримати точний підрахунок для кожної таблиці:

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_xmlзапустить пройдений SQL-запит і поверне XML з результатом (кількість рядків для цієї таблиці). Потім зовнішня xpath()витягує інформацію про підрахунок із цього xml та перетворює її в число

Виведена таблиця насправді не потрібна, але xpath()трохи спрощує її розуміння - інакше ціле query_to_xml()потрібно передати у xpath()функцію.


3
Дуже розумний. Шкода, що немає query_to_jsonb().
клин

@a_horse_with_no_name, чи дасть це питання про продуктивність на зайнятих і величезних таблицях під час виконання?
Спайк

@Spike: проблеми з продуктивністю порівняно з чим? Основне вузьке місце ефективності працює select count(*)на кожному столі.
a_horse_with_no_name

@a_horse_with_no_name, виконавши функцію x_path проти 100 мільйонів записів.
Спайк

@Spike: xpath()функція застосована лише до одного рядка - результатcount(*)
a_horse_with_no_name

24

Щоб отримати оцінки, див відповідь Грега Сміта .

Щоб отримати точні підрахунки, інші відповіді наразі наштовхуються на деякі питання, деякі з них серйозні (див. Нижче). Ось версія, яка сподівається краще:

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;

Він приймає ім'я схеми як параметр, або publicякщо параметр не вказаний.

Для роботи з певним списком схем або списком, що надходить із запиту, не змінюючи функції, його можна викликати в межах запиту таким чином:

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;

Це створює вихід із 3 стовпців зі схемою, таблицею та підрахунком рядків.

Ось деякі питання в інших відповідях, що ця функція дозволяє уникнути:

  • Імена таблиць і схем не слід вводити у виконуваний SQL, не цитуючи їх, quote_identабо з більш сучасною format()функцією з її %Iрядком формату. В іншому випадку деякі зловмисники можуть назвати свою таблицю, tablename;DROP TABLE other_tableщо цілком справедливо як ім'я таблиці.

  • Навіть без введення SQL та проблем із забавними символами, назва таблиці може існувати у варіантах, що залежать від конкретного випадку. Якщо таблиця буде іменована ABCDта інша abcd, SELECT count(*) FROM...вживане ім'я повинне використовувати цитується, інакше воно буде пропускатися ABCDта рахуватись abcdдвічі. %IФормат робить це автоматично.

  • information_schema.tablesперелічує власні складені типи на додаток до таблиць, навіть коли table_type є 'BASE TABLE'(!). Як наслідок, ми не можемо повторити information_schema.tables, інакше ризикуємо мати, select count(*) from name_of_composite_typeі це не вдасться. OTOH pg_class where relkind='r'завжди повинен працювати добре.

  • Тип COUNT () є bigint, ні int. Таблиці, що містять більше 2,15 мільярдів рядків (хоч кількість (*) на них - погана ідея).

  • Постійний тип не потрібно створювати, щоб функція повертала набір результатів з кількох стовпців. RETURNS TABLE(definition...)є кращою альтернативою.


18

Якщо ви не заперечуєте проти потенційно несвіжих даних, ви можете отримати доступ до тієї ж статистики, яка використовується оптимізатором запитів .

Щось на зразок:

SELECT relname, n_tup_ins - n_tup_del as rowcount FROM pg_stat_all_tables;

@mlissner: Якщо ваш інтервал автовакууму занадто довгий або ви не запустили посібник ANALYZEпо таблиці, статистика може вийти з ладу . Це питання завантаження бази даних та налаштування бази даних (якщо статистику оновлюють частіше, статистика буде точнішою, але це може знизити продуктивність виконання). Зрештою, єдиний спосіб отримати точні дані - запустити select count(*) from tableвсі таблиці.
ig0774

17

Хитра, практична відповідь для людей, які намагаються оцінити, який план Heroku їм потрібен, і не можуть чекати, коли лічильник повільних рядків Heroku оновиться:

В основному ви хочете запустити \dtв psql, копіювати результати в вашому улюбленому текстовому редакторі (це буде виглядати наступним чином :

 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

), потім запустіть пошук по регулярному вираженню та замініть так:

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

до:

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

що дасть вам щось подібне до цього:

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
;

(Вам потрібно буде видалити останній unionі додати крапку з комою в кінці вручну)

Запустіть його psqlі закінчите.

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

Мені подобається ця ідея
GuilPejon

В Атомі мені довелося переробити пошук і замінити так: select '$1', count(*) from $1 union/g
Чак

Також у публікації сказано: "Вам потрібно буде видалити об'єднання та додати крапку з комою". Це друкарня. Вам потрібно видалити /g(зберегти union) і додати один крапку з комою ( ;) в самому кінці. Не забудьте видалити останній unionперед крапкою з комою.
патрон

1
"Не забудьте видалити останній unionперед крапкою з комою" - це те, що я мав на увазі :) Додано слово "останнє" для уточнення
Aur Saraf

10

Не впевнений, чи відповідь у баші прийнятна для вас, але 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
По суті, це просто зводиться до того ж select count(*) from table_name;в ОП!
Ноах Магедман

8

Зазвичай я не покладаюся на статистику, особливо в 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;

Це добре, але перший запит також повинен містити схему для значення rownum. Якщо в різних схемах є суперечливі імена, це не буде працювати, як очікувалося. Таким чином, ця частина запиту повинна виглядати більше dsql2('select count(*) from livescreen.'||table_name)або краще, щоб вона могла бути перетворена на власну функцію.
jakub-olczyk

6

Я не пам'ятаю URL-адреси, звідки я це зібрав. Але сподіваюся, що це повинно вам допомогти:

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; 

Виконання select count_em_all();повинно отримати кількість рядків усіх ваших таблиць.


1
Добре зазначати назви стовпців (наприклад quote_ident(t_name.relname)), щоб забезпечити належну підтримку незвичайних імен (наприклад, "назва стовпця").
Горський

Щоб відкинути його згодом: DROP FUNCTION count_em_all ();
Алекс Габі

Помилка: виберіть count_em_all (); ПОМИЛКА: помилка синтаксису на "групі" РОБОЧИЙ ЛІНІЯ 1: ВИБІР КУНТУВАННЯ () ЯК "підрахунок" ВІД групи ВИКОНАННЯ заяви
Алекс Габі

Чудово! Вибір та сортування - SELECT * FROM count_em_all() as r ORDER BY r.num_rows DESC;
Ken4scholars

6

Прості два кроки:
(Примітка: Не потрібно нічого змінювати - просто скопіюйте пасту)
1. створити функцію

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. Запустіть цей запит, щоб отримати кількість рядків для всіх таблиць

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;

або

Щоб отримати рядки, підраховують таблиці

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

Я зробив невелику варіацію, щоб включити всі таблиці, також для непублічних таблиць.

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; 

використовувати select count_em_all();для виклику.

Сподіваюся, ви знайдете це корисно. Пол


ПОМИЛКА: "r.table_schema" не відома змінна
slashdottir

2

Це працювало для мене

ВИБІРТЕ ім'я схеми, перейменування, n_live_tup ІЗ pg_stat_user_tables ЗАМОВИТИ НА n_live_tup DESC;


1

Мені подобається Daniel Веріте відповідь . Але коли ви не можете використовувати оператор CREATE, ви можете використовувати bash-рішення, або, якщо ви користувач Windows, то один з повноважень:

# 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

Мені хотілося загального з усіх таблиць + список таблиць з їх кількістю. Трохи схожа на діаграму ефективності того, де було проведено найбільше часу

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

Звичайно, ви можете також поставити LIMITзастереження про результати в цій версії, щоб отримати найбільших nзлочинців, а також загальну кількість.

Єдине, що слід зазначити з цього приводу, це те, що вам потрібно дати йому просидіти деякий час після масового імпорту. Я перевірив це, просто додавши 5000 рядків до бази даних в декількох таблицях за допомогою реальних даних про імпорт. Він показував 1800 записів протягом хвилини (можливо, вікно, яке можна настроювати)

Це ґрунтується на роботі https://stackoverflow.com/a/2611745/1548557 , тож дякую вам та вдячність за запит, який можна використовувати в рамках CTE

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.