Dipende molto dalle circostanze e dai requisiti esatti. Considera il mio commento alla domanda .
Soluzione semplice
Con DISTINCT ON
in 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;
Risultato ordinato.
O con NOT EXISTS
SQL standard (funziona con ogni RDBMS che conosco):
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
);
Stesso risultato, ma con ordinamento arbitrario, a meno che non venga aggiunto ORDER BY
.
A seconda della distribuzione dei dati, dei requisiti e degli indici esatti, uno di questi può essere più veloce.
Generalmente, DISTINCT ON
è il vincitore e ottieni un risultato ordinato su di esso. Ma per alcuni casi altre tecniche di query sono (molto) più veloci, ancora. Vedi sotto.
Le soluzioni con sottoquery per calcolare i valori max / min sono generalmente più lente. Le varianti con CTE sono generalmente più lente, ancora.
Le viste semplici (come quelle proposte da un'altra risposta) non aiutano affatto le prestazioni in Postgres.
SQL Fiddle.
Soluzione corretta
Stringhe e regole di confronto
Prima di tutto, soffri di un layout di tabella non ottimale. Può sembrare banale, ma la normalizzazione del tuo schema può fare molto.
Ricerca per tipi di carattere ( text
, varchar
, ...) deve essere fatto in base alle impostazioni internazionali - il COLLATION in particolare. Molto probabilmente il tuo DB usa alcune regole locali (come nel mio caso:) de_AT.UTF-8
. Scoprilo con:
SHOW lc_collate;
Ciò rende più lenti l' ordinamento e l'indicizzazione delle ricerche . Più lunghe sono le stringhe (nomi dei prodotti), peggio. Se in realtà non ti interessano le regole di confronto nell'output (o l'ordinamento), questo può essere più veloce se aggiungi 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;
Nota come ho aggiunto le regole di confronto in due punti.
Due volte più veloce nel mio test con 20k righe ciascuno e nomi molto semplici ('good123').
Indice
Se si suppone che la query utilizzi un indice, le colonne con i dati dei caratteri devono utilizzare un confronto corrispondente ( good
nell'esempio):
CREATE INDEX inventory_good_date_desc_collate_c_idx
ON price(good COLLATE "C", the_date DESC);
Assicurati di leggere gli ultimi due capitoli di questa risposta correlata su SO:
Puoi anche avere più indici con regole di confronto diverse sulle stesse colonne, se hai anche bisogno di merci ordinate in base a un'altra (o impostazione predefinita) di confronto in altre query.
Normalizzare
Le stringhe ridondanti (nome del bene) gonfiano anche le tabelle e gli indici, il che rende tutto ancora più lento. Con un corretto layout della tabella potresti evitare la maggior parte del problema. Potrebbe apparire così:
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));
Le chiavi primarie forniscono automaticamente (quasi) tutti gli indici di cui abbiamo bisogno.
A seconda dei dettagli mancanti, un indice a più price
colonne attivo con ordine decrescente nella seconda colonna può migliorare le prestazioni:
CREATE INDEX price_good_date_desc_idx ON price(good, the_date DESC);
Ancora una volta, le regole di confronto devono corrispondere alla tua query (vedi sopra).
In Postgres 9.2 o versioni successive gli "indici di copertura" per le scansioni solo indice potrebbero essere di aiuto, soprattutto se le tue tabelle contengono colonne aggiuntive, rendendo la tabella sostanzialmente più grande dell'indice di copertura.
Queste query risultanti sono molto più veloci:
NON ESISTE
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.
Soluzioni più veloci
Se ciò non è ancora abbastanza veloce, potrebbero esserci soluzioni più veloci.
Sottoquery JOIN LATERAL
correlata CTE ricorsiva /
Soprattutto per le distribuzioni di dati con molti prezzi per bene :
Vista materializzata
Se devi eseguire questo spesso e velocemente, ti suggerisco di creare una vista materializzata. Penso che sia sicuro presumere che i prezzi e gli inventari per le date passate raramente cambino. Calcola il risultato una volta e archivia un'istantanea come vista materializzata.
Postgres 9.3+ ha un supporto automatizzato per viste materializzate. Puoi facilmente implementare una versione base nelle versioni precedenti.