Cara cepat untuk menemukan jumlah baris tabel di PostgreSQL

107

Saya perlu mengetahui jumlah baris dalam tabel untuk menghitung persentase. Jika jumlah total lebih besar dari beberapa konstanta yang telah ditentukan sebelumnya, saya akan menggunakan nilai konstanta. Jika tidak, saya akan menggunakan jumlah baris yang sebenarnya.

Saya bisa menggunakan SELECT count(*) FROM table. Tetapi jika nilai konstanta saya adalah 500.000 dan saya memiliki 5.000.000.000 baris di tabel saya, menghitung semua baris akan membuang banyak waktu.

Apakah mungkin untuk berhenti menghitung segera setelah nilai konstanta saya terlampaui?

Saya membutuhkan jumlah baris yang tepat hanya selama itu di bawah batas yang diberikan. Sebaliknya, jika hitungannya di atas batas, saya menggunakan nilai batas sebagai gantinya dan menginginkan jawaban secepat mungkin.

Sesuatu seperti ini:

SELECT text,count(*), percentual_calculus()  
FROM token  
GROUP BY text  
ORDER BY count DESC;
Renato Dinhani
sumber
5
Bisa tidak Anda hanya mencoba untuk memilih yang pertama n baris di mana n = konstan + 1 ? Jika mengembalikan lebih dari konstanta Anda, Anda tahu Anda harus menggunakan konstanta Anda, dan jika tidak, Anda baik?
gddc
Apakah Anda memiliki bidang identitas atau kenaikan otomatis dalam tabel
Sparky
1
@Sparky: PK yang didukung urutan tidak dijamin akan berdekatan, baris dapat dihapus atau mungkin ada celah yang disebabkan oleh transaksi yang dibatalkan.
mu terlalu pendek
Pembaruan Anda tampaknya bertentangan dengan pertanyaan awal Anda ... apakah Anda perlu mengetahui jumlah baris yang tepat, atau Anda hanya perlu mengetahui jumlah persisnya jika di bawah ambang batas?
Flimzy
1
@ RenatoDinhaniConceição: Bisakah Anda menjelaskan masalah Tepat yang Anda coba pecahkan? Saya pikir jawaban saya di bawah ini menyelesaikan apa yang awalnya Anda katakan sebagai masalah Anda. Pembaruan membuatnya terlihat seperti Anda ingin menghitung (*) serta banyak bidang lainnya. Akan membantu jika Anda dapat menjelaskan dengan tepat apa yang Anda coba lakukan. Terima kasih.
Ritesh

Jawaban:

224

Menghitung baris dalam tabel besar dikenal lambat di PostgreSQL. Untuk mendapatkan angka yang tepat itu harus melakukan penghitungan penuh baris karena sifat MVCC . Ada cara untuk mempercepat ini secara dramatis jika jumlah tersebut tidak tidak harus tepat seperti itu tampaknya dalam kasus Anda.

Alih-alih mendapatkan hitungan pastinya ( lambat dengan tabel besar):

SELECT count(*) AS exact_count FROM myschema.mytable;

Anda mendapatkan perkiraan yang mendekati seperti ini ( sangat cepat ):

SELECT reltuples::bigint AS estimate FROM pg_class where relname='mytable';

Seberapa dekat perkiraannya tergantung pada apakah Anda ANALYZEcukup berlari . Biasanya sangat dekat.
Lihat FAQ Wiki PostgreSQL .
Atau halaman wiki khusus untuk kinerja hitungan (*) .

Lebih baik

Artikel di dalam PostgreSQL Wiki adalah adalah sedikit ceroboh . Ini mengabaikan kemungkinan bahwa ada beberapa tabel dengan nama yang sama dalam satu database - dalam skema yang berbeda. Untuk menjelaskan itu:

SELECT c.reltuples::bigint AS estimate
FROM   pg_class c
JOIN   pg_namespace n ON n.oid = c.relnamespace
WHERE  c.relname = 'mytable'
AND    n.nspname = 'myschema'

Atau lebih baik lagi

SELECT reltuples::bigint AS estimate
FROM   pg_class
WHERE  oid = 'myschema.mytable'::regclass;

Lebih cepat, lebih sederhana, lebih aman, lebih elegan. Lihat manual tentang Jenis Pengenal Objek .

Gunakan to_regclass('myschema.mytable')di Postgres 9.4+ untuk menghindari pengecualian untuk nama tabel yang tidak valid:


TABLESAMPLE SYSTEM (n) di Postgres 9.5+

SELECT 100 * count(*) AS estimate FROM mytable TABLESAMPLE SYSTEM (1);

Seperti komentar @a_horse , klausa yang baru ditambahkan untuk SELECTperintah tersebut mungkin berguna jika statistik dalam pg_classtidak cukup terkini karena alasan tertentu. Sebagai contoh:

  • Tidak ada autovacuumlari.
  • Segera setelah besar INSERTatau DELETE.
  • TEMPORARYtabel (yang tidak tercakup oleh autovacuum).

Ini hanya melihat pemilihan acak n % ( 1dalam contoh) blok dan menghitung baris di dalamnya. Sampel yang lebih besar meningkatkan biaya dan mengurangi kesalahan, pilihan Anda. Akurasi bergantung pada lebih banyak faktor:

  • Distribusi ukuran baris. Jika blok tertentu kebetulan memiliki baris yang lebih lebar dari biasanya, jumlahnya lebih rendah dari biasanya, dll.
  • Tupel mati atau FILLFACTORmenempati ruang per blok. Jika didistribusikan secara tidak merata di seluruh tabel, perkiraan mungkin salah.
  • Kesalahan pembulatan umum.

Dalam kebanyakan kasus, perkiraan dari pg_classakan lebih cepat dan lebih akurat.

Jawaban untuk pertanyaan sebenarnya

Pertama, saya perlu mengetahui jumlah baris dalam tabel itu, jika jumlah total lebih besar dari beberapa konstanta yang telah ditentukan sebelumnya,

Dan apakah itu ...

... mungkin pada saat hitungan melewati nilai konstanta saya, itu akan menghentikan penghitungan (dan tidak menunggu untuk menyelesaikan penghitungan untuk menginformasikan jumlah baris lebih besar).

Iya. Anda dapat menggunakan subkueri denganLIMIT :

SELECT count(*) FROM (SELECT 1 FROM token LIMIT 500000) t;

Postgres benar-benar berhenti menghitung di luar batas yang diberikan, Anda mendapatkan hitungan tepat dan terkini hingga n baris (500000 dalam contoh), dan n sebaliknya. Tidak secepat perkiraannya pg_class.

Erwin Brandstetter
sumber
8
Saya akhirnya memperbarui halaman Postgres Wiki dengan kueri yang ditingkatkan.
Erwin Brandstetter
5
Dengan 9,5 mendapatkan perkiraan cepat harus dimungkinkan menggunakan tablesampleklausa: misalnyaselect count(*) * 100 as cnt from mytable tablesample system (1);
a_horse_with_no_name
1
@JeffWidman: Semua perkiraan ini bisa lebih besar dari jumlah baris sebenarnya karena berbagai alasan. Paling tidak, penghapusan mungkin telah terjadi untuk sementara waktu.
Erwin Brandstetter
2
@ErwinBrandstetter menyadari bahwa pertanyaan ini sudah lama, tetapi jika Anda memasukkan kueri ke dalam subkueri maka lakukan batas apakah ini masih akan efisien atau apakah seluruh subkueri akan dieksekusi kemudian dibatasi di kueri luar. SELECT count(*) FROM (Select * from (SELECT 1 FROM token) query) LIMIT 500000) limited_query;(Saya bertanya karena saya mencoba mendapatkan hitungan dari kueri sewenang-wenang yang mungkin sudah memiliki klausa batas di dalamnya)
Nicholas Erdenberger
1
@NicholasErdenberger: Itu tergantung pada subquery. Postgres mungkin perlu mempertimbangkan lebih banyak baris dari batasnya (seperti ORDER BY somethingsaat tidak dapat menggunakan indeks, atau dengan fungsi agregat). Selain itu, hanya sejumlah baris dari subquery yang diproses.
Erwin Brandstetter
12

Saya melakukan ini sekali di aplikasi postgres dengan menjalankan:

EXPLAIN SELECT * FROM foo;

Kemudian memeriksa output dengan regex, atau logika serupa. Untuk SELECT * sederhana, baris pertama keluaran akan terlihat seperti ini:

Seq Scan on uids  (cost=0.00..1.21 rows=8 width=75)

Anda dapat menggunakan rows=(\d+)nilai sebagai perkiraan kasar dari jumlah baris yang akan dikembalikan, kemudian hanya melakukan yang sebenarnya SELECT COUNT(*)jika perkiraan tersebut, katakanlah, kurang dari 1,5x ambang Anda (atau angka berapa pun yang Anda anggap masuk akal untuk aplikasi Anda).

Bergantung pada kompleksitas kueri Anda, angka ini mungkin menjadi semakin kurang akurat. Faktanya, dalam aplikasi saya, saat kami menambahkan gabungan dan kondisi kompleks, itu menjadi sangat tidak akurat sehingga benar-benar tidak berharga, bahkan untuk mengetahui berapa banyak baris yang akan kami kembalikan dalam kekuatan 100, jadi kami harus meninggalkan strategi itu.

Tetapi jika kueri Anda cukup sederhana sehingga Pg dapat memprediksi dalam batas kesalahan yang wajar berapa banyak baris yang akan dikembalikan, ini mungkin berhasil untuk Anda.

Flimzy
sumber
2

Referensi diambil dari Blog ini.

Anda dapat menggunakan di bawah ini untuk membuat kueri untuk menemukan jumlah baris.

Menggunakan pg_class:

 SELECT reltuples::bigint AS EstimatedCount
    FROM   pg_class
    WHERE  oid = 'public.TableName'::regclass;

Menggunakan pg_stat_user_tables:

SELECT 
    schemaname
    ,relname
    ,n_live_tup AS EstimatedCount 
FROM pg_stat_user_tables 
ORDER BY n_live_tup DESC;
Anvesh
sumber
Harap diingat bahwa Anda perlu melakukan ANALISIS VAKUM pada tabel Anda agar metode ini dapat bekerja.
William Abma
1

Di Oracle, Anda dapat menggunakan rownumuntuk membatasi jumlah baris yang dikembalikan. Saya menebak konstruksi serupa ada di SQL lain juga. Jadi, untuk contoh yang Anda berikan, Anda dapat membatasi jumlah baris yang dikembalikan menjadi 500001 dan menerapkan count(*)kemudian:

SELECT (case when cnt > 500000 then 500000 else cnt end) myCnt
FROM (SELECT count(*) cnt FROM table WHERE rownum<=500001)
Ritesh
sumber
1
SELECT count (*) cnt FROM table akan selalu mengembalikan satu baris. Tidak yakin bagaimana LIMIT akan menambahkan manfaat apa pun di sana.
Chris Bednarski
@ChrisBednarski: Saya memverifikasi versi oracle dari jawaban saya di Oracle db. Ini berfungsi dengan baik dan memecahkan apa yang saya pikir sebagai masalah OP (0,05 dtk count(*)dengan rownum, 1 dtk tanpa menggunakan rownum). Ya, SELECT count(*) cnt FROM tableselalu akan mengembalikan 1 baris, tetapi dengan kondisi LIMIT, itu akan mengembalikan "500001" ketika ukuran tabel lebih dari 500000 dan <size> ketika ukuran tabel <= 500000.
Ritesh
2
Kueri PostgreSQL Anda benar-benar tidak masuk akal. Salah secara sintaksis dan logis. Harap perbaiki atau hapus.
Erwin Brandstetter
@ErwinBrandstetter: Dihapus, tidak menyadari PostgreSQL sangat berbeda.
Ritesh
@ Allrite: tidak diragukan lagi kueri Oracle Anda berfungsi dengan baik. LIMIT bekerja secara berbeda. Pada tingkat dasar, ini membatasi jumlah baris yang dikembalikan ke klien, bukan jumlah baris yang ditanyakan oleh mesin database.
Chris Bednarski
0

Seberapa lebar kolom teks?

Dengan GROUP BY, tidak banyak yang dapat Anda lakukan untuk menghindari pemindaian data (setidaknya pemindaian indeks).

Saya akan merekomendasikan:

  1. Jika memungkinkan, ubah skema untuk menghapus duplikasi data teks. Dengan cara ini penghitungan akan terjadi pada bidang kunci asing yang sempit di tabel 'banyak'.

  2. Alternatifnya, membuat kolom yang dihasilkan dengan HASH teks, lalu GROUP BY kolom hash. Sekali lagi, ini untuk mengurangi beban kerja (memindai melalui indeks kolom sempit)

Edit:

Pertanyaan asli Anda tidak cocok dengan hasil edit Anda. Saya tidak yakin apakah Anda mengetahui bahwa JUMLAH, ketika digunakan dengan GROUP BY, akan mengembalikan jumlah item per grup dan bukan jumlah item di seluruh tabel.

Chris Bednarski
sumber
0

Anda bisa mendapatkan hitungan dengan kueri di bawah ini (tanpa * atau nama kolom apa pun).

select from table_name;
SuperNova
sumber
2
Ini sepertinya tidak lebih cepat dari count(*).
Cerah
-3

Untuk SQL Server (2005 atau lebih tinggi), metode cepat dan andal adalah:

SELECT SUM (row_count)
FROM sys.dm_db_partition_stats
WHERE object_id=OBJECT_ID('MyTableName')   
AND (index_id=0 or index_id=1);

Detail tentang sys.dm_db_partition_stats dijelaskan di MSDN

Kueri menambahkan baris dari semua bagian tabel (mungkin) yang dipartisi.

index_id = 0 adalah tabel tidak berurutan (Heap) dan index_id = 1 adalah tabel terurut (clustered index)

Metode yang bahkan lebih cepat (tetapi tidak dapat diandalkan) dijelaskan di sini.

DrKoch
sumber