Moyen rapide de découvrir le nombre de lignes d'une table dans PostgreSQL

108

J'ai besoin de connaître le nombre de lignes dans un tableau pour calculer un pourcentage. Si le nombre total est supérieur à une constante prédéfinie, j'utiliserai la valeur constante. Sinon, j'utiliserai le nombre réel de lignes.

Je peux utiliser SELECT count(*) FROM table. Mais si ma valeur constante est 500 000 et que j'ai 5 000 000 000 de lignes dans ma table, compter toutes les lignes perdra beaucoup de temps.

Est-il possible d'arrêter de compter dès que ma valeur constante est dépassée?

J'ai besoin du nombre exact de lignes uniquement tant qu'il est inférieur à la limite donnée. Sinon, si le nombre est supérieur à la limite, j'utilise la valeur limite à la place et je veux la réponse le plus rapidement possible.

Quelque chose comme ça:

SELECT text,count(*), percentual_calculus()  
FROM token  
GROUP BY text  
ORDER BY count DESC;
Renato Dinhani
la source
5
Ne pourriez-vous pas simplement essayer de sélectionner les n premières lignes où n = constant + 1 ? Si elle renvoie plus que votre constante, vous savez que vous devriez utiliser votre constante, et si ce n'est pas le cas, vous êtes bon?
gddc
Avez-vous une identité ou un champ d'incrémentation automatique dans la table
Sparky
1
@Sparky: les PK soutenus par séquence ne sont pas garantis d'être contigus, les lignes peuvent être supprimées ou il peut y avoir des lacunes causées par des transactions annulées.
mu est trop court
Votre mise à jour semble contredire votre question initiale ... avez-vous besoin de connaître le nombre exact de lignes, ou avez-vous seulement besoin de connaître le nombre exact s'il est inférieur à un seuil?
Flimzy
1
@ RenatoDinhaniConceição: Pouvez-vous expliquer le problème exact que vous essayez de résoudre? Je pense que ma réponse ci-dessous résout ce que vous avez initialement dit être votre problème. La mise à jour donne l'impression que vous voulez compter (*) ainsi que de nombreux autres champs. Il serait utile que vous puissiez expliquer exactement ce que vous essayez de faire. Merci.
Ritesh

Réponses:

226

Le comptage des lignes dans les grandes tables est connu pour être lent dans PostgreSQL. Pour obtenir un nombre précis, il doit effectuer un décompte complet des lignes en raison de la nature du MVCC . Il existe un moyen d' accélérer considérablement ce processus si le décompte ne doit pas être exact comme il semble l'être dans votre cas.

Au lieu d'obtenir le décompte exact ( lent avec de grandes tables):

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

Vous obtenez une estimation précise comme celle-ci ( extrêmement rapide ):

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

La précision de l'estimation dépend du fait que vous en exécutez ANALYZEsuffisamment. C'est généralement très proche.
Consultez la FAQ du wiki PostgreSQL .
Ou la page wiki dédiée pour les performances de count (*) .

Mieux encore

L'article paru dans le Wiki PostgreSQL est était un peu bâclé . Il a ignoré la possibilité qu'il puisse y avoir plusieurs tables du même nom dans une base de données - dans différents schémas. Pour en tenir compte:

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'

Ou mieux encore

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

Plus rapide, plus simple, plus sûr, plus élégant. Consultez le manuel sur les types d'identificateurs d'objets .

Utilisez to_regclass('myschema.mytable')dans Postgres 9.4+ pour éviter les exceptions pour les noms de table non valides:


TABLESAMPLE SYSTEM (n) dans Postgres 9.5+

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

Comme @a_horse l'a commenté , la clause nouvellement ajoutée pour la SELECTcommande peut être utile si les statistiques de pg_classne sont pas assez actuelles pour une raison quelconque. Par exemple:

  • Pas de autovacuumcourse.
  • Immédiatement après un gros INSERTou DELETE.
  • TEMPORARYtables (qui ne sont pas couvertes par autovacuum).

Cela ne regarde qu'une sélection aléatoire de n % ( 1dans l'exemple) de blocs et compte les lignes. Un plus grand échantillon augmente le coût et réduit l'erreur, votre choix. La précision dépend de plusieurs facteurs:

  • Répartition de la taille des lignes. Si un bloc donné contient des lignes plus larges que d'habitude, le nombre est inférieur à la normale, etc.
  • Tuples morts ou un FILLFACTOR espace occupé par bloc. Si elle est inégalement répartie sur le tableau, l'estimation peut être erronée.
  • Erreurs d'arrondi générales.

Dans la plupart des cas, l'estimation de pg_class sera plus rapide et plus précise.

Réponse à la question réelle

Tout d'abord, j'ai besoin de connaître le nombre de lignes dans cette table, si le nombre total est supérieur à une constante prédéfinie,

Et si c'est ...

... est possible au moment où le comptage passe ma valeur constante, cela arrêtera le comptage (et n'attendra pas de terminer le comptage pour informer que le nombre de lignes est supérieur).

Oui. Vous pouvez utiliser une sousLIMIT - requête avec :

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

Postgres arrête réellement de compter au-delà de la limite donnée, vous obtenez un exact et actuel pour jusqu'à n lignes (500000 dans l'exemple), et n sinon. Pas aussi rapide que l'estimation pg_class, cependant.

Erwin Brandstetter
la source
8
J'ai finalement mis à jour la page Postgres Wiki avec la requête améliorée.
Erwin Brandstetter
5
Avec 9.5, obtenir une estimation rapide devrait être possible en utilisant la tablesampleclause: par exempleselect count(*) * 100 as cnt from mytable tablesample system (1);
a_horse_with_no_name
1
@JeffWidman: Toutes ces estimations peuvent être supérieures au nombre réel de lignes pour diverses raisons. Notamment, des suppressions peuvent s'être produites entre-temps.
Erwin Brandstetter
2
@ErwinBrandstetter réalise que cette question est ancienne, mais si vous enveloppiez la requête dans une sous-requête, la limite serait-elle toujours efficace ou la sous-requête entière serait-elle exécutée puis limitée dans la requête externe. SELECT count(*) FROM (Select * from (SELECT 1 FROM token) query) LIMIT 500000) limited_query;(Je demande parce que j'essaie d'obtenir un décompte à partir d'une requête arbitraire qui pourrait déjà
contenir
1
@NicholasErdenberger: Cela dépend de la sous-requête. Postgres peut de toute façon avoir besoin de considérer plus de lignes que la limite (comme avec ORDER BY somethingalors qu'il ne peut pas utiliser d'index, ou avec des fonctions d'agrégation). En dehors de cela, seul le nombre limité de lignes de la sous-requête est traité.
Erwin Brandstetter
12

Je l'ai fait une fois dans une application postgres en exécutant:

EXPLAIN SELECT * FROM foo;

Examinez ensuite la sortie avec une expression régulière ou une logique similaire. Pour un simple SELECT *, la première ligne de sortie devrait ressembler à ceci:

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

Vous pouvez utiliser la rows=(\d+)valeur comme une estimation approximative du nombre de lignes qui seraient renvoyées, puis ne faire le réel que SELECT COUNT(*)si l'estimation est, par exemple, inférieure à 1,5 fois votre seuil (ou tout autre nombre que vous jugez pertinent pour votre application).

Selon la complexité de votre requête, ce nombre peut devenir de moins en moins précis. En fait, dans mon application, au fur et à mesure que nous ajoutions des jointures et des conditions complexes, cela devenait si inexact qu'il était complètement inutile, même de savoir comment, dans une puissance de 100, le nombre de lignes que nous avions renvoyées, nous avons donc dû abandonner cette stratégie.

Mais si votre requête est suffisamment simple pour que Pg puisse prédire dans une marge d'erreur raisonnable le nombre de lignes qu'elle renverra, cela peut fonctionner pour vous.

Flimzy
la source
2

Référence tirée de ce blog.

Vous pouvez utiliser ci-dessous pour rechercher le nombre de lignes.

Utilisation de pg_class:

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

Utilisation de pg_stat_user_tables:

SELECT 
    schemaname
    ,relname
    ,n_live_tup AS EstimatedCount 
FROM pg_stat_user_tables 
ORDER BY n_live_tup DESC;
Anvesh
la source
Notez simplement que vous devez VACUUM ANALYZE vos tables pour que cette méthode fonctionne.
William Abma
1

Dans Oracle, vous pouvez utiliser rownumpour limiter le nombre de lignes renvoyées. Je suppose qu'une construction similaire existe également dans d'autres SQL. Ainsi, pour l'exemple que vous avez donné, vous pouvez limiter le nombre de lignes renvoyées à 500001 et appliquer count(*)alors:

SELECT (case when cnt > 500000 then 500000 else cnt end) myCnt
FROM (SELECT count(*) cnt FROM table WHERE rownum<=500001)
Ritesh
la source
1
SELECT count (*) cnt FROM table retournera toujours une seule ligne. Je ne sais pas comment LIMIT va y ajouter des avantages.
Chris Bednarski
@ChrisBednarski: J'ai vérifié la version oracle de ma réponse sur une base de données Oracle. Cela fonctionne très bien et résout ce que je pensais être le problème d'OP (0,05 s count(*)avec rownum, 1 s sans l'utilisation de rownum). Oui, SELECT count(*) cnt FROM tableil retournera toujours 1 ligne, mais avec la condition LIMIT, il retournera "500001" lorsque la taille de la table est supérieure à 500000 et <taille> lorsque la taille de la table <= 500000.
Ritesh
2
Votre requête PostgreSQL est complètement absurde. Syntaxiquement et logiquement faux. Veuillez le corriger ou le supprimer.
Erwin Brandstetter
@ErwinBrandstetter: Supprimé, je ne savais pas que PostgreSQL était si différent.
Ritesh
@allrite: il ne fait aucun doute que votre requête Oracle fonctionne correctement. LIMIT fonctionne différemment cependant. Au niveau de base, il limite le nombre de lignes renvoyées au client, et non le nombre de lignes interrogées par le moteur de base de données.
Chris Bednarski
0

Quelle est la largeur de la colonne de texte?

Avec un GROUP BY, vous ne pouvez pas faire grand chose pour éviter une analyse de données (au moins une analyse d'index).

Je recommande:

  1. Si possible, modifiez le schéma pour supprimer la duplication des données texte. De cette façon, le décompte se produira sur un champ de clé étrangère étroite dans la table «plusieurs».

  2. Vous pouvez également créer une colonne générée avec un HASH du texte, puis GROUP BY la colonne de hachage. Encore une fois, cela permet de réduire la charge de travail (parcourir un index de colonne étroit)

Éditer:

Votre question d'origine ne correspondait pas tout à fait à votre modification. Je ne sais pas si vous savez que COUNT, lorsqu'il est utilisé avec un GROUP BY, renverra le nombre d'éléments par groupe et non le nombre d'éléments dans l'ensemble du tableau.

Chris Bednarski
la source
0

Vous pouvez obtenir le nombre par la requête ci-dessous (sans * ni aucun nom de colonne).

select from table_name;
SuperNova
la source
2
Cela ne semble pas être plus rapide que count(*).
Ensoleillé le
-3

Pour SQL Server (2005 ou supérieur), une solution rapide et fiable méthode est:

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

Les détails sur sys.dm_db_partition_stats sont expliqués dans MSDN

La requête ajoute des lignes de toutes les parties d'une table (éventuellement) partitionnée.

index_id = 0 est une table non ordonnée (Heap) et index_id = 1 est une table ordonnée (index clusterisé)

Des méthodes encore plus rapides (mais peu fiables) sont détaillées ici.

DrKoch
la source