La requête:
SELECT COUNT(*) as count_all,
posts.id as post_id
FROM posts
INNER JOIN votes ON votes.post_id = posts.id
GROUP BY posts.id;
Renvoie les enregistrements n
dans PostgreSQL:
count_all | post_id
-----------+---------
1 | 6
3 | 4
3 | 5
3 | 1
1 | 9
1 | 10
(6 rows)
Je veux juste récupérer le nombre d'enregistrements retournés: 6
.
J'ai utilisé une sous-requête pour obtenir ce que je veux, mais cela ne semble pas optimal:
SELECT COUNT(*) FROM (
SELECT COUNT(*) as count_all, posts.id as post_id
FROM posts
INNER JOIN votes ON votes.post_id = posts.id
GROUP BY posts.id
) as x;
Comment puis-je obtenir le nombre d'enregistrements dans ce contexte directement dans PostgreSQL?
Je pense que vous avez juste besoin de COUNT(DISTINCT post_id) FROM votes
.
Voir la section "4.2.7. Expressions agrégées" dans http://www.postgresql.org/docs/current/static/sql-expressions.html .
EDIT: Correction de mon erreur imprudente selon le commentaire d'Erwin.
Il y a aussi EXISTS
:
SELECT count(*) AS post_ct
FROM posts p
WHERE EXISTS (SELECT FROM votes v WHERE v.post_id = p.id);
Dans Postgres et avec plusieurs entrées du côté n - comme vous l'avez probablement, c'est généralement plus rapide que count(DISTINCT post_id)
:
SELECT count(DISTINCT p.id) AS post_ct
FROM posts p
JOIN votes v ON v.post_id = p.id;
Plus il y a de lignes par publication dans votes
, plus la différence de performances est grande. Testez avec EXPLAIN ANALYZE
.
count(DISTINCT post_id)
doit lire tous lignes, les trier ou les hacher, puis ne considérer que la première par ensemble identique. EXISTS
analysera uniquement votes
(ou, de préférence, un index sur post_id
) jusqu'à ce que la première correspondance soit trouvée.
Si chaque post_id
Dans votes
est garanti d'être présent dans la table posts
(intégrité référentielle appliquée avec une contrainte de clé étrangère), cette forme abrégée équivaut à la forme plus longue:
SELECT count(DISTINCT post_id) AS post_ct
FROM votes;
Peut être plus rapide que la requête EXISTS
avec pas ou peu d'entrées par article.
La requête que vous aviez fonctionne également sous une forme plus simple:
SELECT count(*) AS post_ct
FROM (
SELECT FROM posts
JOIN votes ON votes.post_id = posts.id
GROUP BY posts.id
) sub;
Pour vérifier mes réclamations, j'ai exécuté une référence sur mon serveur de test avec des ressources limitées. Le tout dans un schéma distinct:
Faux une situation typique de post/vote:
CREATE SCHEMA y;
SET search_path = y;
CREATE TABLE posts (
id int PRIMARY KEY
, post text
);
INSERT INTO posts
SELECT g, repeat(chr(g%100 + 32), (random()* 500)::int) -- random text
FROM generate_series(1,10000) g;
DELETE FROM posts WHERE random() > 0.9; -- create ~ 10 % dead tuples
CREATE TABLE votes (
vote_id serial PRIMARY KEY
, post_id int REFERENCES posts(id)
, up_down bool
);
INSERT INTO votes (post_id, up_down)
SELECT g.*
FROM (
SELECT ((random()* 21)^3)::int + 1111 AS post_id -- uneven distribution
, random()::int::bool AS up_down
FROM generate_series(1,70000)
) g
JOIN posts p ON p.id = g.post_id;
Toutes les requêtes suivantes ont renvoyé le même résultat (8093 des 9107 messages avaient des votes).
J'ai effectué 4 tests avec EXPLAIN ANALYZE
Et j'ai pris le meilleur de cinq sur Postgres 9.1.4 avec chacun des trois interroge et ajoute le temps d'exécution total résultant .
Comme si.
Après ..
ANALYZE posts;
ANALYZE votes;
Après ..
CREATE INDEX foo on votes(post_id);
Après ..
VACUUM FULL ANALYZE posts;
CLUSTER votes using foo;
count(*) ... WHERE EXISTS
count(DISTINCT x)
- forme longue avec jointurecount(DISTINCT x)
- forme courte sans jointureMeilleur moment pour requête originale en question:
Pour version simplifiée :
La requête de @ wildplasser avec un CTE utilise le même plan que le formulaire long (scan d'index sur les publications, scan d'index sur les votes, fusionner la jointure) plus un peu de surcharge pour le CTE. Meilleur temps:
--- (Les analyses indexées uniquement dans le prochain PostgreSQL 9.2 peuvent améliorer le résultat pour chacune de ces requêtes, surtout pour EXISTS
.
Benchmark connexe et plus détaillé pour Postgres 9.5 (en fait récupérer des lignes distinctes, pas seulement compter)
Utilisation de OVER()
et LIMIT 1
:
SELECT COUNT(1) OVER()
FROM posts
INNER JOIN votes ON votes.post_id = posts.id
GROUP BY posts.id
LIMIT 1;
WITH uniq AS (
SELECT DISTINCT posts.id as post_id
FROM posts
JOIN votes ON votes.post_id = posts.id
-- GROUP BY not needed anymore
-- GROUP BY posts.id
)
SELECT COUNT(*)
FROM uniq;