Optimizar la consulta máxima grupal

select * 
from records 
where id in ( select max(id) from records group by option_id )

Esta consulta funciona bien incluso en millones de filas. Sin embargo, como puede ver en el resultado de la declaración de explicación:

                                               QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------------------------
Nested Loop  (cost=30218.84..31781.62 rows=620158 width=44) (actual time=1439.251..1443.458 rows=1057 loops=1)
->  HashAggregate  (cost=30218.41..30220.41 rows=200 width=4) (actual time=1439.203..1439.503 rows=1057 loops=1)
     ->  HashAggregate  (cost=30196.72..30206.36 rows=964 width=8) (actual time=1438.523..1438.807 rows=1057 loops=1)
           ->  Seq Scan on records records_1  (cost=0.00..23995.15 rows=1240315 width=8) (actual time=0.103..527.914 rows=1240315 loops=1)
->  Index Scan using records_pkey on records  (cost=0.43..7.80 rows=1 width=44) (actual time=0.002..0.003 rows=1 loops=1057)
     Index Cond: (id = (max(records_1.id)))
Total runtime: 1443.752 ms

(cost=0.00..23995.15 rows=1240315 width=8) <- Aquí dice que está escaneando todas las filas y eso obviamente es ineficiente.

También intenté reordenar la consulta:

select r.* from records r
inner join (select max(id) id from records group by option_id) r2 on r2.id= r.id;

                                               QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------------

Nested Loop  (cost=30197.15..37741.04 rows=964 width=44) (actual time=835.519..840.452 rows=1057 loops=1)
->  HashAggregate  (cost=30196.72..30206.36 rows=964 width=8) (actual time=835.471..835.836 rows=1057 loops=1)
     ->  Seq Scan on records  (cost=0.00..23995.15 rows=1240315 width=8) (actual time=0.336..348.495 rows=1240315 loops=1)
->  Index Scan using records_pkey on records r  (cost=0.43..7.80 rows=1 width=44) (actual time=0.003..0.003 rows=1 loops=1057)
     Index Cond: (id = (max(records.id)))
Total runtime: 840.809 ms

(cost=0.00..23995.15 rows=1240315 width=8) <- Todavía escaneando todas las filas.

Traté con y sin índice en(option_id), (option_id, id), (option_id, id desc), ninguno de ellos tuvo ningún efecto en el plan de consulta.

¿Hay alguna manera de ejecutar una consulta máxima grupal en Postgres sin escanear todas las filas?

Lo que estoy buscando, programáticamente, es un índice que almacene la identificación máxima para cadaoption_id a medida que se insertan en la tabla de registros. De esa forma, cuando consulto los máximos de option_ids, solo debería escanear registros de índice tantas veces como haya diferentes option_ids.

He vistoselect distinct on respuestas en todo SO de usuarios de alto rango (gracias a @Clodoaldo Neto por darme palabras clave para buscar). He aquí por qué no funciona:

create index index_name on records(option_id, id desc)

select distinct on (option_id) *
from records
order by option_id, id desc
                                               QUERY PLAN
------------------------------------------------------------------------------------------------------------------------------------------------------------
Unique  (cost=0.43..76053.10 rows=964 width=44) (actual time=0.049..1668.545 rows=1056 loops=1)
  ->  Index Scan using records_option_id_id_idx on records  (cost=0.43..73337.25 rows=1086342 width=44) (actual time=0.046..1368.300 rows=1086342 loops=1)
Total runtime: 1668.817 ms

Eso es genial, está usando un índice. Sin embargo, usar un índice para escanear todos los identificadores no tiene mucho sentido. Según mis ejecuciones, de hecho es más lento que un simple escaneo secuencial.

Lo suficientemente interesante, MySQL 5.5 es capaz de optimizar la consulta simplemente usando un índice enrecords(option_id, id)

mysql> select count(1) from records;

+----------+
| count(1) |
+----------+
|  1086342 |
+----------+

1 row in set (0.00 sec)

mysql> explain extended select * from records
       inner join ( select max(id) max_id from records group by option_id ) mr
                                                      on mr.max_id= records.id;

+------+----------+--------------------------+
| rows | filtered | Extra                    |
+------+----------+--------------------------+
| 1056 |   100.00 |                          |
|    1 |   100.00 |                          |
|  201 |   100.00 | Using index for group-by |
+------+----------+--------------------------+

3 rows in set, 1 warning (0.02 sec)

Respuestas a la pregunta(4)

Su respuesta a la pregunta