代码之家  ›  专栏  ›  技术社区  ›  Alvaro Alday

Rails 5:如何根据关联的模型属性对模型进行排序,并使它们与众不同

  •  0
  • Alvaro Alday  · 技术社区  · 6 年前

    我在Rails中有一个作用域,它应该按照最后创建的聊天消息的属性来排序一组对话。 就像这样:

    class Conversation < ApplicationRecord
        has_many :chat_messages, -> { order(created_at: :asc) }, dependent: :nullify do
          def last_from_user
            from_user.order("created_at ASC").last
          end
    
          def last_delivered_from_user
            from_user.order("delivered_at ASC").last
          end
    
          def last_from_agent
            from_agent.order("created_at ASC").last
          end
        end
    
        default_scope { joins(:chat_messages).order("chat_messages.created_at desc").distinct }
    end
    

    例如,当ID为4的组织收到一条新消息时,我希望订单是:

    [4,1,2,3]
    

    但是,我得到的是:

    [1,2,3,4]
    

    如果我删除这样的独特方法:

    class Conversation < ApplicationRecord
        default_scope { joins(:chat_messages).order("chat_messages.created_at desc") }
    end
    

    对话的顺序很好,但要重复: [4,1,2,3,4]

    我的聊天信息模型如下:

        class ChatMessage < ApplicationRecord
    
          # Associations
          # ------------------------------------------------------------
    
          belongs_to :conversation
        end
    

    我不能使用“唯一”方法,因为这将使分页无法正常工作。

    但是,当一个会话有多条消息时,“distinct”方法会扰乱顺序。这个范围返回会话的创建顺序,而不是我实际需要的顺序。

    1 回复  |  直到 6 年前
        1
  •  2
  •   Schwern    6 年前

    如果我们移除Rails,只需看一下SQL,它就更清晰了。您所拥有的是一个典型的SQL问题,即尝试根据连接的最大值或最小值来排序列表。

    您运行的查询如下:

    SELECT conversations.*
    FROM conversations
    INNER JOIN chat_messages
      ON chat_messages.conversation_id = conversations.id
    ORDER BY chat_messages.created_at desc
    

    你会回来的 每对对话和聊天信息的一行 . 这就是为什么你有副本。

    你不能用 distinct 有了这个。

    SELECT conversations.*
    FROM conversations
    INNER JOIN chat_messages
      ON chat_messages.conversation_id = conversations.id
    ORDER BY chat_messages.created_at desc
    
    ERROR 3065 (HY000): Expression #1 of ORDER BY clause is not in SELECT list,
    references column 'test.chat_messages.created_at' which is not in SELECT list;
    this is incompatible with DISTINCT
    

    如果你使用 select distinct conversations.*, chat_messages.created_at 你又回到起点了。

    我不确定是什么 .distinct 正在做,但可能会引发一个异常。


    相反,用 group by conversation.id . 现在您有了一个分组查询,您可以 order by max(chat_messages.created_at) desc .

    select c.*
    from conversations c
    join chat_messages cm
      on cm.conversation_id = c.id
    group by c.id
    order by max(cm.created_at) desc
    

    将此转换为Rails…

    Conversations
      .joins(:chat_messages)
      .group(:id)
      .order("max(chat_messages.created_at) desc")