代码之家  ›  专栏  ›  技术社区  ›  Jeriko

Rails Arel选择不同的列

  •  11
  • Jeriko  · 技术社区  · 14 年前

    我用新的 scope 方法(AREL 0.4.0,Rails 3.0.0.rc)

    基本上我有:

    topics 模型 has_many :comments 和A comments 模型(用 topic_id 列) belongs_to :topics .

    我正试图收集一些“热门话题”,即最近评论过的话题。当前代码如下:

    # models/comment.rb
    scope :recent, order("comments.created_at DESC")
    
    # models/topic.rb
    scope :hot, joins(:comments) & Comment.recent & limit(5)
    

    如果我执行 Topic.hot.to_sql ,将激发以下查询:

    SELECT "topics".* FROM "topics" INNER JOIN "comments"
    ON "comments"."topic_id" = "topics"."id"
    ORDER BY comments.created_at DESC LIMIT 5
    

    这很好,但它可能返回重复的主题-如果主题3最近被评论了几次,它将被返回几次。

    我的问题

    我该如何返回一组不同的主题,记住我仍然需要访问 comments.created_at 字段,以显示最后一个日志是多久以前的?我会想象沿着 distinct group_by 但我不太确定该怎么做。

    任何建议都非常感谢-我已经增加了100个代表的奖金,希望很快能找到一个优雅的解决方案。

    4 回复  |  直到 12 年前
        1
  •  5
  •   Harish Shetty    14 年前

    解决方案1

    这不使用AREL,但使用Rails 2.x语法:

    Topic.all(:select => "topics.*, C.id AS last_comment_id, 
                           C.created_at AS last_comment_at",
              :joins => "JOINS (
                 SELECT DISTINCT A.id, A.topic_id, B.created_at
                 FROM   messages A,
                 (
                   SELECT   topic_id, max(created_at) AS created_at
                   FROM     comments
                   GROUP BY topic_id
                   ORDER BY created_at
                   LIMIT 5
                 ) B
                 WHERE  A.user_id    = B.user_id AND 
                        A.created_at = B.created_at
               ) AS C ON topics.id = C.topic_id
              "
    ).each do |topic|
      p "topic id: #{topic.id}"
      p "last comment id: #{topic.last_comment_id}"
      p "last comment at: #{topic.last_comment_at}"
    end
    

    确保索引 created_at topic_id 列中 comments 表。

    解决方案2

    添加一个 last_comment_id 你的专栏 Topic 模型。更新 拉斯特 创建注释后。这种方法比使用复杂的SQL来确定最后一条注释快得多。

    例如:

    class Topic < ActiveRecord::Base
      has_many :comments
      belongs_to :last_comment, :class_name => "Comment"
      scope :hot, joins(:last_comment).order("comments.created_at DESC").limit(5)
    end
    
    class  Comment
      belongs_to :topic
    
      after_create :update_topic
    
      def update_topic
        topic.last_comment = self
        topic.save
        # OR better still
        # topic.update_attribute(:last_comment_id, id)
      end
    end
    

    这比运行复杂的SQL查询来确定热门主题要有效得多。

        2
  •  3
  •   Jonas Elfström    14 年前

    在大多数SQL实现中,这并不是那么优雅。一种方法是首先获取按主题ID分组的五个最近评论的列表,然后通过用in子句进行子选择来获取comments.created。

    我对阿雷尔很陌生,但像这样的事情可以奏效。

    recent_unique_comments = Comment.group(c[:topic_id]) \
                                    .order('comments.created_at DESC') \
                                    .limit(5) \
                                    .project(comments[:topic_id]
    recent_topics = Topic.where(t[:topic_id].in(recent_unique_comments))
    
    # Another experiment (there has to be another way...)
    
    recent_comments = Comment.join(Topic) \
                             .on(Comment[:topic_id].eq(Topic[:topic_id])) \ 
                             .where(t[:topic_id].in(recent_unique_comments)) \
                             .order('comments.topic_id, comments.created_at DESC') \
                             .group_by(&:topic_id).to_a.map{|hsh| hsh[1][0]}
    
        3
  •  3
  •   Steve Weet    14 年前

    为了实现这一点,您需要使用 GROUP BY 获取每个主题的最新评论。然后您可以订购这个范围 created_at 获取最近对主题的评论。

    下面的代码适用于我使用sqlite

    class Comment < ActiveRecord::Base
    
      belongs_to :topic
    
      scope :recent, order("comments.created_at DESC")
      scope :latest_by_topic, group("comments.topic_id").order("comments.created_at DESC")
    end
    
    
    class Topic < ActiveRecord::Base
      has_many :comments
    
      scope :hot, joins(:comments) & Comment.latest_by_topic & limit(5)
    end
    

    我使用以下seeds.rb生成测试数据

    (1..10).each do |t|
      topic = Topic.new
      (1..10).each do |c|
        topic.comments.build(:subject => "Comment #{c} for topic #{t}")
      end
      topic.save
    end
    

    下面是测试结果

    ruby-1.9.2-p0 > Topic.hot.map(&:id)
     => [10, 9, 8, 7, 6] 
    ruby-1.9.2-p0 > Topic.first.comments.create(:subject => 'Topic 1 - New comment')
     => #<Comment id: 101, subject: "Topic 1 - New comment", topic_id: 1, content: nil, created_at: "2010-08-26 10:53:34", updated_at: "2010-08-26 10:53:34"> 
    ruby-1.9.2-p0 > Topic.hot.map(&:id)
     => [1, 10, 9, 8, 7] 
    ruby-1.9.2-p0 > 
    

    为sqlite生成的SQL(重新格式化)非常简单,我希望AREL为其他引擎呈现不同的SQL,因为在许多DB引擎中,这肯定会失败,因为主题中的列不在“分组依据列表”中。如果这确实存在问题,那么您可以通过将所选列限制为comments.topic\u id来克服它。

    puts Topic.hot.to_sql
    SELECT     "topics".* 
    FROM       "topics" 
    INNER JOIN "comments" ON "comments"."topic_id" = "topics"."id" 
    GROUP BY  comments.topic_id 
    ORDER BY  comments.created_at DESC LIMIT 5
    
        4
  •  2
  •   ice cream    12 年前

    因为这个问题是关于阿雷尔的,所以我想我应该添加这个,因为Rails3.2.1添加了 uniq 查询方法:

    如果添加 .uniq 在阿雷尔的作品中增加了 DISTINCT select 语句。

    例如 Topic.hot.uniq

    也在范围内工作:

    例如 scope :hot, joins(:comments).order("comments.created_at DESC").limit(5).uniq

    所以我假设

    scope :hot, joins(:comments) & Comment.recent & limit(5) & uniq
    

    也可能有效。

    http://apidock.com/rails/ActiveRecord/QueryMethods/uniq