这在很大程度上取决于情况和确切的要求。考虑我对这个问题的评论。
简单的解决方案
随着DISTINCT ON
在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;
有序的结果。
或使用NOT EXISTS
标准SQL(适用于我知道的每个RDBMS):
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
);
结果相同,但排序顺序任意-除非添加ORDER BY
。
根据数据分布,确切的要求和索引,这两者之一可能会更快。
通常,DISTINCT ON
胜利者是您,您将获得排序结果。但是对于某些情况,其他查询技术却要快得多。见下文。
带有子查询以计算最大值/最小值的解决方案通常较慢。但是,带有CTE的变体通常较慢。
单纯的观点(如另一个答案所建议的那样)完全没有帮助Postgres表现。
SQL提琴。
正确的解决方案
字符串和排序规则
首先,您的表布局欠佳。这看似微不足道,但规范化架构可能会走很长一段路。
必须根据语言环境(尤其是COLLATION)对字符类型(text
,,varchar
...)进行排序。您的数据库很可能使用一些本地规则集(例如,在我的情况下:)。找出与:de_AT.UTF-8
SHOW lc_collate;
这会使排序和索引查找变慢。字符串(商品名称)越长,效果越差。如果您实际上并不关心输出中的排序规则(或根本不考虑排序顺序),那么添加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;
请注意我如何在两个地方添加排序规则。
在我的测试中,速度是以前的两倍,每行有2万行,并且具有非常基本的名称('good123')。
指数
如果您的查询应该使用索引,则包含字符数据的列必须使用匹配的排序规则(good
在示例中):
CREATE INDEX inventory_good_date_desc_collate_c_idx
ON price(good COLLATE "C", the_date DESC);
确保阅读有关SO的相关答案的最后两章:
您甚至可以在同一列上使用具有不同排序规则的多个索引-如果您还需要在其他查询中根据其他(或默认)排序规则对商品进行排序。
归一化
多余的字符串(好名字)也会使您的表和索引膨胀,从而使一切变慢。使用适当的表布局,您可以避免大多数问题。可能看起来像这样:
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));
主键自动(几乎)提供我们需要的所有索引。
根据缺少的细节,在第二列上按降序排列的多列索引price
可能会提高性能:
CREATE INDEX price_good_date_desc_idx ON price(good, the_date DESC);
同样,排序规则必须与您的查询匹配(请参见上文)。
在Postgres 9.2或更高版本中,仅索引扫描的“覆盖索引”可能会有所帮助-尤其是如果您的表包含更多列,使该表大大大于覆盖索引。
这些结果查询要快得多:
不存在
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
);
区别开
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提琴。
更快的解决方案
如果那还不够快,那么可能会有更快的解决方案。
递归CTE JOIN LATERAL
//相关子查询
特别是对于每件商品有很多价格的数据分发:
物化视图
如果您需要经常快速运行它,建议您创建一个实例化视图。我认为可以肯定地说,过去日期的价格和库存很少变化。计算一次结果,然后将快照存储为实例化视图。
Postgres 9.3+具有对物化视图的自动支持。您可以在旧版本中轻松实现基本版本。