我有两个模型,Conversation和Phones,两个模型彼此都有_and_belongs_to_many。电话可以有很多对话,对话可以有很多电话(两个或更多)。
class Conversation < ActiveRecord::Base
has_and_belongs_to_many :phones
end
class Phone < ActiveRecord::Base
has_and_belongs_to_many :conversations
end
当然,还有一个conversations_phones连接表。
如果我有两个或更多电话对象,我如何找到他们共享的所有对话的列表?问题:对话不能包括任何其他电话(IE电话号码的数量等于我们搜索的号码)。
我已经能够使用纯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太新了。
答案 0 :(得分: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) = ?
..其中?
是像'{559,12801}'::int[]
在快速测试中慢了30倍。
为了完整性,(简化)提议的alternative by @BroiSatse in the comments在类似的快速测试中执行 20x :
...
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