我有一个系统和一个报告模型。系统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
感谢您的意见。
答案 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)