1

Conversation と Phones の 2 つのモデルがあり、どちらも互いに has_and_belongs_to_many です。電話は多くの会話を持つことができ、会話は多くの電話 (2 つ以上) を持つことができます。

class Conversation < ActiveRecord::Base
  has_and_belongs_to_many :phones
end

class Phone < ActiveRecord::Base
  has_and_belongs_to_many :conversations
end

もちろん、conversations_phones 結合テーブルもあります。

2 つ以上の電話オブジェクトがある場合、それらが共有するすべての会話のリストを見つけるにはどうすればよいですか? 問題点: 会話に他の電話を含めることはできません (つまり、電話 ID の数は、検索に使用した数と同じです)。

私は純粋なRailsでそれを行うことができましたが、すべての会話をループし、データベースを頼りにする必要があります. 良くない。

純粋な SQL を実行してもかまいません。モデル ID を使用すると、インジェクション攻撃を阻止するのに役立ちます。

私が来た最も近いものは次のとおりです。

SELECT conversations.* FROM conversations 
INNER JOIN conversations_phones AS t0_r0 ON conversations.id = t0_r0.conversation_id 
INNER JOIN conversations_phones AS t0_r1 ON conversations.id = t0_r1.conversation_id 
WHERE (t0_r0.phone_id = ? AND t0_r1.phone_id = ?), @phone_from.id, @phone_to.id

ただし、外部の電話との会話も含まれます。私は GROUP BY と HAVING COUNT が役立つと感じています。私は SQL に慣れていません。

4

1 に答える 1

2

私はあなたがほとんどそこにいたと思います。NOT EXISTS追加の準半結合を使用して部外者との会話を除外するだけです:

SELECT c.*
FROM   conversations c
JOIN   conversations_phones AS cp1 ON cp1.conversation_id = c.id
                                  AND cp1.phone_id = ?
JOIN   conversations_phones AS cp2 ON cp2.conversation_id = c.id
                                  AND cp2.phone_id = ?
...
WHERE NOT EXISTS (
   SELECT 1
   FROM   conversations_phones cp
   WHERE  cp.conversation_id = c.id
   AND    cp.phone_id NOT IN (cp1.phone_id, cp2.phone_id, ...) -- or repeat param
   )
, @phone1.id, @phone2.id, ...

簡単にするために条件を JOIN 句に入れましたが、クエリ プランは変更されません。と のインデックス
必要であることは言うまでもありません。conversations(id)conversations_phones(conversation_id, phone_id)

代替手段(はるかに遅い):

非常に単純ですが、遅いです:

SELECT cp.conversation_id
FROM  (
   SELECT conversation_id, phone_id
   FROM   conversations_phones
   ORDER  BY 1,2
   ) cp
GROUP  BY 1
HAVING array_agg(phone_id) = ?

..?は、次のような ID のソートされた配列です'{559,12801}'::int[]

簡単なテストでは30 倍遅くなります。

完全を期すために、コメントで@BroiSatseによって提案された(簡略化された)代替案は、同様のクイックテストで約20倍遅くなります。

...
JOIN (
   SELECT conversation_id, COUNT(*) AS phone_count
   FROM   conversations_phones
   GROUP  BY prod_id
   ) AS pc ON pc.conversation_id = c.id AND phone_count = 2

または、もう少し単純で高速です。

...
JOIN (
   SELECT conversation_id
   FROM   conversations_phones
   GROUP  BY prod_id
   HAVING COUNT(*) = 2
   ) AS pc ON pc.conversation_id = c.id
于 2013-08-10T15:31:56.547 に答える