これは、relational-divisionとタグ付けされた質問を表示 の場合です。 -同じ会話に追加がないという特別な要件が追加されました ユーザー。
想定 テーブル"conversationUsers"
のPKです これは、組み合わせの一意性を強制します。NOT NULL
また、パフォーマンスに不可欠なインデックスを暗黙的に提供します。 thisのマルチカラムPKのカラム 注文!それ以外の場合は、さらに多くのことを行う必要があります。
インデックス列の順序について:
基本的なクエリには、「ブルートフォース」があります。 すべての一致するユーザーの数をカウントするアプローチ 指定されたすべてのユーザーの会話を行い、指定されたすべてのユーザーに一致する会話をフィルタリングします。小さなテーブルや短い入力配列、および/またはユーザーごとの会話が少ない場合は問題ありませんが、適切にスケーリングされません :
SELECT "conversationId"
FROM "conversationUsers" c
WHERE "userId" = ANY ('{1,4,6}'::int[])
GROUP BY 1
HAVING count(*) = array_length('{1,4,6}'::int[], 1)
AND NOT EXISTS (
SELECT FROM "conversationUsers"
WHERE "conversationId" = c."conversationId"
AND "userId" <> ALL('{1,4,6}'::int[])
);
NOT EXISTS
を使用して追加のユーザーとの会話を排除する 反半結合。詳細:
代替手法:
他にもさまざまな(はるかに)高速なrelational-division があります。 クエリテクニック。ただし、最速のものは動的にはあまり適していません。 ユーザーIDの数。
高速クエリの場合 動的な数のユーザーIDも処理できる場合は、を検討してください。再帰CTE :
WITH RECURSIVE rcte AS (
SELECT "conversationId", 1 AS idx
FROM "conversationUsers"
WHERE "userId" = ('{1,4,6}'::int[])[1]
UNION ALL
SELECT c."conversationId", r.idx + 1
FROM rcte r
JOIN "conversationUsers" c USING ("conversationId")
WHERE c."userId" = ('{1,4,6}'::int[])[idx + 1]
)
SELECT "conversationId"
FROM rcte r
WHERE idx = array_length(('{1,4,6}'::int[]), 1)
AND NOT EXISTS (
SELECT FROM "conversationUsers"
WHERE "conversationId" = r."conversationId"
AND "userId" <> ALL('{1,4,6}'::int[])
);
使いやすくするために、これを関数またはプリペアドステートメント でラップします。 。いいね:
PREPARE conversations(int[]) AS
WITH RECURSIVE rcte AS (
SELECT "conversationId", 1 AS idx
FROM "conversationUsers"
WHERE "userId" = $1[1]
UNION ALL
SELECT c."conversationId", r.idx + 1
FROM rcte r
JOIN "conversationUsers" c USING ("conversationId")
WHERE c."userId" = $1[idx + 1]
)
SELECT "conversationId"
FROM rcte r
WHERE idx = array_length($1, 1)
AND NOT EXISTS (
SELECT FROM "conversationUsers"
WHERE "conversationId" = r."conversationId"
AND "userId" <> ALL($1);
電話:
EXECUTE conversations('{1,4,6}');
db <> fiddle ここ (機能のデモンストレーションも行います )
まだ改善の余地があります。トップを獲得する パフォーマンスでは、会話が最も少ないユーザーを入力配列の最初に配置して、できるだけ多くの行を早期に削除する必要があります。最高のパフォーマンスを得るには、非動的、非再帰的なクエリを動的に生成できます(高速のいずれかを使用) 最初のリンクからのテクニック)そしてそれを順番に実行します。動的SQLを使用して単一のplpgsql関数でラップすることもできます...
詳細説明:
代替:まばらに書かれたテーブルのMV
テーブル"conversationUsers"
の場合 ほとんどが読み取り専用です(古い会話が変更される可能性は低いです) MATERIALIZED VIEW
並べ替えられた配列に事前に集計されたユーザーを使用して、その配列列にプレーンなbtreeインデックスを作成します。
CREATE MATERIALIZED VIEW mv_conversation_users AS
SELECT "conversationId", array_agg("userId") AS users -- sorted array
FROM (
SELECT "conversationId", "userId"
FROM "conversationUsers"
ORDER BY 1, 2
) sub
GROUP BY 1
ORDER BY 1;
CREATE INDEX ON mv_conversation_users (users) INCLUDE ("conversationId");
実証されたカバーインデックスにはPostgres11が必要です。参照:
サブクエリの行の並べ替えについて:
古いバージョンでは、(users, "conversationId")
でプレーンな複数列のインデックスを使用します 。配列が非常に長い場合、Postgres10以降ではハッシュインデックスが意味をなす可能性があります。
その場合、はるかに高速なクエリは単純に次のようになります。
SELECT "conversationId"
FROM mv_conversation_users c
WHERE users = '{1,4,6}'::int[]; -- sorted array!
db <> fiddle こちら
ストレージ、書き込み、メンテナンスに追加されるコストと、読み取りパフォーマンスのメリットを比較検討する必要があります。
余談ですが、二重引用符のない正当な識別子を検討してください。 conversation_id
"conversationId"
の代わりに など: