Rails以最大条件加入

时间:2011-09-07 20:12:47

标签: mysql ruby-on-rails activerecord join max

鉴于这两个模型

class Article < AR::Base
  belongs_to :tag

  default_scope order('id DESC')
end

class Tag < AR::Base
  has_many :articles

  scope :featured, order(:ordering).limit(5)
end

我正在尝试以这样一种方式加入这两个表:我可以检索特色标签列表,并为每个表添加该标签中带有单个查询的最新文章,例如

Tag.featured.each do |tag|
  p tag.name
  p tag.articles.first.title # this will fetch the article in the tag with the highest id
end

这样编写的代码有(n+1) query problem,我正在尝试优化它,因为它会非常频繁地运行。我在includes范围内排除了featured来电,因为它会在前5个标记中加载所有Article(很多文章......)

在普通的旧SQL中,我可以使用像这样的查询来连接这两个表

SELECT tag_id, MAX(id) FROM articles GROUP BY tag_id
# or in Rails
Article.select('MAX(id), tag_id').group(:tag_id)
+--------+---------+
| tag_id | MAX(id) |
+--------+---------+
| 14     | 26787   |
...
| 1      | 27854   |
| 5      | 27780   |
| 0      | 10953   |
+--------+---------+

作为连接表,我可以使用单个查询检索所有数据。

如何在Rails和ActiveRecord中移植它?

更新

我需要在AR上下文中执行的完整查询是

SELECT a.*, t.*
FROM tags t
JOIN (SELECT MAX(id) AS latest, tag_id FROM articles GROUP BY tag_id) j ON j.tag_id = t.id
JOIN articles p ON j.latest = a.id
LIMIT 5

我尝试使用最新查询的AR#find_by_sql方法,我得到了正确的结果集但是我无法浏览对象

sql = '...' # This holds the previous query
Tag.find_by_sql(sql).first.class
=> "Tag"
Tag.find_by_sql(sql).first.articles.first
=> nil # why???

更新2

也尝试了

Tag.joins('JOIN ... all my query join part here ...').first.articles.first
=> nil

但是我注意到我可以直接将我的文章字段用作标记字段,即我可以写

Tag.find_by_sql(...).first.title # where title is a field of Article class
Tag.joins(...).first.title # where title is a field of Article class

但显然我无法调用Article实例方法。

1 个答案:

答案 0 :(得分:0)

找到了我的问题的部分解决方案。我可以通过两个查询获取所需的记录,并且仍然将数据作为AR对象加载:

# This will fetch the needed article ids
ids = Article.select('MAX(id) as max_id').group(:tag_id).map(&:max_id)
# This return the top tags each one with the article needed in articles.first
Tag.includes(:articles).where('article.id IN (?)', ids).limit(5).each do |t|
  t.name # gives the tag name
  t.articles # gives [<#Article...>] i.e. an array with a single article
end

Rails将执行三个查询:

  1. 选择ids
  2. 一个用于选择第1点的returned_ids中的Tags ID
  3. 使用LEFT OUTER JOIN作为条件
  4. ,使用(非常奇怪的)WHERE a.id IN (ids) AND t.id IN (returned_ids)获取最新信息来从两个表中获取数据

    我不能使用像

    这样的WHERE条件
    where('article.id IN (SELECT MAX(id) from articles)')
    

    因为MySQL has a bug and it would think the subquery is derived。它似乎是will be fixed in 6.0

    仍在寻找更好的答案,也许是一个JOIN,例如我的问题中加载AR对象的那个。