这可以在一个查询中执行吗?

时间:2010-03-10 11:37:37

标签: sql ruby-on-rails activerecord

我有两种模式:

class User
end

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

我想获取给定用户的所有好友,这些好友按最近的消息日期排序,在给定用户和他的好友之间的对话中出现,如果可能在同一查询中,则获取对话中的留言数量。

现在,我坚持这个:

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)

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 记录,但我需要 user2 user3 user4当 user1 只向他的好友发送消息时,该特殊情况下的记录。

b)中

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)

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

情况C. user2作为伙伴1的伙伴缺失导致:joins => :sender。否则,如果:joins => :recipient缺少user3和user4。那是饼干。 无论我们如何加入模特。如何在一个查询中解决这种情况?

2 个答案:

答案 0 :(得分:2)

您需要select_extra_columns gem来返回join / aggregate列。假设您已安装gem,请修改您的User模型,如下所示。

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

现在,您可以进行以下调用以获取朋友的详细信息。

user.friends_with_conversation.each do |friend|
  p friend.name
  p friend.last_message_at
  p friend.message_count
end

您需要gem在查询返回的last_message_at对象中返回message_countUser

修改 我不熟悉PostgresSQL。粗略阅读文档表明,遵循SQL可能会有效。

: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
"

答案 1 :(得分:1)

看起来你想要一个替代的“连接”字符串(即不是:joins => :sender)。 :joins => :recipient会对情况B给出正确答案吗?

如果没有 - 您也可以将手工制作的SQL传递给:joins密钥,然后根据需要加入表格。

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