Home > front end >  PostgreSQL query optimization challenge
PostgreSQL query optimization challenge

Time:05-01

I am trying to optimize this query:

SELECT eq.*,
    reg_last_dt.dt as reg_last_date
FROM Equipment eq
  INNER JOIN (
    select max( dt ) as dt, id_eq_equipment 
    from consum 
    group by id_eq_equipment
  ) as reg_last_dt ON reg_last_dt.id_eq_equipment = eq.id_eq

Explain shows me this:

Hash Join  (cost=839806.69..839833.33 rows=23 width=1461)
  Hash Cond: (eq.id_eq = consum.id_eq_equipment)
  ->  Seq Scan on equipment eq  (cost=0.00..26.29 rows=129 width=1453)
  ->  Hash  (cost=839806.40..839806.40 rows=23 width=10)
        ->  Finalize GroupAggregate  (cost=839805.60..839806.17 rows=23 width=10)
              Group Key: consum.id_eq_equipment
              ->  Sort  (cost=839805.60..839805.71 rows=46 width=10)
                    Sort Key: consum.id_eq_equipment
                    ->  Gather  (cost=839799.50..839804.33 rows=46 width=10)
                          Workers Planned: 2
                          ->  Partial HashAggregate  (cost=838799.50..838799.73 rows=23 width=10)
                                Group Key: consum.id_eq_equipment
                                ->  Parallel Seq Scan on consum  (cost=0.00..755192.33 rows=16721433 width=10)

This looks not very optimal. Is there anything I could do to make it better?

CodePudding user response:

The row estimates in the query plan (only rows=129 for Equipment, and only rows=23 for aggregated consum) indicate that this query using a LATERAL subquery instead should perform much faster:

SELECT eq.*, r.reg_last_date
FROM   Equipment eq
CROSS  JOIN LATERAL (
   SELECT max(dt) AS reg_last_date
   FROM   consum c
   WHERE  c.id_eq_equipment = eq.id_eq
   ) r;

Be sure to have a multicolumn index on consum(id_eq_equipment, dt)!

Related:

Maybe your really want a LEFT JOIN to return all rows from Equipment? See:

CodePudding user response:

If the estimates in the plan are correct, it would almost certainly be faster to do it with a subselect, this way:

SELECT 
    eq.*, 
    (select max( dt ) from consum where consum.id_eq_equipment = eq.id_eq) as reg_last_date
FROM Equipment eq

Note this will return NULL for reg_last_date where there is no corresponding record in consum, so you might want to filter those out if you don't want to see them.

You would need an index on (id_eq_equipment, dt) to make it fast

  • Related