Reputation: 98
Please, help to optimize my query:
select id, paid_till, rating, paid
from lots
left join (select 1 paid) paid on current_timestamp <= lots.paid_till
order by paid asc, rating desc, updated_at desc;
And query plan:
Sort (cost=1948.17..1948.18 rows=4 width=28) (actual time=0.703..0.704 rows=4 loops=1)
Sort Key: (1), lots.rating DESC, lots.updated_at DESC
Sort Method: quicksort Memory: 25kB
-> Nested Loop Left Join (cost=0.00..1948.13 rows=4 width=28) (actual time=0.014..0.682 rows=4 loops=1)
Join Filter: (now() <= lots.paid_till)
Rows Removed by Join Filter: 2
-> Seq Scan on lots (cost=0.00..1948.04 rows=4 width=24) (actual time=0.008..0.675 rows=4 loops=1)
-> Materialize (cost=0.00..0.03 rows=1 width=4) (actual time=0.001..0.001 rows=1 loops=4)
-> Result (cost=0.00..0.01 rows=1 width=0) (actual time=0.000..0.000 rows=1 loops=1)
Planning time: 0.210 ms
Execution time: 0.724 ms
What indices I should add? How can I fix "nested loop left join"?
P.S. I can't use virtual column in select for ordering, cause Rails issues.
Upvotes: 0
Views: 335
Reputation: 94914
That's a strange way to get the paid
value. Use a CASE
expression instead:
select
id,
paid_till,
rating,
case when current_timestamp <= paid_till then 1 else 0 end as paid
from lots
order by paid asc, rating desc, updated_at desc;
This proabbly won't speed up the query, though. There is no WHERE
clause, so the full table must be read. The only way I see to get this faster, would be a covering index:
create index idx_quick on lots
(
case when current_timestamp <= paid_till then 1 else 0 end,
rating,
updated_at desc,
paid_till,
id)
;
But it seems overkill to have an index tailor made for this query.
Upvotes: 2
Reputation:
To get rid of the left join use:
select id, paid_till, rating, paid_till is null as paid
from lots
where current_timestamp <= lots.paid_till
or paid_till is null
order by 4 asc, rating desc, updated_at desc;
Not sure what you mean with "I can't use virtual column in select for ordering, cause Rails issues" but if your obfuscation layer can't sort on an expression, you could do:
select *
from (
select id, paid_till, rating, paid_till is null as paid
from lots
where current_timestamp <= lots.paid_till
or paid_till is null
) t
order by paid asc, rating desc, updated_at desc;
Or to simply sort all unpaid rows at the end, without using the paid
expression at all:
select id, paid_till, rating
from lots
where current_timestamp <= lots.paid_till
or paid_till is null
order by paid_till asc nulls last, rating desc, updated_at desc;
All of the above will get rid of the "Nested Loop Left Join" step. But I really doubt that that is your problem - even on production. The "Seq Scan on lots" for a big table is going to have much more impact.
If the condition current_timestamp <= lots.paid_till
only returns a small fraction of that table, putting an index on the paid_till
column might help. It would help even more if you can get rid of the or paid_till is null
condition. This could be achieved by storing 'infinity'
instead of null
for those rows that have not yet paid. Without null
values in that column the condition can be reduced to where current_timestamp <= lots.paid_till
which will make use of an index if that is cheaper then a Seq Scan.
To understand when it makes sense for the optimizer to use an index might you want to read this or this
Upvotes: 1