Возможно ли это сделать в одном запросе? - PullRequest
3 голосов
/ 10 марта 2010

У меня есть две модели:

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'

Этот запрос приводит к выводу:

а)

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 записей это особая ситуация A, когда user1 отправляет сообщения только своим друзьям.

* 1 028 * б)
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, в противном случае, у меня есть то, что я хочу иметь - все три друга упорядочены по самой последней дате сообщения, которое появляется в разговоре между данным пользователем и его другом.

в)

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. Пользователь2 как партнер пользователя1 отсутствует Причина :joins => :sender. В противном случае, если :joins => :recipient будет отсутствовать user3 и user4. Это взломщик. Неважно, как мы присоединяемся к моделям . Как решить эту ситуацию одним запросом?

Ответы [ 2 ]

2 голосов
/ 10 марта 2010

Вам нужен гем select_extra_columns , чтобы вернуть столбцы соединения / агрегирования. Предполагая, что вы установили гем, измените модель пользователя, как показано ниже.

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

Чтобы получить last_message_at и message_count в объекте User, возвращаемом запросом, требуется гем.

Редактировать Я не знаком с 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 голос
/ 10 марта 2010

Похоже, вам нужна альтернативная строка "join" (т.е. не :joins => :sender). :joins => :recipient даст правильный ответ для ситуации B?

Если нет - вы также можете передать вручную созданный SQL-код на клавишу :joins и присоединиться к таблицам, как вам нравится.

Похоже, что здесь есть хорошее учебное пособие: http://www.railway.at/articles/2008/04/24/database-agnostic-database-ignorant/

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...