26

配列内のすべての要素を一致させる必要がある句を実現するための最も簡単で最速の方法は何ですか?使用する場合は1つだけではありませんINか?結局のところ、 mongodbの$allのように動作するはずです。

conversation_usersがconversation_idとuser_idの間の結合テーブルであるグループ会話について考えると、次のようなことを念頭に置いています。

WHERE (conversations_users.user_id ALL IN (1,2))

更新16.07.12

スキーマとケースに関する詳細情報の追加:

  1. 結合テーブルはかなり単純です。

                  Table "public.conversations_users"
         Column      |  Type   | Modifiers | Storage | Description 
    -----------------+---------+-----------+---------+-------------
     conversation_id | integer |           | plain   | 
     user_id         | integer |           | plain   | 
    
  2. 会話には多くのユーザーがいて、ユーザーは多くの会話に属しています。会話内のすべてのユーザーを見つけるために、私はこの結合テーブルを使用しています。

  3. scope結局、私は参加者に応じて会話を見つけてくれるレール上のルビーを見つけようとしています-例:

    scope :between, ->(*users) {
      joins(:users).where('conversations_users.user_id all in (?)', users.map(&:id))
    }
    

更新23.07.12

私の質問は、人々の完全な一致を見つけることについてです。したがって:

クエリを実行すると、間の会話(1,2,3)は一致しません(1,2)

4

9 に答える 9

33

結合テーブルが適切な方法に従っており、一意の複合キー (行の重複を防ぐための制約) が定義されていると仮定すると、次の単純なクエリのようになります。

select conversation_id from conversations_users where user_id in (1, 2)
group by conversation_id having count(*) = 2

最後の数字 2 は user_ids のリストの長さであることに注意してください。user_id リストの長さが変わる場合は、明らかに変更する必要があります。結合テーブルに重複がないと想定できない場合は、"count(*)" を "count(distinct user_id)" に変更しますが、パフォーマンスが低下する可能性があります。

このクエリは、会話に追加のユーザーが含まれている場合でも、指定されたすべてのユーザーを含むすべての会話を検索します。

指定された一連のユーザーとの会話のみが必要な場合は、次のように where 句でネストされたサブクエリを使用する方法があります最初と最後の行は元のクエリと同じで、真ん中の 2 行だけが新しいことに注意してください。

select conversation_id from conversations_users where user_id in (1, 2)
   and conversation_id not in
   (select conversation_id from conversations_users where user_id not in (1,2))
group by conversation_id having count(*) = 2

同様に、データベースがサポートしている場合は集合差分演算子を使用できます。次に、Oracle 構文の例を示します。(Postgres または DB2 の場合、キーワード「minus」を「except」に変更します。)

select conversation_id from conversations_users where user_id in (1, 2)
  group by conversation_id having count(*) = 2
minus
  select conversation_id from conversations_users where user_id not in (1,2)

優れたクエリ オプティマイザは、最後の 2 つのバリエーションを同じように処理する必要がありますが、特定のデータベースで確認してください。たとえば、Oracle 11GR2 クエリ プランは、マイナス演算子を適用する前に 2 つの会話 ID のセットを並べ替えますが、最後のクエリの並べ替え手順をスキップします。したがって、行数、コア、キャッシュ、インデックスなどの複数の要因に応じて、どちらのクエリ プランも高速になる可能性があります。

于 2012-07-21T05:28:20.697 に答える
7

これらのユーザーを配列にまとめています。これを読みやすくするために、CTE (WITH 句にあるもの) も使用しています。

=> select * from conversations_users ;
 conversation_id | user_id
-----------------+---------
               1 |       1
               1 |       2
               2 |       1
               2 |       3
               3 |       1
               3 |       2
(6 rows)       

=> WITH users_on_conversation AS (
  SELECT conversation_id, array_agg(user_id) as users
  FROM conversations_users
  WHERE user_id in (1, 2) --filter here for performance                                                                                      
  GROUP BY conversation_id
)
SELECT * FROM users_on_conversation
WHERE users @> array[1, 2];
 conversation_id | users
-----------------+-------
               1 | {1,2}
               3 | {1,2}
(2 rows) 

編集(一部のリソース)

于 2012-07-16T21:34:48.303 に答える
4

これにより、ActiveRecordオブジェクトが保持されます。

以下の例では、配列内のすべてのコードに関連付けられているタイム シートを知りたいと考えています。

codes = [8,9]

Timesheet.joins(:codes).select('count(*) as count, timesheets.*').
           where('codes.id': codes).
           group('timesheets.id').
           having('count(*) = ?', codes.length)

操作する完全なActiveRecordオブジェクトが必要です。真のスコープにしたい場合は、上記の例を使用して、結果を.pluck(:id).

于 2015-05-15T17:13:48.110 に答える
3

@Alexの答えINcount()おそらく最も単純な解決策ですが、このPL/pgSQL関数の方が高速であると期待しています。

CREATE OR REPLACE FUNCTION f_conversations_among_users(_user_arr int[])
  RETURNS SETOF conversations AS
$BODY$
DECLARE
    _sql text := '
    SELECT c.*
    FROM   conversations c';
    i int;
BEGIN

FOREACH i IN ARRAY _user_arr LOOP
    _sql  := _sql  || '
    JOIN   conversations_users x' || i || ' USING (conversation_id)';
END LOOP;

_sql  := _sql  || '
    WHERE  TRUE';

FOREACH i IN ARRAY _user_arr LOOP
    _sql  := _sql  || '
    AND    x' || i || '.user_id = ' || i;
END LOOP;

/* uncomment for conversations with exact list of users and no more
_sql  := _sql  || '
    AND    NOT EXISTS (
        SELECT 1
        FROM   conversations_users u
        WHERE  u.conversation_id = c.conversation_id
        AND    u.user_id <> ALL (_user_arr)
        )
*/

-- RAISE NOTICE '%', _sql;
RETURN QUERY EXECUTE _sql;

END;
$BODY$ LANGUAGE plpgsql VOLATILE;

電話:

SELECT * FROM f_conversations_among_users('{1,2}')

関数は動的にビルドし、次の形式のクエリを実行します。

SELECT c.*
FROM   conversations c
JOIN   conversations_users x1 USING (conversation_id)
JOIN   conversations_users x2 USING (conversation_id)
...
WHERE  TRUE
AND    x1.user_id = 1
AND    x2.user_id = 2
...

このフォームは、リレーショナル除算のクエリの広範なテストで最高のパフォーマンスを発揮しました。

アプリでクエリを作成することもできますが、1つの配列パラメーターを使用することを前提としています。また、これはとにかくおそらく最速です。

どちらのクエリでも、高速であるためには次のようなインデックスが必要です。

CREATE INDEX conversations_users_user_id_idx ON conversations_users (user_id);

複数列のプライマリ(または一意の)キー(user_id, conversation_id)も同様ですが、1つ(conversation_id, user_id)(非常によくあるように!)は劣ります。上記のリンクで短い理論的根拠を見つけるか、dba.SEのこの関連する質問の下で包括的な評価を見つけます

また、に主キーがあると仮定しますconversations.conversation_id

EXPLAIN ANALYZE@Alexのクエリとこの関数を使用してパフォーマンステストを実行し、結果を報告できますか?

どちらのソリューションも、追加のユーザーとの会話を含め、少なくともアレイ内のユーザーが参加する会話を見つけることに注意してください。
それらを除外したい場合は、関数の追加句のコメントを外します(または他のクエリに追加します)。

関数の機能についてさらに説明が必要な場合は教えてください。

于 2012-07-21T12:54:10.113 に答える
1

@Alex Blakemore の回答に基づいて、Conversationクラスの同等の Rails 4 スコープは次のようになります。

# Conversations exactly with users array
scope :by_users, -> (users) { 
                           self.by_any_of_users(users)
                             .group("conversations.id")
                             .having("COUNT(*) = ?", users.length) -
                           joins(:conversations_users)
                             .where("conversations_users.user_id NOT IN (?)", users)
}
# generates an IN clause
scope :by_any_of_users, -> (users) { joins(:conversations_users).where(conversations_users: { user_id: users }).distinct }

-Rails (マイナス)を実行する代わりに最適化できることに注意してください.where("NOT IN")。ただし、それは読むのが非常に複雑になります。

于 2016-06-24T20:26:05.377 に答える
1

一時テーブルをいじり始めたくないのではないかと思います。

あなたの質問は、正確に一連のユーザーとの会話が必要なのか、それともスーパーセットとの会話が必要なのかについて明確ではありませんでした。スーパーセットの場合は次のとおりです。

with users as (select user_id from users where user_id in (<list>)
              ),
     conv  as (select conversation_id, user_id
               from conversations_users
               where user_id in (<list>)
              )
select distinct conversation_id
from users u left outer join
     conv c
     on u.user_id = c.user_id
where c.conversation_id is not null

このクエリがうまく機能するためには、users と conversations_users の両方で user_id にインデックスがあることを前提としています。

正確なセットの場合。. .

with users as (select user_id from users where user_id in (<list>)
              ),
     conv  as (select conversation_id, user_id
               from conversations_users
               where user_id in (<list>)
              )
select distinct conversation_id
from users u full outer join
     conv c
     on u.user_id = c.user_id
where c.conversation_id is not null and u.user_id is not null
于 2012-07-20T01:23:21.370 に答える
1
select id from conversations where not exists(
    select * from conversations_users cu 
    where cu.conversation_id=conversations.id 
    and cu.user_id not in(1,2,3)        
)

これは簡単に Rails スコープにすることができます。

于 2012-07-18T12:40:52.053 に答える
1

すべての可能な値でマッピング テーブルを作成し、これを使用します

select 
    t1.col from conversations_users as t1 
    inner join mapping_table as map on t1.user_id=map.user_id
group by 
    t1.col  
having  
    count(distinct conversations_users.user_id)=
    (select count(distinct user_id) from mapping)
于 2012-07-13T10:40:42.733 に答える