简体   繁体   中英

Rails sql query to find record that belongs to two different users via has_many through

I have set up a messaging system between characters as follows. Each character has_many conversations through: :chats, and each conversation has_many :messages, some of which belong to one of the participants, and the rest to the other participant.

character.rb

has_many :chats,  foreign_key: "character_id",
                  dependent: :destroy
has_many :conversations, through: :chats, source: :conversation
has_many :messages

conversation.rb

has_many :messages

chat.rb

belongs_to :character
belongs_to :conversation

message.rb

belongs_to :character
belongs_to :conversation

A conversation between two characters "Alf" (character_id: 1) and "Baz" (character_id: 2) would therefore involve two rows in the Chats table with the same conversation_id (10, say):

Chats
character_id    conversation_id
1               10
2               10

There might exist another conversation (conversation_id: 23) in which both Alf and Baz are involved with a third user ("Cal", character_id: 7):

Chats
character_id    conversation_id
1               23
2               23
7               23

It is important that the query does not select this group conversation.

My question is, how do you construct an SQL query to find the conversation between ONLY Alf and Baz?

I'm stuck because there are three steps, so three SQL queries: first you have to find all conversations that belong to Alf, and then select from these the ones that also belong to Baz, and finally select from these the one that belongs to only Alf and Baz. How do you 'chain' three sql queries in one?

I'm thinking something along these lines:

alf_id = @alf.id
baz_id = @baz.id
find_by_sql("  SELECT      *
               FROM        Chats
               RIGHT JOIN  Conversations
               ON          Chats.character_id = #{alf_id}
               SELECT      *
               FROM        Conversations
               INNER JOIN  Chats
               ON          Chats.conversation_id = Conversations.id
               AND         Chats.character_id = #{baz_id}
               WHERE       (conversation belongs to only 2 characters)
           ; ")

EDIT Possible solution? Can anyone say if this is correct or not?:

sender_id    = @sender.id
recipient_id = @recipient.id
conversationID = find_by_sql("
      SELECT Conversations.id FROM
      (
            (
                  (
                        Conversations INNER JOIN ( Chats WHERE Chats.character_id=#{sender_id} )
                                      ON Chats.conversation_id=Conversations.id
                  )
                  INNER JOIN ( Chats WHERE Chats.character_id = #{recipient_id} )
                        ON Chats.conversation_id=Conversations.id
            )
            GROUP BY conversation_id
                  HAVING COUNT(Chats.conversation_id)=2
      )
; ")

Something like this:

select conversation_id
from conversations
group by conversation_id
having
        count(case when character_id = <ch1> then 1 end) = 1
    and count(case when character_id = <ch2> then 1 end) = 1
    and count(*) = 2

Another option is:

select conversation_id from conversations where character_id = <ch1>
intersect
select conversation_id from conversations where character_id = <ch2>
except
select conversation_id from conversations where character_id not in (<ch1>, <ch2>)

The first is probably faster and more portable.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM