レールアレル別々の列を選択します
-
30-09-2019 - |
質問
新しいものでわずかなブロックを打った scope
方法(Arel 0.4.0、Rails 3.0.0.RC)
基本的に私は持っています:
a topics
モデル、それ has_many :comments
, 、a comments
モデル(aで 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の担当者の賞金を追加しました。
解決
解決策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
Aを追加します 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クエリを実行してホットトピックを決定するよりもはるかに効率的です。
他のヒント
これは、ほとんどのSQL実装ではそれほどエレガントではありません。 1つの方法は、最初にTopic_IDによってグループ化された5つの最新のコメントのリストを取得することです。次に、in句を使用してsubを選択してcomments.created_atを取得します。
私はアレルにとても新しいですが、このようなものはうまくいくかもしれません
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]}
これを達成するには、 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(Reforatted)は非常に簡単であり、トピック内の列が「グループごと」にないため、多くのDBエンジンで確かに失敗するため、Arelが他のエンジンのSQLを異なることを願っています。これが問題を提示した場合、選択した列を単なるコメントに制限することで、おそらくそれを克服することができます。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
質問はアレルに関するものだったので、Rails 3.2.1が追加するので、私はこれを追加すると思いました uniq
QueryMethodsへ:
追加する場合 .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
おそらく機能するはずです。