Ele depende muito das circunstâncias e necessidades exatas. Considere o meu comentário para a pergunta .
Solução simples
Com DISTINCT ON
no Postgres:
SELECT DISTINCT ON (i.good, i.the_date)
i.the_date, p.the_date AS pricing_date, i.good, p.price
FROM inventory i
LEFT JOIN price p ON i.good = p.good AND i.the_date >= p.the_date
ORDER BY i.good, i.the_date, p.the_date DESC;
Resultado ordenado.
Ou NOT EXISTS
no SQL padrão (funciona com todos os RDBMS que eu conheço):
SELECT i.the_date, p.the_date AS pricing_date, i.good, i.quantity, p.price
FROM inventory i
LEFT JOIN price p ON p.good = i.good AND p.the_date <= i.the_date
WHERE NOT EXISTS (
SELECT 1 FROM price p1
WHERE p1.good = p.good
AND p1.the_date <= i.the_date
AND p1.the_date > p.the_date
);
Mesmo resultado, mas com ordem de classificação arbitrária - a menos que você adicione ORDER BY
.
Dependendo da distribuição dos dados, requisitos e índices exatos, qualquer um deles pode ser mais rápido.
Geralmente, DISTINCT ON
é o vencedor e você obtém um resultado classificado em cima dele. Mas, em certos casos, outras técnicas de consulta são (muito) mais rápidas ainda. Ver abaixo.
Soluções com subconsultas para calcular valores máx / min são geralmente mais lentas. As variantes com CTEs são geralmente mais lentas, ainda.
Vistas simples (como propostas por outra resposta) não ajudam no desempenho no Postgres.
SQL Fiddle.
Solução adequada
Cordas e agrupamento
Primeiro de tudo, você sofre de um layout de tabela abaixo do ideal. Pode parecer trivial, mas normalizar seu esquema pode percorrer um longo caminho.
A classificação por tipos de caracteres ( text
, varchar
...) tem que ser feito de acordo com o local - o COLLATION em particular. Provavelmente, seu banco de dados usa algum conjunto local de regras (como, no meu caso de_AT.UTF-8
:). Descubra com:
SHOW lc_collate;
Isso torna a classificação e as pesquisas de índice mais lentas . Quanto mais longas as cordas (nomes das mercadorias), pior. Se você realmente não se importa com as regras de agrupamento em sua saída (ou com a ordem de classificação), isso pode ser mais rápido se você adicionar COLLATE "C"
:
SELECT DISTINCT ON (i.good COLLATE "C", i.the_date)
i.the_date, p.the_date AS pricing_date, i.good, p.price
FROM inventory i
LEFT JOIN price p ON i.good = p.good AND i.the_date >= p.the_date
ORDER BY i.good COLLATE "C", i.the_date, p.the_date DESC;
Observe como adicionei o agrupamento em dois lugares.
Duas vezes mais rápido no meu teste, com 20 mil linhas cada e nomes muito básicos ('good123').
Índice
Se sua consulta deve usar um índice, as colunas com dados de caracteres precisam usar um agrupamento correspondente ( good
no exemplo):
CREATE INDEX inventory_good_date_desc_collate_c_idx
ON price(good COLLATE "C", the_date DESC);
Leia os dois últimos capítulos desta resposta relacionada no SO:
Você pode até ter vários índices com diferentes agrupamentos nas mesmas colunas - se também precisar de mercadorias classificadas de acordo com outro agrupamento (ou o padrão) em outras consultas.
Normalizar
Seqüências redundantes (nome de bom) também incham suas tabelas e índices, o que torna tudo ainda mais lento. Com um layout de tabela adequado, você pode evitar a maior parte do problema. Pode ficar assim:
CREATE TABLE good (
good_id serial PRIMARY KEY
, good text NOT NULL
);
CREATE TABLE inventory (
good_id int REFERENCES good (good_id)
, the_date date NOT NULL
, quantity int NOT NULL
, PRIMARY KEY(good_id, the_date)
);
CREATE TABLE price (
good_id int REFERENCES good (good_id)
, the_date date NOT NULL
, price numeric NOT NULL
, PRIMARY KEY(good_id, the_date));
As chaves primárias fornecem automaticamente (quase) todos os índices que precisamos.
Dependendo dos detalhes ausentes, um índice de price
várias colunas ativado com ordem decrescente na segunda coluna pode melhorar o desempenho:
CREATE INDEX price_good_date_desc_idx ON price(good, the_date DESC);
Novamente, o agrupamento deve corresponder à sua consulta (veja acima).
No Postgres 9.2 ou posterior, "índices de cobertura" para verificações apenas de índice poderia ajudar um pouco mais - especialmente se suas tabelas mantiverem colunas adicionais, tornando a tabela substancialmente maior que o índice de cobertura.
Essas consultas resultantes são muito mais rápidas:
NÃO EXISTE
SELECT i.the_date, p.the_date AS pricing_date, g.good, i.quantity, p.price
FROM inventory i
JOIN good g USING (good_id)
LEFT JOIN price p ON p.good_id = i.good_id AND p.the_date <= i.the_date
AND NOT EXISTS (
SELECT 1 FROM price p1
WHERE p1.good_id = p.good_id
AND p1.the_date <= i.the_date
AND p1.the_date > p.the_date
);
DISTINCT ON
SELECT DISTINCT ON (i.the_date)
i.the_date, p.the_date AS pricing_date, g.good, i.quantity, p.price
FROM inventory i
JOIN good g USING (good_id)
LEFT JOIN price p ON p.good_id = i.good_id AND p.the_date <= i.the_date
ORDER BY i.the_date, p.the_date DESC;
SQL Fiddle.
Soluções mais rápidas
Se isso ainda não for rápido o suficiente, pode haver soluções mais rápidas.
CTE recursiva / JOIN LATERAL
/ subconsulta correlacionada
Especialmente para distribuições de dados com muitos preços por bem :
Vista materializada
Se você precisar executar isso com frequência e rapidez, sugiro que você crie uma visualização materializada. Eu acho que é seguro assumir que preços e estoques para datas passadas raramente mudam. Calcule o resultado uma vez e armazene um instantâneo como vista materializada.
O Postgres 9.3+ tem suporte automatizado para visualizações materializadas. Você pode implementar facilmente uma versão básica em versões mais antigas.