私はPostgres 9.5でこのクエリからさらにパフォーマンスを絞り込もうとしています。 400,000行以上実行しています。
試してみると、CASE
ステートメントがクエリコストにかなりの量を追加していることに気づきました。既存の列を単純に合計するだけで置き換えると、実行時間が半分になります。これらの合計を計算するより効率的な方法はありますか?
SELECT sum("tag1"), sum("tag2"), sum("total_tags")
FROM (
SELECT people.data->'recruiter_id' AS recruiter_id,
(CASE WHEN people.data->'tags' ? 'tag1' THEN 1 END) AS "tag1",
(CASE WHEN people.data->'tags' ? 'tag2' THEN 1 END) AS "tag2",
((CASE WHEN people.data->'tags' ? 'tag1' THEN 1 ELSE 0 END) +
(CASE WHEN people.data->'tags' ? 'tag2' THEN 1 ELSE 0 END)) AS total_tags
FROM people WHERE people.data->'tags' ?| ARRAY['tag1','tag2'] ) AS target
GROUP BY recruiter_id
EXPLAIN ANALYSE
の出力:
HashAggregate (cost=1076.30..1078.22 rows=550 width=202) (actual time=7043.115..7043.208 rows=449 loops=1)
Group Key: (people.data -> 'recruiter_id'::text)
-> Bitmap Heap Scan on people (cost=12.85..1072.72 rows=550 width=202) (actual time=13.908..2619.878 rows=48492 loops=1)
Recheck Cond: ((data -> 'tags'::text) ?| '{tag1,tag2}'::text[])
Heap Blocks: exact=26114
-> Bitmap Index Scan on index_people_on_data_tags (cost=0.00..12.82 rows=550 width=0) (actual time=9.219..9.219 rows=48493 loops=1)
Index Cond: ((data -> 'tags'::text) ?| '{tag1,tag2}'::text[])
Planning time: 0.139 ms
Execution time: 7043.291 ms
実行中:
Gccでコンパイルされたx86_64-pc-linux-gnu上のPostgreSQL 9.5.5(Ubuntu 4.8.2-19ubuntu1)4.8.2、64ビット
内部クエリと外部クエリは、アプリケーションの別々の部分によって生成されます。再構築せずに最適化することは可能ですか?
これは全体的に高速でシンプルになるはずです。
SELECT *, tag1 + tag2 AS total_tags
FROM (
SELECT (data->>'recruiter_id')::int AS recruiter_id -- cheaper to group by int
, count(*) FILTER (WHERE data->'tags' ? 'tag1') AS tag1
, count(*) FILTER (WHERE data->'tags' ? 'tag2') AS tag2
FROM people
WHERE data->'tags' ?| ARRAY['tag1','tag2']
GROUP BY 1
) target;
recruiter_id
が整数エンティティであると仮定すると、jsonb
オブジェクトを含むanよりも整数値でグループ化する方が安価です。 integer値。私はまた、とにかく結果の整数値を取得したいと思います。
サブクエリで1回だけカウントしてから、外側のSELECT
に合計のカウントを追加します。
条件付きカウントには集約FILTER
句を使用します。
より短い構文が必要な場合は、これにより同じ結果とパフォーマンスが得られます。
count(data->'tags' ? 'tag1' OR NULL) AS tag1
jsonb
のインデックスと不足している統計通常、indexesは、大きなテーブルでのパフォーマンスを決定する要因です。しかし、クエリは400.000行のうち48.493行、つまり> 12%を取得するため、インデックスはこのクエリをまったく助けません。
なぜ悪い決断なのか?クエリプランナーには値がない値の内部a json
/jsonb
オブジェクトを使用し、一般的な選択性推定に基づいて最適なクエリプランを選択する必要があります。 rows = 550
が見つかるはずですが、クエリでは実際に〜90 x倍(rows=48493
)。ビットマップインデックススキャンを使用するクエリプランは、適切な決定ではありません。順次スキャンの方が高速です(インデックスをまったく使用しません)。
インデックスは、頻度が低いタグ(タグがある場合)に役立ちますが、data->'tags'
の式インデックスが最適です。たぶんjsonb_path_ops
インデックスと、適応されたクエリとの組み合わせです。もっと:
ただし、この理由やその他の理由により、一般的なタグを使用している場合、プレーンなPostgres配列または完全に正規化されたスキーマは、jsonb
オブジェクトのパフォーマンスを大幅に上回ります。
Postgresql-performanceリストでのこの議論は、あなたの問題について正確にです:
試す
SELECT count("tag1"), count("tag2"), count("tag1")+count("tag2")
FROM (
SELECT people.data->'recruiter_id' AS recruiter_id,
nullif(people.data->'tags' ? 'tag1',false) AS "tag1",
nullif(people.data->'tags' ? 'tag2',false) AS "tag2"
FROM people
) AS target
GROUP BY recruiter_id
HAVING count("tag1")+count("tag2") > 0
@Dudu Markowitzの答えは正しいと思いますが、クエリを再構成する方法が限られている場合は、少なくとも次のようなことができます。
SELECT sum(tag1) AS sum_tag1, sum(tag2) AS sum_tag2,
/* Take out the sum("total_tags") */
sum(tag1) + sum(tag2) AS sum_total_tags
FROM (
SELECT people.data->'recruiter_id' AS recruiter_id,
(CASE WHEN people.data->'tags' ? 'tag1' THEN 1 END) AS "tag1",
(CASE WHEN people.data->'tags' ? 'tag2' THEN 1 END) AS "tag2",
/* You save one CASE with two "->" */
FROM people
WHERE people.data->'tags' ?| ARRAY['tag1','tag2']
) AS target
GROUP BY recruiter_id