私はpostgres 9.4を使用しています。
messages
のスキーマは次のとおりです。メッセージはfeed_idに属し、posted_atを持っています。また、メッセージには親メッセージを含めることができます(返信の場合)。
Table "public.messages"
Column | Type | Modifiers
------------------------------+-----------------------------+-----------
message_id | character varying(255) | not null
feed_id | integer |
parent_id | character varying(255) |
posted_at | timestamp without time zone |
share_count | integer |
Indexes:
"messages_pkey" PRIMARY KEY, btree (message_id)
"index_messages_on_feed_id_posted_at" btree (feed_id, posted_at DESC NULLS LAST)
share_count
で順序付けられたすべてのメッセージを返したいのですが、parent_id
ごとに1つのメッセージだけを返したいです。つまり、複数のメッセージが同じparent_id
の場合、最新のメッセージ(posted_at
)のみが返されます。 parent_id
はnullにすることができ、null parent_id
のメッセージはすべて返されます。
私が使用したクエリは次のとおりです。
WITH filtered_messages AS (SELECT *
FROM messages
WHERE feed_id IN (7)
AND (posted_at >= '2015-01-01 04:00:00.000000')
AND (posted_at < '2015-04-28 04:00:00.000000'))
SELECT *
FROM (SELECT DISTINCT ON(COALESCE(parent_id, message_id)) parent_id,
message_id,
posted_at,
share_count
FROM filtered_messages
ORDER BY COALESCE(parent_id, message_id), posted_at DESC NULLS LAST
) messages
ORDER BY share_count DESC NULLS LAST, posted_at DESC NULLS LAST;
これが http://sqlfiddle.com/#!15/588e5/1/ です。SQLFiddleで、スキーマ、正確なクエリ、および期待される結果を定義しました。
ただし、メッセージテーブルが大きくなると、クエリのパフォーマンスが低下します。複数のソートインデックスを追加しようとしましたが、インデックスを使用していないようです。これが説明です: http://explain.depesz.com/s/Sv2
正しいインデックスを作成するにはどうすればよいですか?
このクエリは、どの場合でもかなり高速になるはずです。
SELECT parent_id, message_id, posted_at, share_count
FROM messages
WHERE feed_id = 7
AND posted_at >= '2015-01-01 4:0:0'
AND posted_at < '2015-04-28 4:0:0'
AND parent_id IS NULL -- match index condition
UNION ALL
(
SELECT DISTINCT ON(parent_id)
parent_id, message_id, posted_at, share_count
FROM messages
WHERE feed_id = 7
AND posted_at >= '2015-01-01 4:0:0'
AND posted_at < '2015-04-28 4:0:0'
AND parent_id IS NOT NULL -- match index condition
ORDER BY parent_id, posted_at DESC NULLS LAST
)
ORDER BY share_count DESC NULLS LAST, posted_at DESC NULLS LAST;
ここでは、CTEは、単純なサブクエリでも配信できなかったことは何もしません。また、CTEは個別に実行され、その結果が具体化されるため、最適化バリアが導入されます。
実際に必要なサブクエリレベルがもう1つあります。
式(COALESCE(parent_id, message_id)
はプレーンインデックスと互換性がありません。その式にはインデックスが必要です。しかし、データの分布によっては、それもあまり役に立たない場合があります。詳細については、以下のリンクをクリックしてください。
parent_id IS NULL
の単純なケースを個別のSELECT
に分割すると、最適な結果が得られる場合と得られない場合があります。特にそれがまれなケースである場合は特にそうです。その場合、(COALESCE(parent_id, message_id)
のインデックスと組み合わせたクエリの方がパフォーマンスが向上する可能性があります。その他の考慮事項が適用されます...
特にこれらのインデックスでサポートされている場合:
CREATE INDEX messages_idx_null ON messages (
feed_id
, posted_at DESC NULLS LAST
, share_count DESC NULLS LAST
, parent_id, message_id
)
WHERE parent_id IS NULL;
CREATE INDEX messages_idx_notnull ON messages (
feed_id
, posted_at DESC NULLS LAST
, share_count DESC NULLS LAST
, parent_id, message_id
)
WHERE parent_id IS NOT NULL;
2つの部分インデックスは、テーブル全体をまとめてカバーし、1つの合計インデックスとして一緒にほぼ同じサイズになります。
最後の2つの列parent_id, message_id
は、 index-only scans を取得した場合にのみ意味があります。そうでない場合は、両方のインデックスからそれらを削除します。
欠落している詳細に応じて、DISTINCT ON
は目的に最適なクエリ手法である場合とそうでない場合があります。ここで詳細な説明を読んでください:
そしておそらくここでより速い代替案: