3

bazによってグループ化された「最も早い」( MIN(save_date))行から値()をすばやく選択する必要がありますfoo_id。次のクエリは、正しい行を返します (ほとんどの場合、save_dates が重複している場合、各 foo_id の倍数を返すことができます)。

foosテーブルには約 55,000 行が含まれ、テーブルsamplesには約 2,500 万行が含まれます。

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ごとに1つのbazのみが保証されます。LEFT JOINバズのないfooに対して知る必要がある場合は、このクエリのfoosテーブルだけを使用してください。

インデックスをオン(foo_id, save_date)にすると、オプティマイザーは、1つのバズだけを保持し、楽しくスキップしてグループ化を行うのに十分なほど賢くなります。

于 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;

これは、 ごとに正確に 1 行が必要であると仮定していますfoo_id。同じ最も古いをsample共有する複数の行がある場合、タイ ブレーカーとして機能します。 save_datebaz

このケースは、昨日のこの質問と非常によく似ています。

より多くのアドバイス:

  • CTE で選択しないでください。valでのみ必要ですORDER BY

  • でのコストのかかる順次スキャンを回避するにはfoos:

    • fooswithの行を常に追跡する場合は、部分的な複数列インデックス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 に答える