Szybki sposób na sprawdzenie liczby wierszy tabeli w PostgreSQL

110

Aby obliczyć procent, muszę znać liczbę wierszy w tabeli. Jeśli całkowita liczba jest większa niż pewna predefiniowana stała, użyję stałej wartości. W przeciwnym razie użyję rzeczywistej liczby wierszy.

Mogę użyć SELECT count(*) FROM table. Ale jeśli moja stała wartość to 500 000, a w tabeli mam 5 000 000 000 wierszy, liczenie wszystkich wierszy spowoduje stratę czasu.

Czy można przerwać liczenie, gdy tylko moja stała wartość zostanie przekroczona?

Potrzebuję dokładnej liczby wierszy tylko wtedy, gdy jest poniżej podanego limitu. W przeciwnym razie, jeśli liczba jest powyżej limitu, zamiast tego używam wartości granicznej i chcę odpowiedzi jak najszybciej.

Coś takiego:

SELECT text,count(*), percentual_calculus()  
FROM token  
GROUP BY text  
ORDER BY count DESC;
Renato Dinhani
źródło
5
Czy nie mógłbyś po prostu spróbować zaznaczyć pierwszych n wierszy, gdzie n = stała + 1 ? Jeśli zwraca więcej niż twoja stała, wiesz, że powinieneś użyć swojej stałej, a jeśli nie, jesteś dobry?
gddc
Czy masz w tabeli pole tożsamości lub automatycznego
wzrostu
1
@Sparky: nie ma gwarancji, że PK wspierane przez sekwencję będą ciągłe, wiersze mogą zostać usunięte lub mogą występować luki spowodowane przerwanymi transakcjami.
mu jest za krótkie
Twoja aktualizacja wydaje się być sprzeczna z pierwotnym pytaniem… czy musisz znać dokładną liczbę wierszy, czy też musisz znać dokładną liczbę tylko wtedy, gdy jest poniżej progu?
Flimzy
1
@ RenatoDinhaniConceição: Czy możesz wyjaśnić dokładny problem, który próbujesz rozwiązać? Myślę, że moja poniższa odpowiedź rozwiązuje Twój problem. Aktualizacja sprawia, że ​​chcesz policzyć (*), a także wiele innych pól. Byłoby pomocne, gdybyś mógł dokładnie wyjaśnić, co próbujesz zrobić. Dzięki.
Ritesh

Odpowiedzi:

229

Zliczanie wierszy w dużych tabelach jest znane w PostgreSQL jako powolne. Aby uzyskać dokładną liczbę, musi wykonać pełne zliczenie wierszy ze względu na charakter MVCC . Istnieje sposób, aby radykalnie to przyspieszyć, jeśli liczba nie musi być dokładna, jak się wydaje w twoim przypadku.

Zamiast uzyskiwać dokładną liczbę ( powolne przy dużych stołach):

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

Otrzymujesz przybliżone oszacowanie w ten sposób ( niezwykle szybko ):

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

To, jak dokładne jest oszacowanie, zależy od tego, czy biegasz ANALYZEwystarczająco. Zwykle jest bardzo blisko.
Zobacz FAQ Wiki PostgreSQL .
Lub dedykowana strona wiki dla wydajności count (*) .

Jeszcze lepiej

W artykule w PostgreSQL Wiki jest był nieco niechlujny . Zignorował możliwość, że może istnieć wiele tabel o tej samej nazwie w jednej bazie danych - w różnych schematach. Aby to uwzględnić:

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'

Albo jeszcze lepiej

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

Szybszy, prostszy, bezpieczniejszy, bardziej elegancki. Zobacz podręcznik dotyczący typów identyfikatorów obiektów .

Użyj to_regclass('myschema.mytable')w Postgres 9.4+, aby uniknąć wyjątków dla nieprawidłowych nazw tabel:


TABLESAMPLE SYSTEM (n) w Postgres 9.5+

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

Podobnie jak w komentarzu @a_horse , nowo dodana klauzula SELECTpolecenia może być przydatna, jeśli statystyki w programie pg_classnie są z jakiegoś powodu wystarczająco aktualne. Na przykład:

  • Żadnego autovacuumbiegania.
  • Natychmiast po dużym INSERTlub DELETE.
  • TEMPORARYstoły (które nie są objęte autovacuum).

To patrzy tylko na losowy n % ( 1w przykładzie) wybór bloków i liczy wiersze w nim. Większa próbka zwiększa koszt i zmniejsza błąd, Twój wybór. Dokładność zależy od wielu czynników:

  • Rozkład rozmiaru wiersza. Jeśli zdarzy się, że dany blok ma szersze niż zwykle rzędy, liczba jest mniejsza niż zwykle itp.
  • Martwe krotki lub FILLFACTORzajmują miejsce na blok. Jeśli rozkład jest nierównomierny w tabeli, oszacowanie może być nieprawidłowe.
  • Ogólne błędy zaokrąglania.

W większości przypadków oszacowanie z pg_classbędzie szybsze i dokładniejsze.

Odpowiedz na aktualne pytanie

Najpierw muszę znać liczbę wierszy w tej tabeli, jeśli całkowita liczba jest większa niż pewna wstępnie zdefiniowana stała,

I czy to ...

... jest możliwe w momencie, gdy licznik przekroczy moją stałą wartość, zatrzyma zliczanie (i nie będzie czekać na zakończenie liczenia, aby poinformować, że liczba wierszy jest większa).

Tak. Możesz użyć podzapytania zLIMIT :

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

Postgres faktycznie przestaje liczyć poza podany limit, otrzymasz dokładną i aktualną liczbę dla maksymalnie n wierszy (w przykładzie 500000) i n w przeciwnym razie. pg_classJednak nie tak szybko, jak szacowano .

Erwin Brandstetter
źródło
8
Ostatecznie zaktualizowałem stronę Wiki Postgres o ulepszone zapytanie.
Erwin Brandstetter
5
Przy wersji 9.5 szybkie oszacowanie powinno być możliwe przy użyciu tablesampleklauzuli: np.select count(*) * 100 as cnt from mytable tablesample system (1);
a_horse_with_no_name
1
@JeffWidman: Wszystkie te szacunki mogą być większe niż rzeczywista liczba wierszy z różnych powodów. Co więcej, w międzyczasie mogły nastąpić usunięcia.
Erwin Brandstetter
2
@ErwinBrandstetter zdajemy sobie sprawę, że to pytanie jest stare, ale jeśli opakowałeś zapytanie w podzapytanie, to czy limit będzie nadal skuteczny, czy też całe podzapytanie zostanie wykonane i ograniczone w zapytaniu zewnętrznym. SELECT count(*) FROM (Select * from (SELECT 1 FROM token) query) LIMIT 500000) limited_query;(Pytam, ponieważ próbuję uzyskać liczbę z dowolnego zapytania, które może już zawierać klauzulę limitu)
Nicholas Erdenberger
1
@NicholasErdenberger: To zależy od podzapytania. Postgres może i tak musieć wziąć pod uwagę więcej wierszy niż limit (na przykład w przypadku, ORDER BY somethinggdy nie może używać indeksu lub funkcji agregujących). Poza tym przetwarzana jest tylko ograniczona liczba wierszy z podzapytania.
Erwin Brandstetter
12

Zrobiłem to raz w aplikacji postgres, uruchamiając:

EXPLAIN SELECT * FROM foo;

Następnie zbadaj dane wyjściowe za pomocą wyrażenia regularnego lub podobnej logiki. W przypadku prostego polecenia SELECT * pierwsza linia wyniku powinna wyglądać mniej więcej tak:

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

Możesz użyć tej rows=(\d+)wartości jako przybliżonego oszacowania liczby wierszy, które zostaną zwrócone, a następnie wykonaj rzeczywistą wartość tylko SELECT COUNT(*)wtedy, gdy oszacowanie jest, powiedzmy, mniejsze niż 1,5-krotność progu (lub dowolnej liczby, którą uznasz za sensowną dla Twojej aplikacji).

W zależności od złożoności zapytania liczba ta może być coraz mniej dokładna. W rzeczywistości w mojej aplikacji, gdy dodaliśmy łączenia i złożone warunki, stało się to tak niedokładne, że było całkowicie bezwartościowe, nawet wiedzieć, jak w ramach potęgi 100, ile wierszy byśmy zwrócili, więc musieliśmy porzucić tę strategię.

Ale jeśli zapytanie jest na tyle proste, że Pg może przewidzieć, z pewnym rozsądnym marginesem błędu, ile wierszy zwróci, może to zadziałać.

Flimzy
źródło
2

Odniesienie zaczerpnięte z tego bloga.

Możesz użyć poniżej, aby wyszukać liczbę wierszy.

Korzystanie z pg_class:

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

Korzystanie z pg_stat_user_tables:

SELECT 
    schemaname
    ,relname
    ,n_live_tup AS EstimatedCount 
FROM pg_stat_user_tables 
ORDER BY n_live_tup DESC;
Anvesh
źródło
Zwróć uwagę, że aby ta metoda zadziałała, musisz przeprowadzić ANALIZĘ PRÓŻNIOWĄ swoje stoły.
William Abma
1

W Oracle można użyć rownumdo ograniczenia liczby zwracanych wierszy. Domyślam się, że podobna konstrukcja istnieje również w innych SQL. Tak więc w podanym przykładzie możesz ograniczyć liczbę zwracanych wierszy do 500001 i zastosować a count(*)następnie:

SELECT (case when cnt > 500000 then 500000 else cnt end) myCnt
FROM (SELECT count(*) cnt FROM table WHERE rownum<=500001)
Ritesh
źródło
1
SELECT count (*) cnt FROM table zawsze zwróci pojedynczy wiersz. Nie jestem pewien, w jaki sposób LIMIT doda tam jakąkolwiek korzyść.
Chris Bednarski
@ChrisBednarski: Sprawdziłem wersję mojej odpowiedzi w wyroczni na bazie danych Oracle. Działa świetnie i rozwiązuje to, co uważałem za problem OP (0,05 s count(*)z rownum, 1 s bez użycia rownum). Tak, SELECT count(*) cnt FROM tablezawsze zwraca 1 wiersz, ale z warunkiem LIMIT zwraca „500001”, gdy rozmiar tabeli przekracza 500000 i <size>, gdy rozmiar tabeli wynosi <= 500000.
Ritesh
2
Twoje zapytanie PostgreSQL jest kompletnym nonsensem. Błędne składniowo i logicznie. Popraw go lub usuń.
Erwin Brandstetter
@ErwinBrandstetter: Usunięto, nie zdawałem sobie sprawy, że PostgreSQL jest tak inny.
Ritesh
@allrite: bez wątpienia Twoje zapytanie Oracle działa dobrze. LIMIT działa jednak inaczej. Na poziomie podstawowym ogranicza liczbę wierszy zwracanych do klienta, a nie liczbę wierszy odpytywanych przez silnik bazy danych.
Chris Bednarski
0

Jak szeroka jest kolumna tekstu?

Z GROUP BY niewiele można zrobić, aby uniknąć skanowania danych (przynajmniej skanowania indeksu).

Polecam:

  1. Jeśli to możliwe, zmiana schematu w celu usunięcia duplikatów danych tekstowych. W ten sposób zliczanie będzie się odbywać na wąskim polu klucza obcego w tabeli „wiele”.

  2. Możesz też utworzyć wygenerowaną kolumnę z HASH tekstu, a następnie GROUP BY kolumnę z krzyżykiem. Ponownie, ma to na celu zmniejszenie obciążenia pracą (przejrzyj indeks wąskiej kolumny)

Edytować:

Twoje pierwotne pytanie nie do końca pasowało do Twojej zmiany. Nie jestem pewien, czy wiesz, że funkcja COUNT, gdy jest używana z funkcją GROUP BY, zwróci liczbę elementów na grupę, a nie liczbę elementów w całej tabeli.

Chris Bednarski
źródło
0

Możesz uzyskać liczbę za pomocą poniższego zapytania (bez * lub jakichkolwiek nazw kolumn).

select from table_name;
SuperNova
źródło
2
To nie wydaje się być szybsze niż count(*).
Słoneczny
-3

W przypadku SQL Server (2005 lub nowszy) szybką i niezawodną metodą jest:

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

Szczegóły dotyczące sys.dm_db_partition_stats są wyjaśnione w witrynie MSDN

Zapytanie dodaje wiersze ze wszystkich części (prawdopodobnie) podzielonej na partycje tabeli.

index_id = 0 to nieuporządkowana tabela (Heap), a index_id = 1 to uporządkowana tabela (indeks klastrowy)

Jeszcze szybsze (ale zawodne) metody są szczegółowo opisane tutaj.

DrKoch
źródło