Compte tenu du tableau:
Column | Type
id | integer
latitude | numeric(9,6)
longitude | numeric(9,6)
speed | integer
equipment_id | integer
created_at | timestamp without time zone
Indexes:
"geoposition_records_pkey" PRIMARY KEY, btree (id)
Le tableau compte 20 millions d'enregistrements ce qui n'est pas, relativement parlant, un grand nombre. Mais cela ralentit les analyses séquentielles.
Comment puis-je obtenir le dernier enregistrement (max(created_at)
) de chaque equipment_id
?
J'ai essayé les deux requêtes suivantes, avec plusieurs variantes que j'ai lues à travers de nombreuses réponses de ce sujet:
select max(created_at),equipment_id from geoposition_records group by equipment_id;
select distinct on (equipment_id) equipment_id,created_at
from geoposition_records order by equipment_id, created_at desc;
J'ai également essayé de créer des index btree pour equipment_id,created_at
Mais Postgres trouve que l'utilisation d'un seqscan est plus rapide. Forcer enable_seqscan = off
Ne sert à rien non plus car la lecture de l'index est aussi lente que le scan séquentiel, probablement pire.
La requête doit être exécutée périodiquement en renvoyant toujours la dernière.
Utiliser Postgres 9.3.
Expliquez/analysez (avec 1,7 million d'enregistrements):
set enable_seqscan=true;
explain analyze select max(created_at),equipment_id from geoposition_records group by equipment_id;
"HashAggregate (cost=47803.77..47804.34 rows=57 width=12) (actual time=1935.536..1935.556 rows=58 loops=1)"
" -> Seq Scan on geoposition_records (cost=0.00..39544.51 rows=1651851 width=12) (actual time=0.029..494.296 rows=1651851 loops=1)"
"Total runtime: 1935.632 ms"
set enable_seqscan=false;
explain analyze select max(created_at),equipment_id from geoposition_records group by equipment_id;
"GroupAggregate (cost=0.00..2995933.57 rows=57 width=12) (actual time=222.034..11305.073 rows=58 loops=1)"
" -> Index Scan using geoposition_records_equipment_id_created_at_idx on geoposition_records (cost=0.00..2987673.75 rows=1651851 width=12) (actual time=0.062..10248.703 rows=1651851 loops=1)"
"Total runtime: 11305.161 ms"
Un index b-tree multicolonne simple devrait fonctionner après tout:
CREATE INDEX foo_idx
ON geoposition_records (equipment_id, created_at DESC NULLS LAST);
Pourquoi DESC NULLS LAST
?
Si vous ne parvenez pas à comprendre le sens dans le planificateur de requêtes, une fonction faisant une boucle dans la table d'équipement devrait faire l'affaire. La recherche d'un ID d'équipement à la fois utilise l'index. Pour un petit nombre (57 à en juger par votre sortie EXPLAIN ANALYZE
), C'est rapide.
Il est sûr de supposer que vous avez une table equipment
?
CREATE OR REPLACE FUNCTION f_latest_equip()
RETURNS TABLE (equipment_id int, latest timestamp) AS
$func$
BEGIN
FOR equipment_id IN
SELECT e.equipment_id FROM equipment e ORDER BY 1
LOOP
SELECT g.created_at
FROM geoposition_records g
WHERE g.equipment_id = f_latest_equip.equipment_id
-- prepend function name to disambiguate
ORDER BY g.created_at DESC NULLS LAST
LIMIT 1
INTO latest;
RETURN NEXT;
END LOOP;
END
$func$ LANGUAGE plpgsql STABLE;
Fait pour un appel Nice aussi:
SELECT * FROM f_latest_equip();
À bien y penser, en utilisant cette table equipment
, vous pourriez faire le sale boulot avec des sous-requêtes faiblement corrélées:
SELECT equipment_id
,(SELECT created_at
FROM geoposition_records
WHERE equipment_id = eq.equipment_id
ORDER BY created_at DESC NULLS LAST
LIMIT 1) AS latest
FROM equipment eq;
Les performances sont très bonnes.
LATERAL
rejoignez Postgres 9.3+SELECT eq.equipment_id, r.latest
FROM equipment eq
LEFT JOIN LATERAL (
SELECT created_at
FROM geoposition_records
WHERE equipment_id = eq.equipment_id
ORDER BY created_at DESC NULLS LAST
LIMIT 1
) r(latest) ON true;
Explication détaillée:
Performances similaires à la sous-requête corrélée. Comparaison des performances de max()
, DISTINCT ON
, Fonction, sous-requête corrélée et LATERAL
dans ceci:
Tentative 1
Si
equipment
distincte, etgeoposition_records(equipment_id, created_at desc)
alors ce qui suit fonctionne pour moi:
select id as equipment_id, (select max(created_at)
from geoposition_records
where equipment_id = equipment.id
) as max_created_at
from equipment;
Je n'ai pas été en mesure de forcer PG à effectuer une requête rapide pour déterminer les deux la liste des equipment_id
S et la max(created_at)
associée. Mais je vais réessayer demain!
Tentative 2
J'ai trouvé ce lien: http://zogovic.com/post/44856908222/optimizing-postgresql-query-for-distinct-values En combinant cette technique avec ma requête de la tentative 1, j'obtiens:
WITH RECURSIVE equipment(id) AS (
SELECT MIN(equipment_id) FROM geoposition_records
UNION
SELECT (
SELECT equipment_id
FROM geoposition_records
WHERE equipment_id > equipment.id
ORDER BY equipment_id
LIMIT 1
)
FROM equipment WHERE id IS NOT NULL
)
SELECT id AS equipment_id, (SELECT MAX(created_at)
FROM geoposition_records
WHERE equipment_id = equipment.id
) AS max_created_at
FROM equipment;
et cela fonctionne RAPIDEMENT! Mais tu as besoin
geoposition_records(equipment_id, created_at desc)
.