sql >> データベース >  >> RDS >> PostgreSQL

特定の数の関連付けを持つ行を検索するSQLクエリ

    これは、 の場合です。 -同じ会話に追加がないという特別な要件が追加されました ユーザー。

    想定 テーブル"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を使用して追加のユーザーとの会話を排除する 反半結合。詳細:

    代替手法:

    他にもさまざまな(はるかに)高速な があります。 クエリテクニック。ただし、最速のものは動的にはあまり適していません。 ユーザー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"の代わりに など:



    1. MySQLからのJframeのファイリング

    2. 削除または更新のアクションなしで外部キーを使用する理由

    3. 一意のキーなしで存在しない場合はmysql挿入

    4. チェックボックスを動的に作成する