关于sql:是否可以在一个查询中执行?

Is this possible to do in one query?

我有两种型号:

1
2
3
4
5
6
7
class USER
END

class Message
 belongs_to :sender, :class_name=> 'User'
 belongs_to :recipient, :class_name=> 'User'
END

我想按消息的最新日期来获取给定用户的所有好友,该消息在给定用户与其好友之间的对话中出现,并且如果可能的话,在同一个查询中,要获取其对话中的消息数。

现在,我被困在这里:

1
2
3
4
5
Messages.all(:joins => :sender,
   :conditions => ['sender_id = ? OR recipient_id = ?', some_user.id, some_user.id],
   :SELECT => 'users.*, sender_id, recipient_id, MAX(messages.created_at) as last_created, COUNT(messages.id) as messages_count',
   :GROUP => 'messages.sender_id, messages.recipient_id',
   :ORDER => 'last_created DESC'

该查询产生以下输出:

a)

1
2
3
4
users.* | sender_id | recipient_id | MAX(last_created) | messages_count
user1   | 1         | 2            | bla               | bla
user1   | 1         | 3            | bla               | bla
user1   | 1         | 4            | bla               | bla

因为模型由messages.sender_id = user.id联接,所以我只提取了user1记录,但是在这种特殊情况A下,当user1仅向其好友发送消息时,我需要user2,user3和user4记录。

b)

1
2
3
4
users.* | sender_id | recipient_id | MAX(last_created) | messages_count
user2   | 2         | 1            | bla               | bla
user3   | 3         | 1            | bla               | bla
user4   | 4         | 1            | bla               | bla

在情况B中,否则,我有我想要的东西-所有三个好友在最新消息日期之前排序,这在给定用户与其好友之间的对话中显示。

c)

1
2
3
4
users.* | sender_id | recipient_id | MAX(last_created) | messages_count
user1   | 1         | 2            | bla               | bla
user3   | 3         | 1            | bla               | bla
user4   | 4         | 1            | bla               | bla

作为用户1的伙伴的情况C.user2丢失原因:joins => :sender。否则,如果:joins => :recipient将缺少user3和user4。多数民众赞成在饼干。不管我们如何加入模型。如何在一个查询中解决这种情况?


您需要select_extra_columns gem返回连接/聚集列。假设您已经安装了gem,请如下所示修改您的用户模型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class USER
  select_extra_columns


  def friends_with_conversation
    USER.all(
     :SELECT =>"users.*, b.last_message_at, b.message_count",
     :joins  =>"
            RIGHT JOIN
             ( SELECT   IF(a.sender_id=#{self.id}, a.recipient_id,
                             a.sender_id) AS friend_id,
                        MAX(a.created_at) AS last_message_at,
                        COUNT(a.id)       AS message_count
               FROM     messages AS a
               WHERE    a.sender_id = #{self.id} OR
                        a.recipient_id = #{self.id}
               GROUP BY IF(a.sender_id=#{self.id}, a.recipient_id,
                             a.sender_id)
             ) AS b ON users.id = b.friend_id
          "
,
      :ORDER  =>"b.last_message_at DESC",      
      :extra_columns => {:last_message_at=>:datetime, :message_count => :INTEGER}
    )
  END  
END

现在您可以进行以下呼叫以获取朋友的详细信息。

1
2
3
4
5
USER.friends_with_conversation.each do |friend|
  p friend.name
  p friend.last_message_at
  p friend.message_count
END

您需要gem在查询返回的User对象中返回last_message_atmessage_count

编辑
我对PostgresSQL不熟悉。对文档进行有经验的阅读建议,可以使用以下SQL。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
:joins  =>"
 RIGHT JOIN
 ( SELECT   CASE WHEN a.sender_id=#{self.id}
                 THEN a.recipient_id
                 ELSE a.sender_id
            END               AS friend_id,
            MAX(a.created_at) AS last_message_at,
            COUNT(a.id)       AS message_count
   FROM     messages AS a
   WHERE    a.sender_id = #{self.id} OR
            a.recipient_id = #{self.id}
   GROUP BY CASE WHEN a.sender_id=#{self.id}
                 THEN a.recipient_id
                 ELSE a.sender_id
            END
 ) AS b ON users.id = b.friend_id
"


好像您想要一个替代的" joins"字符串(即不是:joins => :sender)。
:joins => :recipient对于情况B是否会给出正确的响应?

如果不是-您还可以将手工制作的SQL传递给:joins键,并根据需要连接表。

好像有一个很好的教程,涵盖了这里的联接:
http://www.railway.at/articles/2008/04/24/database-agnostic-database-ignorant/