Activerecord:将 ruby 方法转移到 sql-land

Activerecord: Transfer ruby methods to sql-land

我在 rails 应用程序中有以下架构

我的页面有很多 load_times(这是一个多态关联)。我想获取每个页面的最新 created_at load_time,然后从中获取最大 load_time 字段编号(希望同名字段不会让您感到困惑)。这是我目前使用的方法,但使用 ruby 方法:

pages.includes(:load_times).group(:resource_id).select("load_times.load_time, max(load_times.created_at) as date").reject{|lt| lt.load_time.nil?}.sort_by { |pg| pg.load_time.load_time }.last

如何将 rejectsort_by ruby 方法转移到 sql 土地?

这个怎么样:

pages
.joins(:load_times)
.group(:resource_id)
.select("pages.*, load_times.load_time, MAX(load_times.created_at) as date")
.where.not(load_times: { load_time: nil })
.order("load_times.load_time")
.last

请注意 where.not 仅 Rails >= 4。如果您在 Rails 3 中,您可以将其替换为 where("load_times.load_time IS NOT NULL")


在评论中明确了想要的结果后,这是获取最慢load_time的Page对象的查询,只考虑了最近的load_time条记录的[=56] =].请注意,您通常可以在带有 MAX(load_times.created_at)MAX(load_times.id) 的 having 子句中无差别地获得该结果,后者效率更高一些。如果这样做,请将所有对 created_at 的引用更改为 id

pages
.joins(:load_times)
.where.not(load_times: { load_time: nil })
.group('load_times.created_at')
.having('load_times.created_at = MAX(load_times.created_at)')
.order('load_times.load_time DESC')
.first

编辑

经过更多研究(是的,我个人认为 ;)),我终于让您的查询正常工作。您需要加入 load_times table 两次,使用加入条件中每个 Page 的最后一行。

此外,对于第二个联接,您需要使用单个列来标识 load_times 的最后一个条目。 created_at 可能无法正常工作,因为您的 load_times table 可能有两个 created_at 相同的条目。也许这不是你的情况,但以防万一我使用 id,因为它与 created_at 具有相同的顺序并且是唯一的。

此外,我删除了 where.not(load_times: { load_time: nil }) 位,因为我假设您想要在其上获取具有 load_time 属性的最后一个 load_time 元素。

最后,我假设了一个多态关联,因此 resource_type = 'Page'

开始了:

pages
.joins(:load_times)
.joins("INNER JOIN (SELECT l1.page_id, MAX(l1.id) AS max_id FROM load_times as l1 GROUP BY l1.page_id) l2 ON load_times.page_id = l2.page_id AND load_times.id = l2.max_id AND resource_type = 'Page'")
.order("load_times.load_time DESC")
.first

感谢发人深省的问题,学到不少东西

继续@dgilperez 的工作,我认为这可能 return 您的目标值:

pages
.joins(:load_times)
.where.not(load_times: { load_time: nil })
.group(:resource_id)
.select('pages.*, MAX(load_times.load_time) as load_time, MAX(load_times.created_at) as max_created_at')