Làm thế nào để bạn tìm thấy số lượng hàng cho tất cả các bảng của bạn trong Postgres


395

Tôi đang tìm cách để tìm số lượng hàng cho tất cả các bảng của mình trong Postgres. Tôi biết tôi có thể làm một bảng này cùng một lúc với:

SELECT count(*) FROM table_name;

nhưng tôi muốn xem số lượng hàng cho tất cả các bảng và sau đó đặt hàng theo đó để có ý tưởng về tất cả các bảng của tôi lớn như thế nào.

Câu trả lời:


582

Có ba cách để có được số lượng này, mỗi cách đều có sự đánh đổi riêng.

Nếu bạn muốn có một số đếm thực, bạn phải thực thi câu lệnh CHỌN giống như câu lệnh bạn đã sử dụng đối với mỗi bảng. Điều này là do PostgreSQL giữ thông tin hiển thị hàng trong chính hàng đó chứ không phải bất kỳ nơi nào khác, vì vậy mọi số liệu chính xác chỉ có thể liên quan đến một số giao dịch. Bạn đang nhận được một số lượng những gì giao dịch nhìn thấy tại thời điểm khi nó thực hiện. Bạn có thể tự động hóa việc này để chạy với mọi bảng trong cơ sở dữ liệu, nhưng có lẽ bạn không cần mức độ chính xác đó hoặc muốn chờ lâu.

Cách tiếp cận thứ hai lưu ý rằng trình thu thập số liệu thống kê theo dõi khoảng bao nhiêu hàng "sống" (không bị xóa hoặc bị lỗi thời bởi các bản cập nhật sau này) bất cứ lúc nào. Giá trị này có thể bị giảm một chút trong hoạt động nặng, nhưng nhìn chung là một ước tính tốt:

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

Điều đó cũng có thể cho bạn thấy có bao nhiêu hàng đã chết, đây là một con số thú vị để theo dõi.

Cách thứ ba là lưu ý rằng lệnh ANALYZE của hệ thống, được thực thi bởi quy trình tự động thường xuyên kể từ PostgreQuery 8.3 để cập nhật thống kê bảng, cũng tính toán ước tính hàng. Bạn có thể lấy cái đó như thế này:

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;

Những truy vấn nào tốt hơn để sử dụng là khó nói. Thông thường tôi đưa ra quyết định dựa trên việc có thêm thông tin hữu ích nào tôi cũng muốn sử dụng bên trong pg_group hoặc bên trong pg_stat_user_tables. Đối với các mục đích đếm cơ bản chỉ để xem những thứ lớn nói chung như thế nào, hoặc phải đủ chính xác.


2
Để hoàn thành, vui lòng thêm tùy chọn này cho tùy chọn đầu tiên (cảm ơn tới @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 Phiên bản nào được giới thiệu n_live_tup? Cơ sở dữ liệu Redshift của tôi thiếu cột đó. Nó là một dẫn xuất của Postgres 8.0.2.
Iain Samuel McLean Elder

1
Truy vấn 'cách tiếp cận thứ hai' (sử dụng pg_stat_user_tables) trả lại phần lớn số không n_live_tupcho tôi vì ANALYZEchưa bao giờ được chạy. Thay vì chạy ANALYZEtrên mọi lược đồ / bảng và chờ đợi câu trả lời, trước tiên tôi đã kiểm tra kết quả bằng cách sử dụng 'cách tiếp cận thứ ba' và cách đó (sử dụng pg_class) trả về số đếm rất chính xác.
Brian D

@BrianD, có thể thực hiện phân tích ở cấp cơ sở dữ liệu bằng cách sử dụng tiện ích được phân tích dưới dạng "
dbb

69

Đây là một giải pháp không yêu cầu các hàm để có được số đếm chính xác cho mỗi bảng:

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_xmlsẽ chạy truy vấn SQL đã qua và trả về một XML có kết quả (số hàng cho bảng đó). Bên ngoài xpath()sau đó sẽ trích xuất thông tin đếm từ xml đó và chuyển đổi nó thành một số

Bảng dẫn xuất không thực sự cần thiết, nhưng làm cho xpath()dễ hiểu hơn một chút - nếu không thì toàn bộ query_to_xml()sẽ cần phải được chuyển đến xpath()hàm.


3
Rất thông minh. Thật đáng tiếc là không có query_to_jsonb().
klin

@a_horse_with_no_name, nó sẽ đưa ra bất kỳ vấn đề hiệu suất nào trên các bảng bận rộn và lớn trong khi thực hiện?
Spike

@Spike: vấn đề hiệu năng so với những gì? Nút cổ chai hiệu năng chính đang chạy select count(*)trên mỗi bảng.
a_horse_with_no_name

@a_horse_with_no_name, bằng cách thực hiện hàm x_path đối với 100 triệu bản ghi.
Spike

@Spike: xpath()chức năng chỉ được áp dụng cho một hàng duy nhất - kết quả củacount(*)
a_horse_with_no_name

24

Để có được ước tính, xem câu trả lời của Greg Smith .

Để có được số đếm chính xác, các câu trả lời khác cho đến nay vẫn bị ảnh hưởng bởi một số vấn đề, một số trong số chúng nghiêm trọng (xem bên dưới). Đây là một phiên bản hy vọng tốt hơn:

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;

Nó lấy một tên lược đồ làm tham số hoặc publicnếu không có tham số nào được đưa ra.

Để làm việc với một danh sách cụ thể của lược đồ hoặc danh sách đến từ một truy vấn mà không sửa đổi hàm, nó có thể được gọi từ trong một truy vấn như thế này:

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;

Điều này tạo ra một đầu ra 3 cột với lược đồ, bảng và số hàng.

Bây giờ đây là một số vấn đề trong các câu trả lời khác mà chức năng này tránh được:

  • Tên bảng và lược đồ không nên được chèn vào SQL thực thi mà không được trích dẫn, bằng quote_identhoặc với format()hàm hiện đại hơn với %Ichuỗi định dạng của nó . Nếu không, một số người độc hại có thể đặt tên bảng của họ tablename;DROP TABLE other_tablelà hoàn toàn hợp lệ như tên bảng.

  • Ngay cả khi không có SQL và các vấn đề về ký tự vui nhộn, tên bảng có thể tồn tại trong các biến thể khác nhau tùy theo từng trường hợp. Nếu một bảng được đặt tên ABCDvà một bảng khác abcd, thì SELECT count(*) FROM...phải sử dụng một tên được trích dẫn nếu không nó sẽ bỏ qua ABCDvà đếm abcdhai lần. Các %Iđịnh dạng thực hiện điều này tự động.

  • information_schema.tablesliệt kê các loại hỗn hợp tùy chỉnh ngoài các bảng, ngay cả khi bảng_type là 'BASE TABLE'(!). Kết quả là, chúng ta không thể lặp đi lặp lại information_schema.tables, nếu không chúng ta có nguy cơ gặp phải select count(*) from name_of_composite_typevà điều đó sẽ thất bại. OTOH pg_class where relkind='r'phải luôn hoạt động tốt.

  • Loại COUNT () là bigint, không int. Các bảng có hơn 2,15 tỷ hàng có thể tồn tại (chạy số đếm (*) trên chúng là một ý tưởng tồi).

  • Một loại vĩnh viễn không cần phải được tạo cho một hàm để trả về một tập kết quả với một số cột. RETURNS TABLE(definition...)là một thay thế tốt hơn.


18

Nếu bạn không bận tâm đến dữ liệu cũ, bạn có thể truy cập vào cùng số liệu thống kê được sử dụng bởi trình tối ưu hóa truy vấn .

Cái gì đó như:

SELECT relname, n_tup_ins - n_tup_del as rowcount FROM pg_stat_all_tables;

@mlissner: Nếu khoảng thời gian tự động của bạn quá dài hoặc bạn chưa chạy hướng dẫn ANALYZEtrên bàn, số liệu thống kê có thể giảm. Đó là một câu hỏi về tải cơ sở dữ liệu và cách cấu hình cơ sở dữ liệu (nếu số liệu thống kê được cập nhật thường xuyên hơn, số liệu thống kê sẽ chính xác hơn, nhưng nó có thể làm giảm hiệu suất thời gian chạy). Cuối cùng, cách duy nhất để có được dữ liệu chính xác là chạy select count(*) from tablecho tất cả các bảng.
ig0774

17

Câu trả lời hóc búa, thiết thực cho những người đang cố gắng đánh giá kế hoạch Heroku nào họ cần và không thể chờ bộ đếm hàng chậm của heroku để làm mới:

Về cơ bản bạn muốn chạy \dtvào psql, sao chép kết quả vào trình soạn thảo văn bản yêu thích của bạn (nó sẽ trông như thế này:

 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

), sau đó chạy tìm kiếm regex và thay thế như thế này:

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

đến:

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

sẽ mang lại cho bạn một cái gì đó rất giống với điều này:

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
;

(Bạn sẽ cần xóa phần cuối cùng unionvà thêm dấu chấm phẩy ở cuối bằng tay)

Chạy nó vào psqlvà bạn đã hoàn thành.

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

Tôi thích ý tưởng này
GuilPejon

Trong Atom, tôi đã phải regex tìm kiếm và thay thế như thế này: select '$1', count(*) from $1 union/g
chuck

Ngoài ra, bài đăng có nội dung: "Bạn sẽ cần xóa liên minh và thêm dấu chấm phẩy vào cuối." Đây là một lỗi đánh máy. Bạn cần xóa /g(giữ union) và thêm một dấu chấm phẩy ( ;) vào cuối. Đừng quên xóa cái cuối cùng uniontrước dấu chấm phẩy.
chuck

1
"Đừng quên xóa cái cuối cùng uniontrước dấu chấm phẩy" là ý của tôi :) Đã thêm từ "cuối cùng" để làm rõ
Aur Saraf

10

Không chắc chắn nếu một câu trả lời trong bash được bạn chấp nhận, nhưng 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
Về bản chất, điều này chỉ sôi sục đến cùng select count(*) from table_name;trong OP!
Noach Magedman

8

Tôi thường không dựa vào số liệu thống kê, đặc biệt là trong 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;

Điều này là tốt nhưng truy vấn đầu tiên cũng nên bao gồm lược đồ cho giá trị rownum. Nếu có các tên xung đột trong các lược đồ khác nhau, điều này sẽ không hoạt động như mong đợi. Vì vậy, phần truy vấn này sẽ trông giống dsql2('select count(*) from livescreen.'||table_name)hoặc tốt hơn nó có thể được biến thành một chức năng của chính nó.
jakub-olchot

6

Tôi không nhớ URL từ nơi tôi đã thu thập này. Nhưng hy vọng điều này sẽ giúp bạn:

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; 

Thực hiện select count_em_all();sẽ giúp bạn đếm số hàng của tất cả các bảng của bạn.


1
Bạn nên trích dẫn tên cột (như quote_ident(t_name.relname)) để đảm bảo hỗ trợ phù hợp cho các tên bất thường (ví dụ: "tên cột").
gorsky

Để thả nó sau đó: DROP FUNCTION Count_em_all ();
Aalex Gabi

Có lỗi: chọn Count_em_all (); LRI: lỗi cú pháp tại hoặc gần "nhóm" LINE 1: CHỌN COUNT () NHƯ "đếm" TỪ nhóm ^ QUERY: CHỌN COUNT () NHƯ "đếm" TỪ nhóm TIẾP THEO: Hàm PL / pgQuery Count_em_all () dòng 18 tại FOR over Tuyên bố EXECUTE
Aalex Gabi

Tuyệt quá! Để chọn và sắp xếp - SELECT * FROM count_em_all() as r ORDER BY r.num_rows DESC;
Ken4schologists

6

Hai bước đơn giản:
(Lưu ý: Không cần thay đổi bất cứ điều gì - chỉ cần sao chép dán)
1. tạo chức năng

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. Chạy truy vấn này để lấy số lượng hàng cho tất cả các bảng

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;

hoặc

Để có được hàng đếm theo bảng

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

Tôi đã thực hiện một biến thể nhỏ để bao gồm tất cả các bảng, cũng cho các bảng không công khai.

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; 

sử dụng select count_em_all();để gọi nó

Hy vọng bạn tìm thấy điều này hữu ích. Paul


LRI: "r.table_schema" không phải là một biến đã biết
slashdottir

2

Điều này làm việc cho tôi

CHỌN schemaname, relname, n_live_tup TỪ pg_stat_user_tables ĐẶT HÀNG THEO n_live_tup DESC;


1

Tôi thích câu trả lời của Daniel Vérité . Nhưng khi bạn không thể sử dụng câu lệnh CREATE, bạn có thể sử dụng giải pháp bash hoặc, nếu bạn là người dùng windows, một quyền hạn:

# 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

Tôi muốn tổng số từ tất cả các bảng + một danh sách các bảng với số lượng của chúng. Giống như một biểu đồ hiệu suất của nơi dành phần lớn thời gian

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

Tất nhiên bạn cũng có thể đặt một LIMITđiều khoản về kết quả trong phiên bản này để bạn có được những nngười phạm tội lớn nhất cũng như tổng cộng.

Một điều cần lưu ý về điều này là bạn cần để nó ngồi một lúc sau khi nhập hàng loạt. Tôi đã kiểm tra điều này bằng cách chỉ thêm 5000 hàng vào cơ sở dữ liệu qua nhiều bảng bằng dữ liệu nhập thực. Nó hiển thị 1800 bản ghi trong khoảng một phút (có thể là cửa sổ có thể định cấu hình)

Điều này dựa trên https://stackoverflow.com/a/2611745/1548557 hoạt động, vì vậy cảm ơn bạn và công nhận điều đó cho truy vấn được sử dụng trong CTE

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.