3

我需要从由它们分组baz的“最早”()行中快速选择一个值()。以下查询返回正确的行(几乎可以,当存在重复的 save_dates 时,它可以为每个 foo_id 返回倍数)。MIN(save_date)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 等。

“简化”示例查询(从上面)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)
4

2 回答 2

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。如果您需要了解没有 bazzes 的 foos,只需LEFT JOIN该查询的 foos 表即可。

使用索引 on (foo_id, save_date),优化器应该足够聪明,可以只保留一个 baz 并愉快地进行分组。

于 2012-07-27T21:01:00.553 回答
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_datebaz则作为决胜局。

这个案子和昨天的这个问题很相似。

更多建议:

  • 不要val在 CTE 中选择,您只需要在ORDER BY.

  • 为了避免昂贵的顺序扫描foos

    • 如果您总是在 with 中的行之后foos,请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

    CREATE INDEX samples_foo_id_save_date_baz_idx
    ON samples (foo_id, save_date, baz);
    

这些索引在 9.2 版中使用新的“仅索引扫描”变得更加有效。详细信息和链接在这里

于 2012-07-28T00:40:12.983 回答