索引页

时间:2017-03-02 12:56:57

标签: ruby-on-rails postgresql activerecord

我有一个系统和一个报告模型。系统has_many报告和报告belongs_to系统。每个每日报告包含每个系统175条记录。

我需要在我的系统#index页面上查询,该页面应列出在最近创建报告时过滤的所有系统。这是我的第一次尝试。

@systems = System.joins('LEFT JOIN reports ON reports.system_id = systems.id').group('systems.id').order('MAX(reports.created_at) ASC')

这会列出带有报告的系统(系统负载(2.1ms)),但按system_id排序,而不是按report_at。报告。

第二次尝试

@systems = System.joins(:reports).where("reports.created_at = (SELECT MAX(created_at) FROM reports p group by system_id having p.system_id = reports.system_id)").order('reports.created_at DESC')  

这个查询完成了这项工作,但实际上很慢(系统负载(546.2ms)),尽管在report.created_at上有一个索引。

第三次尝试

@systems = System.joins(:reports).where("reports.id = (SELECT MAX(id) FROM reports p group by system_id having p.system_id = reports.system_id)").order('reports.id DESC')

同样完成工作,比第二次尝试(系统负载(468.3ms)略快)但仍然不够快。

任何提示?

编辑03032017

我在一个小测试数据集上做了数字

旧查询

SELECT s.* FROM systems s
JOIN reports r ON r.system_id = s.id
WHERE r.created_at = (
  SELECT MAX(created_at)
  FROM reports p
  group by p.system_id
  having p.system_id = r.system_id)
ORDER BY r.id DESC

Time: 622.683 ms

Philip Couling解决方案(干净,仅返回带有报告的系统)

SELECT systems.*
FROM systems
JOIN (
  SELECT reports.system_id
    , MAX(reports.created_at) created
  FROM reports
  GROUP BY reports.system_id
) AS r_date ON systems.id = r_date.system_id
ORDER BY r_date.created;

Time: 1.434 ms

BookofGreg解决方案(将为我提供所有系统,报告或无报告)

select systems.* from systems order by updated_at;

Time: 0.253 ms

我无法让systemjack的解决方案起作用。

最快的解决方案:bookofgreg

最干净的解决方案:philip couling

感谢您的意见。

3 个答案:

答案 0 :(得分:0)

一种可能的解决方案,如果您不需要页面上的报告数据,则在更新时会有报告after_save -> { self.system.touch } # in Report。这将导致系统updated_at占用报告更新的时间。

这意味着您只需按照更新后的状态对系统进行排序,而无需加入。

此解决方案假定没有其他方法可以更新System。如果有,那么您可以指定一个时间缓存列,您可以使用它来订购after_save -> { self.system.touch(:report_cached_updated_at) }

http://api.rubyonrails.org/classes/ActiveRecord/Persistence.html#method-i-touch

答案 1 :(得分:0)

(reports.system_id, reports.created_at) 上的索引可能有效地发挥作用:

@systems = System.joins(:reports).where("reports.created_at = (SELECT MAX(created_at) FROM reports p where p.system_id = reports.system_id) system_id)").order('reports.created_at DESC') 

... Alternativly

你的第二段代码:

System.joins(:reports).where("reports.id = (SELECT MAX(id) FROM reports p group by system_id having p.system_id = reports.system_id)").order('reports.id DESC')

扩展为:

   SELECT system.*
     JOIN reports ON system.id = reports.system_id
    WHERE reports.created_at = (
                           SELECT MAX(created_at) 
                             FROM reports p 
                         group by p.system_id 
                           having p.system_id = reports.system_id)
                       )
 ORDER BY reports.id DESC

请注意两次查看报告的方式。此外,因为您包含p.system_id = reports.system_id),所以每个系统记录将调用一次嵌套查询。

理想情况下,您希望获得system_ids和报告日期的列表: 所以...

    SELECT reports.system_id
         , MAX(reports.created_at) created
      FROM reports
  GROUP BY reports.system_id

然后加入:

  SELECT systems.* 
    FROM systems
    JOIN (
           SELECT reports.system_id
                , MAX(reports.created_at) created
             FROM reports
         GROUP BY reports.system_id
         ) AS r_date ON systems.id = r_date.systems_id
ORDER BY r_date.created

答案 2 :(得分:0)

window function可能对您有益。不知道如何在rails中实现这一点,但获取每个系统的最新报告的查询可能如下所示:

select * from (
    select s.*, r.sytem_id, r.created_at,
        row_number() OVER (PARTITION BY s.id ORDER BY r.created_at desc) AS row
    from systems s
    left join reports r on r.system_id = s.id
) where (row = 1 OR r.system_id is null)

检查为null是因为您的示例中有左连接,因此即使没有报告,您也必须要系统。

或更简单(但不确定语法是否正确):

select *
from systems s
left join reports r on r.system_id = s.id
having (r.system_id is null
    OR row_number() OVER (PARTITION BY s.id ORDER BY r.created_at desc) = 1)