问题 Rails Arel选择不同的列
我用新手来轻微阻挡 scope
方法(Arel 0.4.0,Rails 3.0.0.rc)
基本上我有:
一个 topics
模特,哪个 has_many :comments
,和 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个代表奖金,希望很快能找到一个优雅的解决方案。
9401
2017-08-24 08:33
起源
答案:
解决方案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
模型。更新 last_comment_id
创建评论后。此方法比使用复杂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查询以确定热门主题更有效。
5
2017-08-27 05: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
模型。更新 last_comment_id
创建评论后。此方法比使用复杂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查询以确定热门主题更有效。
5
2017-08-27 05:14
在大多数SQL实现中,这并不是那么优雅。一种方法是首先获取按topic_id分组的五个最新评论的列表。然后通过使用IN子句进行子选择来获取comments.created_at。
我对Arel很新,但这样的事情可行
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
2017-08-24 09:55
为了实现这一点,你需要有一个范围 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,因为在主题中的列不在“按列表分组”中,这肯定会在许多数据库引擎中失败。如果这确实存在问题,那么您可以通过将所选列限制为comments.topic_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
3
2017-08-26 10:57
由于这个问题是关于Arel的,所以我认为我会添加它,因为Rails 3.2.1补充道 uniq
到QueryMethods:
如果你添加 .uniq
它添加了Arel 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
2
2018-04-03 16:09