Importamos taxas de câmbio para DB:
CREATE TABLE currency_rate (
id int8 NOT NULL,
date date NOT NULL,
currency varchar(3) NOT NULL,
rate numeric(12,6) NOT NULL,
CONSTRAINT currency_rate_pk PRIMARY KEY (id)
);
ALTER TABLE currency_rate add constraint currency_rate_un UNIQUE (currency, date);
mas, na verdade, precisamos apenas da última taxa disponível para trabalhar.
É complicado escrever CTE com sort e distinct on (currency)
:
with cr as (
select distinct on (currency) currency, rate from currency_rate
order by currency, date)
select
...,
sum((nd.original_amount - nd.new_amount)*cr.rate) as amount
from notification_data nd
join cr on cr.currency = nd.currency
...
A consulta tem o seguinte plano de execução que é bom :
CTE cr
-> Result (cost=0.28..69.66 rows=13 width=16)
-> Unique (cost=0.28..69.66 rows=13 width=16)
-> Index Scan using currency_rate_un on currency_rate (cost=0.28..67.17 rows=995 width=16)
...
-> Hash Join (cost=1029.26..57129.68 rows=18 width=60)
Hash Cond: ((nd.currency)::text = (cr.currency)::text)
Eu criei a visualização:
CREATE OR REPLACE VIEW latest_currency_rate AS
SELECT
DISTINCT ON (currency) currency, rate, date
FROM currency_rate
ORDER BY currency, date DESC;
mas o otimizador de banco de dados não usa o índice de currency_rate_un
:
explain select * from latest_currency_rate;
Unique (cost=60.83..65.38 rows=12 width=16)
-> Sort (cost=60.83..63.10 rows=910 width=16)
Sort Key: currency_rate.currency, currency_rate.date DESC
-> Seq Scan on currency_rate (cost=0.00..16.10 rows=910 width=16)
e até para:
explain select * from latest_currency_rate where currency = 'USD';
Unique (cost=16.87..17.13 rows=12 width=16)
-> Sort (cost=16.87..17.13 rows=104 width=16)
Sort Key: currency_rate.date DESC
-> Bitmap Heap Scan on currency_rate (cost=5.08..13.38 rows=104 width=16)
Recheck Cond: ((currency)::text = 'USD'::text)
-> Bitmap Index Scan on currency_rate_un (cost=0.00..5.06 rows=104 width=0)
Index Cond: ((currency)::text = 'USD'::text)
A integração da nova visualização à consulta original fornece:
explain select
sum((nd.original_amount - nd.new_amount)*cr.rate) as amount
from notification_data nd
join latest_currency_rate cr on cr.currency = nd.currency
...
...
-> Hash (cost=73.54..73.54 rows=13 width=12)
-> Subquery Scan on cr (cost=68.37..73.54 rows=13 width=12)
-> Unique (cost=68.37..73.41 rows=13 width=16)
-> Sort (cost=68.37..70.89 rows=1008 width=16)
Sort Key: currency_rate.currency, currency_rate.date DESC
-> Seq Scan on currency_rate (cost=0.00..18.08 rows=1008 width=16)
...
Agora estou intrigado. Por que a consulta CTE original usa Index Scan
e a exibição não usa o mesmo índice?
Devo reescrever a visão com algum truque alternativo (em vez de distinct on
)?
Estou pensando em ir com materialized view
para evitar varreduras sequenciais ...
Sua visão dá a resposta correta, enquanto seu CTE dá a resposta errada, usando a data mais antiga, não a mais recente.
Se você quiser usar uma verificação de índice (embora nas minhas mãos isso não faça diferença no desempenho), especifique
DESC
para ambas as colunas ORDER BY ou crie um índice para(currency, date DESC)
.