简体   繁体   English

在Rails中为消息传递系统添加棘手的MySQL查询-请帮助

[英]Tricky MySQL Query for messaging system in Rails - Please Help

I'm writing a facebook style messaging system for a Rails App and I'm having trouble selecting the Messages for the inbox (with will_paginate). 我正在为Rails应用程序编写一个Facebook样式的消息传递系统,但在选择收件箱的消息(使用will_paginate)时遇到了麻烦。

The messages are organized in threads, in the inbox the most recent message of a thread will appear with a link to it's thread. 消息是按线程组织的,在收件箱中,将显示线程的最新消息以及指向该线程的链接。 The thread is organized via a parent_id 1-n relationship with itself. 线程通过与其自身的parent_id 1-n关系进行组织。

So far I'm using something like this: 到目前为止,我正在使用这样的东西:

class Message < ActiveRecord::Base
  belongs_to :sender, :class_name => 'User', :foreign_key => "sender_id"
  belongs_to :recipient, :class_name => 'User', :foreign_key => "recipient_id"
  has_many :children, :class_name => "Message", :foreign_key => "parent_id"
  belongs_to :thread, :class_name => "Message", :foreign_key => "parent_id"
end

class MessagesController < ApplicationController

  def inbox
    @messages = current_user.received_messages.paginate :page => params[:page], :per_page => 10, :order => "created_at DESC"
  end
end

That gives me all the messages, but for one thread the thread itself and the most recent message will appear (and not only the most recent message). 这给了我所有消息,但是对于一个线程,线程本身和最新消息将出现(不仅是最新消息)。 I can also not use the GROUP BY clause, because for the thread itself (the parent so to say) the parent_id = nil of course. 我也不能使用GROUP BY子句,因为对于线程本身(可以说是父级),当然parent_id = nil。

Anyone got an idea on how to solve this in an elegant way? 有人知道如何以一种优雅的方式解决这个问题吗? I already thought about adding the parent_id to the parent itself and then group by parent_id, but I'm not sure if that works. 我已经考虑过将parent_id添加到父项本身,然后按parent_id分组,但是我不确定是否可行。

Thanks 谢谢

I don't know how to accomplish this in Rails, but this is how I did it directly in MySQL: 我不知道如何在Rails中完成此操作,但这是我直接在MySQL中完成的操作:

select * from messages where message_id in ( select max(message_id) from messages where to_uid = 51 group by thread_id ) order by timestamp desc 从message_id中的消息中选择*(从to_uid = 51 by thread_id中的消息中选择max(message_id))按时间戳记desc排序

I used a subquery to grab the most recent message in a thread and then the main query to grab all of the fields for the messages found in the subquery. 我使用了子查询来获取线程中的最新消息,然后使用主查询来获取子查询中找到的消息的所有字段。

My solution would be to get a list of threads (which I'm assuming could be obtained by messages with no parent id). 我的解决方案是获取线程列表(我假设可以通过没有父ID的消息获得)。 Then on the Message model, add a method that will find the latest message in the thread and return it. 然后在Message模型上,添加一个方法,该方法将在线程中找到最新消息并返回它。 You can then use that method to obtain the latest method in each thread and put in a link to the head of the thread easily. 然后,您可以使用该方法在每个线程中获取最新的方法,并轻松地将链接放置到线程的头部。

(Pseudo-)code: (伪)代码:

class Message < ActiveRecord::Base
  belongs_to :sender, :class_name => 'User', :foreign_key => "sender_id"
  belongs_to :recipient, :class_name => 'User', :foreign_key => "recipient_id"
  has_many :children, :class_name => "Message", :foreign_key => "parent_id"
  belongs_to :thread, :class_name => "Message", :foreign_key => "parent_id"

  def get_last_message_in_thread()
    last_message = self
    children.each do |c|
       message = c.get_last_message_in_thread()
       last_message = message if message.created_at > last_message.created_at
    end
    return last_message
  end
end

class MessagesController < ApplicationController

  def inbox
    @messages = current_user.received_messages.find_by_parent_id(Null).paginate :page => params[:page], :per_page => 10, :order => "created_at DESC"
  end
end

You could probably do a lot better than having a recursive function to find the last message in the thread, but it's the simplest solution I can think of to demonstrate the idea. 您可能比具有递归函数来查找线程中的最后一条消息要好得多,但这是我可以证明的最简单的解决方案。 I'm also not sure I have the correct syntax for finding unset parent id's in the inbox function, which is why I marked the code as pseudo code :) 我也不确定我是否具有正确的语法来在收件箱功能中查找未设置的父代ID,这就是为什么我将代码标记为伪代码的原因:)

The only efficient way would be to have a Thread model and use GROUP BY as you mentioned - Anything else would require iteration over the messages. 唯一有效的方法是拥有一个线程模型并使用您提到的GROUP BY-其他任何方法都需要对消息进行迭代。

read update in comments 阅读评论中的更新

giving the parent itself as parent makes it very easy to create queries that operate on the whole thread, because you can group (or anything similar) by parent_id. 将父级本身设置为父级使创建在整个线程上运行的查询变得非常容易,因为您可以按parent_id分组(或类似的名称)。

if you handle the parents differently, all your queries have to take care of this too 如果您对父母的处理方式不同,那么您所有的查询也都必须考虑到这一点。

I figured the only good solution is using a second model to store the most recent messages for every thread (because of performance issues when using GROUP BY with a subselect, see my comments). 我认为唯一好的解决方案是使用第二种模型来存储每个线程的最新消息(由于将GROUP BY与子选择一起使用时出现性能问题,请参见我的评论)。 It won't take a lot of space in the DB because we're only storing id's and no text or even blobs. 它在数据库中不会占用太多空间,因为我们只存储id,而没有文本甚至Blob。

The RecentMessages Model would look something like this: 最近消息模型看起来像这样:

create_table :recent_messages do |t|
  t.integer :sender_id
  t.integer :recipient_id
  t.integer :message_id
  t.integer :message_thread_id

  t.timestamps
end

class RecentMessage < ActiveRecord::Base

  belongs_to :message
  belongs_to :message_thread, :class_name => 'Message'
  belongs_to :sender, :class_name => 'User', :foreign_key => "sender_id"
  belongs_to :recipient, :class_name => 'User', :foreign_key => "recipient_id"

end

The main idea is: All the messages are stored in one model (Messages). 主要思想是:所有消息都存储在一个模型(消息)中。 Whenever a new message is added to a thread (or a thread is created), two things happen (eg with a after_save callback): 每当将新消息添加到线程(或创建线程)时,就会发生两件事(例如,使用after_save回调):

  • Store the new message in the RecentMessages model (that means sender_id, recipient_id, message_id, message_thread_id (= parent_id || id)) 将新消息存储在LatestMessages模型中(这意味着sender_id,receiver_id,message_id,message_thread_id(= parent_id || id))
  • Get the most recent message (from this thread in messages), where sender_id == recipient_id and vice versa (note: This only works if the message model should only support messages between 2 users) and store it in the RecentMessages model as well (if found and if it's not already there) 获取最新消息(从消息中的该线程),其中sender_id ==收件人_id,反之亦然(注意:仅在消息模型仅支持2个用户之间的消息时,此方法才有效)并将其存储在最近消息模型中(如果找到,如果还不存在)

Of course there should only be max. 当然应该只有最大。 2 recent_messages stored in the DB for every message_thread at any given time. 在任何给定时间为每个message_thread存储在数据库中的2个last_messages。

If one wants to show ie the inbox, the following has to happen: 如果要显示收件箱,则必须进行以下操作:

@messages = current_user.recent_received_messages.paginate :page => params[:page], :per_page => 10, :order => "created_at DESC", :include => :message

That's the best I figured out so far. 这是我到目前为止所发现的最好的结果。 I still think it's ugly but it's fast and it works. 我仍然认为这很丑陋,但是速度很快并且可以正常工作。 If anyone comes up with a better solution, I'll be gratefull! 如果有人提出更好的解决方案,我将不胜感激!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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