Référence
Test de la plupart des candidats intéressants avec Postgres 9.4 et 9.5 avec une table à mi - chemin réaliste de 200K lignes dans purchases
et 10k distinctscustomer_id
( moy. 20 lignes par client ).
Pour Postgres 9.5, j'ai effectué un deuxième test auprès de 86446 clients distincts. Voir ci-dessous ( moyenne de 2,3 lignes par client ).
Installer
Table principale
CREATE TABLE purchases (
id serial
, customer_id int -- REFERENCES customer
, total int -- could be amount of money in Cent
, some_column text -- to make the row bigger, more realistic
);
J'utilise une serial
(contrainte PK ajoutée ci-dessous) et un entier customer_id
car c'est une configuration plus typique. Également ajouté some_column
pour compenser généralement plus de colonnes.
Données factices, PK, index - une table typique a également quelques tuples morts:
INSERT INTO purchases (customer_id, total, some_column) -- insert 200k rows
SELECT (random() * 10000)::int AS customer_id -- 10k customers
, (random() * random() * 100000)::int AS total
, 'note: ' || repeat('x', (random()^2 * random() * random() * 500)::int)
FROM generate_series(1,200000) g;
ALTER TABLE purchases ADD CONSTRAINT purchases_id_pkey PRIMARY KEY (id);
DELETE FROM purchases WHERE random() > 0.9; -- some dead rows
INSERT INTO purchases (customer_id, total, some_column)
SELECT (random() * 10000)::int AS customer_id -- 10k customers
, (random() * random() * 100000)::int AS total
, 'note: ' || repeat('x', (random()^2 * random() * random() * 500)::int)
FROM generate_series(1,20000) g; -- add 20k to make it ~ 200k
CREATE INDEX purchases_3c_idx ON purchases (customer_id, total DESC, id);
VACUUM ANALYZE purchases;
customer
table - pour une requête supérieure
CREATE TABLE customer AS
SELECT customer_id, 'customer_' || customer_id AS customer
FROM purchases
GROUP BY 1
ORDER BY 1;
ALTER TABLE customer ADD CONSTRAINT customer_customer_id_pkey PRIMARY KEY (customer_id);
VACUUM ANALYZE customer;
Dans mon deuxième test pour 9.5, j'ai utilisé la même configuration, mais avec random() * 100000
pour générer customer_id
pour obtenir seulement quelques lignes par customer_id
.
Tailles des objets pour la table purchases
Généré avec cette requête .
what | bytes/ct | bytes_pretty | bytes_per_row
-----------------------------------+----------+--------------+---------------
core_relation_size | 20496384 | 20 MB | 102
visibility_map | 0 | 0 bytes | 0
free_space_map | 24576 | 24 kB | 0
table_size_incl_toast | 20529152 | 20 MB | 102
indexes_size | 10977280 | 10 MB | 54
total_size_incl_toast_and_indexes | 31506432 | 30 MB | 157
live_rows_in_text_representation | 13729802 | 13 MB | 68
------------------------------ | | |
row_count | 200045 | |
live_tuples | 200045 | |
dead_tuples | 19955 | |
Requêtes
WITH cte AS (
SELECT id, customer_id, total
, row_number() OVER(PARTITION BY customer_id ORDER BY total DESC) AS rn
FROM purchases
)
SELECT id, customer_id, total
FROM cte
WHERE rn = 1;
2. row_number()
en sous-requête (mon optimisation)
SELECT id, customer_id, total
FROM (
SELECT id, customer_id, total
, row_number() OVER(PARTITION BY customer_id ORDER BY total DESC) AS rn
FROM purchases
) sub
WHERE rn = 1;
SELECT DISTINCT ON (customer_id)
id, customer_id, total
FROM purchases
ORDER BY customer_id, total DESC, id;
4. rCTE avec LATERAL
sous-requête ( voir ici )
WITH RECURSIVE cte AS (
( -- parentheses required
SELECT id, customer_id, total
FROM purchases
ORDER BY customer_id, total DESC
LIMIT 1
)
UNION ALL
SELECT u.*
FROM cte c
, LATERAL (
SELECT id, customer_id, total
FROM purchases
WHERE customer_id > c.customer_id -- lateral reference
ORDER BY customer_id, total DESC
LIMIT 1
) u
)
SELECT id, customer_id, total
FROM cte
ORDER BY customer_id;
5. customer
table avec LATERAL
( voir ici )
SELECT l.*
FROM customer c
, LATERAL (
SELECT id, customer_id, total
FROM purchases
WHERE customer_id = c.customer_id -- lateral reference
ORDER BY total DESC
LIMIT 1
) l;
SELECT (array_agg(id ORDER BY total DESC))[1] AS id
, customer_id
, max(total) AS total
FROM purchases
GROUP BY customer_id;
Résultats
Temps d'exécution pour les requêtes ci-dessus avec EXPLAIN ANALYZE
(et toutes les options désactivées ), le meilleur des 5 exécutions .
Toutes les requêtes ont utilisé un index analyse uniquement sur purchases2_3c_idx
(entre autres étapes). Certains d'entre eux uniquement pour la plus petite taille de l'indice, d'autres plus efficacement.
A. Postgres 9.4 avec 200k lignes et ~ 20 par customer_id
1. 273.274 ms
2. 194.572 ms
3. 111.067 ms
4. 92.922 ms
5. 37.679 ms -- winner
6. 189.495 ms
B. La même chose avec Postgres 9.5
1. 288.006 ms
2. 223.032 ms
3. 107.074 ms
4. 78.032 ms
5. 33.944 ms -- winner
6. 211.540 ms
C. Identique à B., mais avec environ 2,3 lignes par customer_id
1. 381.573 ms
2. 311.976 ms
3. 124.074 ms -- winner
4. 710.631 ms
5. 311.976 ms
6. 421.679 ms
Repères associés
En voici un nouveau par des tests «ogr» avec 10 millions de lignes et 60 000 «clients» uniques sur Postgres 11.5 (en date de septembre 2019). Les résultats sont toujours en ligne avec ce que nous avons vu jusqu'à présent:
Référence originale (obsolète) de 2011
J'ai exécuté trois tests avec PostgreSQL 9.1 sur une table réelle de 65579 lignes et des index btree à une colonne sur chacune des trois colonnes impliquées et j'ai pris le meilleur temps d'exécution de 5 exécutions.
Comparaison de la première requête de @OMGPonies ( A
) à la solution ciDISTINCT ON
- dessus ( B
):
Sélectionnez la table entière, ce qui donne 5958 lignes dans ce cas.
A: 567.218 ms
B: 386.673 ms
Condition d'utilisation WHERE customer BETWEEN x AND y
résultant en 1 000 lignes.
A: 249.136 ms
B: 55.111 ms
Sélectionnez un seul client avec WHERE customer = x
.
A: 0.143 ms
B: 0.072 ms
Même test répété avec l'index décrit dans l'autre réponse
CREATE INDEX purchases_3c_idx ON purchases (customer, total DESC, id);
1A: 277.953 ms
1B: 193.547 ms
2A: 249.796 ms -- special index not used
2B: 28.679 ms
3A: 0.120 ms
3B: 0.048 ms
MAX(total)
?