我需要从按其baz
分组的“最早”(MIN(save_date)
)行中快速选择一个值(foo_id
)。以下查询返回正确的行(当存在重复的save_dates时,它几乎可以返回每个foo_id的倍数。)
foos
表包含大约55k行,samples
表包含大约2500万行。
CREATE TABLE foos (
foo_id int,
val varchar(40),
# ref_id is a FK, constraint omitted for brevity
ref_id int
)
CREATE TABLE samples (
sample_id int,
save_date date,
baz smallint,
# foo_id is a FK, constraint omitted for brevity
foo_id int
)
WITH foo ( foo_id, val ) AS (
SELECT foo_id, val FROM foos
WHERE foos.ref_id = 1
ORDER BY foos.val ASC
LIMIT 25 OFFSET 0
)
SELECT foo.val, firsts.baz
FROM foo
LEFT JOIN (
SELECT A.baz, A.foo_id
FROM samples A
INNER JOIN (
SELECT foo_id, MIN( save_date ) AS save_date
FROM samples
GROUP BY foo_id
) B
USING ( foo_id, save_date )
) firsts USING ( foo_id )
此查询目前耗时超过100秒;我希望看到这个回归大约1秒钟(或更少!)。
如何将此查询编写为最佳?
explains
:显然我正在使用的实际查询不使用表foo,baz等。
“dumbed down”示例查询(来自上方)explain
:
Hash Right Join (cost=337.69..635.47 rows=3 width=100)
Hash Cond: (a.foo_id = foo.foo_id)
CTE foo
-> Limit (cost=71.52..71.53 rows=3 width=102)
-> Sort (cost=71.52..71.53 rows=3 width=102)
Sort Key: foos.val
-> Seq Scan on foos (cost=0.00..71.50 rows=3 width=102)
Filter: (ref_id = 1)
-> Hash Join (cost=265.25..562.90 rows=9 width=6)
Hash Cond: ((a.foo_id = samples.foo_id) AND (a.save_date = (min(samples.save_date))))
-> Seq Scan on samples a (cost=0.00..195.00 rows=1850 width=10)
-> Hash (cost=244.25..244.25 rows=200 width=8)
-> HashAggregate (cost=204.25..224.25 rows=200 width=8)
-> Seq Scan on samples (cost=0.00..195.00 rows=1850 width=8)
-> Hash (cost=0.60..0.60 rows=3 width=102)
-> CTE Scan on foo (cost=0.00..0.60 rows=3 width=102)
答案 0 :(得分:3)
如果我理解这个问题,你想要开窗。
WITH find_first AS (
SELECT foo_id, baz,
row_number()
OVER (PARTITION BY foo_id ORDER BY foo_id, save_date) AS rnum
FROM samples
)
SELECT foo_id, baz FROM find_first WHERE rnum = 1;
使用row_number
代替rank
可以消除重复,并保证每个foo只有一个baz。如果你需要知道没有bazz的foos,只需LEFT JOIN
这个查询的foos表。
对于(foo_id, save_date)
的索引,优化器应该足够聪明,只需要保留一个baz并快速跳过。
答案 1 :(得分:2)
row_number()
是一只美丽的野兽,但DISTINCT ON
在这里更简单。
WITH foo AS (
SELECT foo_id
FROM foos
WHERE ref_id = 1
ORDER BY val
LIMIT 25 OFFSET 0
)
SELECT DISTINCT ON (1) f.foo_id, s.baz
FROM foo f
LEFT JOIN samples s USING (foo_id)
ORDER BY f.foo_id, s.save_date, s.baz;
这假设您需要每foo_id
一行。如果sample
中有多个行共享同一个最早的save_date
,则baz
会成为决胜局。
该案例与this question from yesterday非常相似。
请勿在CTE中选择val
,只需在ORDER BY
中使用。{/ p>
在foos
上避免昂贵的顺序扫描:
如果您总是在使用foos
的{{1}}行之后,请创建partial multi-column index:
ref_id = 1
如果CREATE INDEX foos_val_part_idx ON foos (val)
WHERE ref_id = 1;
是变量:
ref_id
另一个最适合CREATE INDEX foos_ref_id_val_idx ON foos (ref_id, val);
的索引:
samples
使用9.2版中的新“仅索引扫描”,这些索引变得更加有效。详情和链接here。