2017-03-02 65 views
1

我有一个系统和报表模型。系统has_many报告和报告belongs_to系统。每份日报包含每个系统175条记录。索引查询优化页面

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

@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排序不报告created_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 

菲利普·库寿龄解决方案的数字(干净的,仅返回与报告系统)

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 

BookofGr如解决方案(将给我所有的系统,报告或没有报告)

select systems.* from systems order by updated_at; 

Time: 0.253 ms 

我无法得到systemjack的解决方案的工作。

最快的解决方案:bookofgreg

干净的解决方案:菲利普·库寿龄

感谢您的输入。

回答

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') 

另类...

你的第二张代码:

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 
+0

对此回复非常满意,感谢您的解释。明天会试试看。 –

0

如果您不需要页面上的报告数据,则一个可能的解决方案是在更新时报告after_save -> { self.system.touch } # in Report。这将导致系统的updated_at在报告更新之前进行。

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

此解决方案假定没有其他方式来更新系统。如果有,那么你可以指定你可以用它来订购像after_save -> { self.system.touch(:report_cached_updated_at) }

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

+0

非常感谢,这看起来很有希望。我在我的视图中引用了报表数据,但仅引用了最后一个报表日期,在此解决方案中与system_updated相同。 –

+0

没问题:)上周在has_many:through关系中解决了这个问题,这个关系想要显示最近更新的东西的状态。 记得接受它是否适合你,祝你好运! – BookOfGreg

0

一个window function可能给您带来不错。不知道如何实现这种在轨,但查询以获得每个系统的最新报告可能看起来像:

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) 

为空的检查是存在的,因为你有一个左连接在你的榜样,所以你必须要系统即使没有报告。

或简单(但不是肯定的语法是正确的):

​​
+0

欣赏您的输入。从技术角度来看,我认为Philip Couling和你的答案是最纯粹的。如果我看性能增益,我倾向于BookofGreg。每日自动报告导入来自第三方API并包含aproximatley 87000记录,新系统会自动添加到(静态)系统表中。所以我认为完全避免加入可能最适合我的需求:-) –