4

Q:一个查询中选择所有帖子和相关评论,将评论关系加载到p.comments中,但从评论中只选择标题

邮政

#db
create_table :posts do |t|
  t.string :title
  t.text :text

  t.timestamps
end

#modlel
class Post < ActiveRecord::Base
  has_many :comments
end

评论

#db
create_table :comments do |t|
  t.string :title
  t.text :text
  t.integer :post_id

  t.timestamps
end

#model
class Comment < ActiveRecord::Base
  belongs_to :post
end

我需要什么:在一个查询中选择所有帖子和相关评论并将评论关系加载到 p.comments 中,但仅从评论中选择标题:

posts = Post.includes(:comments).select("posts.*, comments.title") 
posts.each do |p|
    puts(p.title)
    puts(p.text)
    p.comments.each do |c| 
        puts(c.title)
    end
end

如果第一部分由 .includes 完成:

posts = Post.includes(:comments).select('posts.*, comments.title')
posts.first.comments.loaded? # true

=> 选择postsid作为 t0_r0, posts. title作为 t0_r1, posts. text作为 t0_r2, posts. created_at作为 t0_r3, posts. updated_at作为 t0_r4, comments. id作为 t1_r0, comments. title作为 t1_r1 comments,。text作为 t1_r2 comments,。post_id作为 t1_r3, commentscreated_at作为 t1_r4, commentsupdated_atAS t1_r5 从左外posts连接comments打开commentspost_id= postsid

它完全忽略了选择。

好的,让我们尝试加入:

Post.joins(:comments).select('posts.*, comments.title')

=> SELECT posts.*, comments.title FROM postsINNER JOIN commentsON commentspost_id= postsid

更好,但我们需要表别名——Rails 不处理关系(为什么?)

posts = Post.joins(:comments).select('posts.*, comments.title as comments_title').first

=> SELECT posts.*, comments.title as comments_title FROM postsINNER JOIN commentsON commentspost_id= postsid

posts.first.comments.loaded? # false
posts.first.comments_title # "Comment 1.1"

好的,让我们试试 ARel

p = Post.arel_table
c = Comment.arel_table
posts = Post.find_by_sql(
    p.join(c).on(c[:post_id].eq(p[:id])) \
     .project(p[:id],p[:title],c[:id],c[:title]).to_sql
)

=> 选择postsid, posts. title, comments. id, comments. titleposts内部连接comments开始commentspost_id= postsid

同样的故事——ActiveRecord 不处理关系。

有什么建议么?

UPD

  1. 如果这个问题没有直接的解决方案,也许有一种方法可以使用一些 AR 方法来转换结果查询。因为,AR 使用 ARel,然后在其上调用 find_by_sql,然后做一些魔术,我无法找到它的执行时间和执行时间,以及中提琴:生成别名和加载关联。

  2. 也许有一对一关系的解决方案?

    帖子 = Post.select('posts.id,posts.title,comments.id,comments.title').includes(:comment) posts.first.comment.loaded? #真的

=> 选择postsid作为 t0_r0, posts. title作为 t0_r1, posts. text作为 t0_r2, posts. created_at作为 t0_r3, posts. updated_at作为 t0_r4, comments. id作为 t1_r0, comments. title作为 t1_r1 comments,。text作为 t1_r2 comments,。post_id作为 t1_r3, commentscreated_at作为 t1_r4, commentsupdated_atAS t1_r5 从左外posts连接comments打开commentspost_id= postsid

4

3 回答 3

3

恐怕我没有任何建议,因为您描述的所有场景都按预期工作。

  • :includes => 急切加载
  • :joins => 不急于加载

所以如果你想直接加载评论,你必须使用包含,连接永远不会起作用。

接下来,由于 SQL 查询中使用了特定的选择语法,急切加载可以工作。换句话说,当与包含一起使用时,您指定的选择将被忽略,并且默认情况下将加载所有列。

我认为最近尝试添加您正在寻找的功能在此票中进行了描述,通过在对包含的调用中添加 except 参数来解决该问题。不过,它似乎还没有得到任何回应。

就我个人而言,我会选择您使用包含指定的第一个示例,但跳过选择并接受 comment.text 已加载的事实。比我能想到的任何其他情况都好。

于 2011-02-01T14:16:30.920 回答
0

您还可以通过执行以下操作来使用带有活动记录的原始 SQL 查询:

post_comment = Post.find_by_sql("SELECT p.*, c.title AS comment_title FROM posts p JOIN comments c ON p.id=c.post_id").first
post_comment.comment_title

有点hacky,但它似乎工作。ActiveRecord 还公开了连接属性,以便您进行更复杂的查询。http://apidock.com/rails/ActiveRecord/Base/connection

于 2013-01-18T23:55:01.060 回答
-1

解决方案之一是切换到 DataMapper 而不是 ActiveRecort。对我来说,它比 AR 要好得多。 使用 DataMapper 而不是 ActiveRecord

于 2011-03-20T15:00:09.697 回答