如何有效地选择postgres中具有MIN日期的行

时间:2012-07-27 19:44:50

标签: sql postgresql query-optimization postgresql-9.1

我需要从按其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)

2 个答案:

答案 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