Bu , şartlara ve kesin gereksinimlere bağlıdır . Bu soruya yorumumu düşünün .
Basit çözüm
DISTINCT ON
Postgres ile birlikte :
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;
Sipariş edilen sonuç.
Veya NOT EXISTS
standart SQL'de (bildiğim her RDBMS ile çalışıyor):
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
);
Aynı sonuç, ancak rasgele sıralama düzeni - eğer sürece ORDER BY
.
Veri dağılımına, kesin gerekliliklere ve endekslere bağlı olarak, bunlardan biri daha hızlı olabilir.
Genel DISTINCT ON
olarak, zafer kazanan kişidir ve bunun üzerine düzenli bir sonuç alırsınız. Ancak bazı durumlar için diğer sorgu teknikleri henüz çok daha hızlıdır. Aşağıya bakınız.
Maks / dak değerlerini hesaplamak için alt sorgular içeren çözümler genellikle daha yavaştır. CTE'li varyantlar genellikle daha yavaştır.
Sade görüşler (başka bir cevabın önerdiği gibi) Postgres'teki performansta hiç yardımcı olmuyor.
SQL Fiddle.
Uygun çözüm
Dizeler ve harmanlama
Her şeyden önce, alt-optimal bir masa düzeninden muzdaripsiniz. Önemsiz görünebilir, ancak şemanızı normalleştirmek çok uzun bir sürebilir.
Karakter türlerinetext
varchar
göre sıralama ( ,, ...) yerel ayara göre yapılmalıdır - özellikle COLLATION . Büyük olasılıkla DB'niz bazı yerel kurallar kullanıyor (benim durumumda olduğu gibi:) de_AT.UTF-8
. Şununla öğrenin:
SHOW lc_collate;
Bu sıralama ve dizin aramaları yavaşlatır . Dizeleriniz (mal isimleri) ne kadar uzun olursa o kadar kötü olur. Çıktınızdaki harmanlama kurallarını (veya sıralama düzenini) gerçekten önemsemiyorsanız, şunu eklerseniz daha hızlı olabilir 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;
Harmanlamayı iki yerde nasıl eklediğime dikkat edin.
Testimde iki kat hızlı ve 20k satırlık her biri ve çok basit isimler ('iyi123').
indeks
Sorgunuzun bir dizin kullanması gerekiyorsa, karakter verileri içeren sütunların eşleşen bir harmanlama kullanması gerekir ( good
örnekte):
CREATE INDEX inventory_good_date_desc_collate_c_idx
ON price(good COLLATE "C", the_date DESC);
SO ile ilgili bu cevabın son iki bölümünü okuduğunuzdan emin olun:
Aynı sütunlarda farklı harmanlamalara sahip birden fazla dizin bile oluşturabilirsiniz - ayrıca diğer sorgularda başka bir (veya varsayılan) harmanlamaya göre sıralanan mallara ihtiyacınız varsa.
normalleştirmek
Yedekli dizeler de (iyi adı) tablolarınızı ve dizinlerinizi şişirir ve bu da her şeyi daha da yavaşlatır. Uygun bir masa düzeni ile başlamak için sorunun çoğunu önleyebilirsiniz. Bu gibi görünebilir:
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));
Birincil anahtarlar otomatik olarak ihtiyacımız olan tüm indeksleri (neredeyse) sağlar.
Eksik ayrıntılar üzerinde bir bağlı çok sütun dizini üzerinde price
performansını artırabilir ikinci sütun üzerinde azalan ile:
CREATE INDEX price_good_date_desc_idx ON price(good, the_date DESC);
Yine, harmanlama sorgunuzla eşleşmelidir (yukarıya bakın).
Postgres 9.2 veya sonraki sürümlerde, yalnızca indeksli taramalar için "örtü endeksleri" biraz yardımcı olabilir - özellikle tablolarınız ek sütunlara sahipse, tabloyu örtü indeksinden önemli ölçüde büyük yapar.
Bu ortaya çıkan sorgular çok daha hızlı:
VAR DEĞİL
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.
Daha hızlı çözümler
Bu hala yeterince hızlı değilse, daha hızlı çözümler olabilir.
Özyinelemeli CTE / JOIN LATERAL
/ korelasyonlu alt sorgu
Özellikle mal başına birçok fiyat içeren veri dağıtımları için :
Materyalleştirilmiş görünüm
Bunu sık ve hızlı bir şekilde çalıştırmanız gerekirse, materyalize bir görünüm yaratmanızı öneririm. Geçmiş tarihler için fiyat ve stokların nadiren değiştiğini varsaymanın güvenli olduğunu düşünüyorum. Sonucu bir kez hesaplayın ve anlık görüntüsü materyalize görünüm olarak kaydedin.
Postgres 9.3+, materyalize görünümler için otomatik desteğe sahiptir. Eski sürümlerde basit bir sürümü kolayca uygulayabilirsiniz.