Un modo veloce per scoprire il conteggio delle righe di una tabella in PostgreSQL


107

Ho bisogno di conoscere il numero di righe in una tabella per calcolare una percentuale. Se il conteggio totale è maggiore di una costante predefinita, userò il valore della costante. Altrimenti, userò il numero effettivo di righe.

Posso usare SELECT count(*) FROM table. Ma se il mio valore costante è 500.000 e ho 5.000.000.000 di righe nella mia tabella, il conteggio di tutte le righe farà perdere molto tempo.

È possibile interrompere il conteggio non appena il mio valore costante viene superato?

Ho bisogno del numero esatto di righe solo finché è inferiore al limite indicato. Altrimenti, se il conteggio è superiore al limite, utilizzo invece il valore limite e desidero la risposta il più velocemente possibile.

Qualcosa come questo:

SELECT text,count(*), percentual_calculus()  
FROM token  
GROUP BY text  
ORDER BY count DESC;

5
Non potresti semplicemente provare a selezionare le prime n righe dove n = costante + 1 ? Se restituisce più della tua costante, sai che dovresti usare la tua costante, e se non lo fa sei bravo?
gddc

Hai un campo identità o incremento automatico nella tabella
Sparky

1
@Sparky: non è garantito che le PK supportate dalla sequenza siano contigue, le righe possono essere eliminate o potrebbero esserci delle lacune causate da transazioni interrotte.
mu è troppo breve

Il tuo aggiornamento sembra contraddire la tua domanda originale ... hai bisogno di conoscere il numero esatto di righe o devi solo conoscere il numero esatto se è inferiore a una soglia?
Flimzy

1
@ RenatoDinhaniConceição: puoi spiegare il problema esatto che stai cercando di risolvere? Penso che la mia risposta di seguito risolva quello che inizialmente hai detto essere il tuo problema. L'aggiornamento fa sembrare che tu voglia contare (*) così come molti altri campi. Sarebbe utile se tu potessi spiegare esattamente cosa stai cercando di fare. Grazie.
Ritesh

Risposte:


224

Il conteggio delle righe in tabelle di grandi dimensioni è noto per essere lento in PostgreSQL. Per ottenere un numero preciso, è necessario eseguire un conteggio completo delle righe a causa della natura di MVCC . C'è un modo per accelerare drasticamente questo se il conteggio non deve essere esatto come sembra essere nel tuo caso.

Invece di ottenere il conteggio esatto ( lento con tavoli grandi):

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

Ottieni una stima ravvicinata come questa ( estremamente veloce ):

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

Quanto è vicina la stima dipende dal fatto che corri ANALYZEabbastanza. Di solito è molto vicino.
Vedi le FAQ sul Wiki di PostgreSQL .
Oppure la pagina wiki dedicata per il conteggio (*) delle prestazioni .

Meglio ancora

L'articolo nel Wiki di PostgreSQL è stato un po 'sciatto . Ha ignorato la possibilità che ci possano essere più tabelle con lo stesso nome in un database - in schemi diversi. Per tenere conto di ciò:

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'

O meglio ancora

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

Più veloce, più semplice, più sicuro, più elegante. Vedere il manuale sui tipi di identificatori di oggetti .

Utilizzare to_regclass('myschema.mytable')in Postgres 9.4+ per evitare eccezioni per nomi di tabelle non validi:


TABLESAMPLE SYSTEM (n) in Postgres 9.5+

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

Come ha commentato @a_horse , la clausola appena aggiunta per il SELECTcomando potrebbe essere utile se le statistiche in pg_classnon sono abbastanza aggiornate per qualche motivo. Per esempio:

  • Nessuna autovacuumcorsa.
  • Subito dopo un grande INSERTo DELETE.
  • TEMPORARYtabelle (che non sono coperte da autovacuum).

Questo guarda solo una selezione casuale n % ( 1nell'esempio) di blocchi e conta le righe in essa. Un campione più grande aumenta il costo e riduce l'errore, scegli tu. La precisione dipende da più fattori:

  • Distribuzione delle dimensioni delle righe. Se un determinato blocco contiene righe più larghe del normale, il conteggio è inferiore al normale ecc.
  • Tuple morte o uno FILLFACTORspazio di occupazione per blocco. Se distribuito in modo non uniforme sulla tabella, la stima potrebbe non essere valida.
  • Errori di arrotondamento generali.

Nella maggior parte dei casi la stima da pg_classsarà più veloce e più accurata.

Risposta alla domanda effettiva

Innanzitutto, ho bisogno di conoscere il numero di righe in quella tabella, se il conteggio totale è maggiore di una costante predefinita,

E se è ...

... è possibile nel momento in cui il conteggio supera il mio valore costante, interromperà il conteggio (e non vedo l'ora di finire il conteggio per informare che il conteggio delle righe è maggiore).

Sì. Puoi utilizzare una sottoquery conLIMIT :

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

Postgres interrompe effettivamente il conteggio oltre il limite dato, ottieni un conteggio esatto e corrente fino a n righe (500000 nell'esempio) e n altrimenti. Non così veloce come la stima in pg_class, però.


8
Alla fine ho aggiornato la pagina Wiki di Postgres con la query migliorata.
Erwin Brandstetter

5
Con 9.5 dovrebbe essere possibile ottenere rapidamente un preventivo utilizzando la tablesampleclausola: ad esempioselect count(*) * 100 as cnt from mytable tablesample system (1);
a_horse_with_no_name

1
@ JeffWidman: tutte queste stime possono essere maggiori del numero effettivo di righe per vari motivi. Non da ultimo, nel frattempo potrebbero essere state eseguite delle eliminazioni.
Erwin Brandstetter

2
@ErwinBrandstetter si rende conto che questa domanda è vecchia, ma se si racchiude la query in una sottoquery, il limite sarebbe ancora efficiente o l'intera sottoquery verrà eseguita e quindi limitata nella query esterna. SELECT count(*) FROM (Select * from (SELECT 1 FROM token) query) LIMIT 500000) limited_query;(Lo chiedo perché sto cercando di ottenere un conteggio da una query arbitraria che potrebbe già contenere una clausola limite)
Nicholas Erdenberger

1
@NicholasErdenberger: dipende dalla sottoquery. Postgres potrebbe dover considerare più righe rispetto al limite comunque (come con ORDER BY somethingmentre non può usare un indice o con funzioni aggregate). A parte questo, viene elaborato solo il numero limitato di righe della sottoquery.
Erwin Brandstetter

12

L'ho fatto una volta in un'app postgres eseguendo:

EXPLAIN SELECT * FROM foo;

Quindi esaminando l'output con una regex o una logica simile. Per un semplice SELECT *, la prima riga di output dovrebbe essere simile a questa:

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

È possibile utilizzare il rows=(\d+)valore come stima approssimativa del numero di righe che verrebbero restituite, quindi eseguire l'effettivo solo SELECT COUNT(*)se la stima è, diciamo, inferiore a 1,5 volte la soglia (o qualsiasi numero ritenga abbia senso per la propria applicazione).

A seconda della complessità della tua query, questo numero potrebbe diventare sempre meno preciso. In effetti, nella mia applicazione, quando abbiamo aggiunto join e condizioni complesse, è diventato così impreciso che era completamente inutile, anche sapere come entro una potenza di 100 quante righe saremmo tornate, quindi abbiamo dovuto abbandonare quella strategia.

Ma se la tua query è abbastanza semplice da consentire a Pg di prevedere con un ragionevole margine di errore quante righe restituirà, potrebbe funzionare per te.


2

Riferimento tratto da questo Blog.

Puoi utilizzare di seguito per eseguire una query per trovare il conteggio delle righe.

Utilizzando pg_class:

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

Utilizzando pg_stat_user_tables:

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

Nota solo rapidamente che devi ANALIZZARE VUOTO le tue tabelle affinché questo metodo funzioni.
William Abma

1

In Oracle, è possibile utilizzare rownumper limitare il numero di righe restituite. Immagino che un costrutto simile esista anche in altri SQL. Quindi, per l'esempio che hai fornito, potresti limitare il numero di righe restituite a 500001 e applicare count(*)quindi:

SELECT (case when cnt > 500000 then 500000 else cnt end) myCnt
FROM (SELECT count(*) cnt FROM table WHERE rownum<=500001)

1
SELECT count (*) cnt FROM table restituirà sempre una singola riga. Non sono sicuro di come LIMIT aggiungerà alcun vantaggio lì.
Chris Bednarski

@ ChrisBednarski: ho verificato la versione Oracle della mia risposta su un database Oracle. Funziona alla grande e risolve quello che pensavo fosse il problema di OP (0,05 s count(*)con rownum, 1 s senza l'uso di rownum). Sì, SELECT count(*) cnt FROM tablerestituirà sempre 1 riga, ma con la condizione LIMIT, restituirà "500001" quando la dimensione della tabella è superiore a 500000 e <size> quando la dimensione della tabella <= 500000.
Ritesh

2
La tua query PostgreSQL è completamente priva di senso. Sintatticamente e logicamente sbagliato. Correggilo o rimuovilo.
Erwin Brandstetter

@ErwinBrandstetter: Rimosso, non mi rendevo conto che PostgreSQL era così diverso.
Ritesh

@allrite: senza dubbio la tua query Oracle funziona bene. LIMIT funziona in modo diverso però. A un livello base, limita il numero di righe restituite al client, non il numero di righe interrogate dal motore di database.
Chris Bednarski

0

Quanto è ampia la colonna di testo?

Con un GROUP BY non c'è molto che puoi fare per evitare una scansione dei dati (almeno una scansione dell'indice).

Io consiglierei:

  1. Se possibile, modificare lo schema per rimuovere la duplicazione dei dati di testo. In questo modo il conteggio avverrà su un campo ristretto di chiave esterna nella tabella "molti".

  2. In alternativa, creando una colonna generata con un HASH del testo, quindi GROUP BY la colonna hash. Ancora una volta, questo serve per ridurre il carico di lavoro (eseguire la scansione di un indice di colonna stretto)

Modificare:

La tua domanda originale non corrisponde esattamente alla tua modifica. Non sono sicuro che tu sappia che COUNT, se utilizzato con GROUP BY, restituirà il conteggio degli elementi per gruppo e non il conteggio degli elementi nell'intera tabella.


0

Puoi ottenere il conteggio dalla query seguente (senza * o alcun nome di colonna).

select from table_name;

2
Questo non sembra essere più veloce di count(*).
Soleggiato

-3

Per SQL Server (2005 o versioni successive) un metodo rapido e affidabile è:

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

I dettagli su sys.dm_db_partition_stats sono spiegati in MSDN

La query aggiunge righe da tutte le parti di una (possibilmente) tabella partizionata.

index_id = 0 è una tabella non ordinata (Heap) e index_id = 1 è una tabella ordinata (indice cluster)

Metodi ancora più veloci (ma inaffidabili) sono descritti qui.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.