2017-03-02 5 views
1

Ich habe ein System und ein Berichtsmodell. System hat viele Berichte und Bericht gehört zum System. Jeder Tagesbericht besteht aus 175 Datensätzen pro System.Abfrageoptimierung für Indexseite

Ich brauche eine Abfrage auf meiner System-Indexseite, die alle Systeme auflisten sollte, die bei der letzten Berichterstellung gefiltert wurden. Dies war mein erster Versuch.

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

Hier werden Systeme mit einem Bericht (System Load (2.1ms)), sondern durch system_id nicht durch Bericht created_at sortiert.

Zweiter Versuch

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

Diese Abfrage macht den Job, aber es ist wirklich langsam (System Load (546.2ms)), trotz eines Index auf report.created_at haben.

Dritter Versuch

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

Auch die Arbeit macht, etwas schneller als der zweite Versuch (System Load (468.3ms)), aber immer noch nicht schnell genug.

Irgendwelche Tipps?

EDIT 03032017

ich die Zahlen auf einem kleinen Test-Datensatz

alte Abfrage

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 Lösung (sauber, kehrt nur Systeme mit Berichten) hat

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 zB Lösung (gibt mir alle Systeme, Bericht oder keinen Bericht)

select systems.* from systems order by updated_at; 

Time: 0.253 ms 

Ich konnte systemjacks Lösung nicht zum Funktionieren bringen.

schnellste Lösung: bookofgreg

sauberste Lösung: philip couling

Vielen Dank für Ihre Eingabe.

Antwort

0

Ein Index auf (reports.system_id, reports.created_at) verwenden könnte diese Arbeit machen effizient:

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

Alternativ ...

Ihr zweites Stück Code:

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

expandiert nach:

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 

Beachten Sie, wie es zweimal auf Berichte suchen hat. Da Sie auch p.system_id = reports.system_id) einschließen, wird die verschachtelte Abfrage einmal pro Systemdatensatz aufgerufen.

Im Idealfall möchten Sie eine Liste von system_ids und Berichtdaten erhalten: So ...

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

Und dann, dass beitreten:

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

Wirklich glücklich mit dieser Antwort, danke für die Erklärung. Werde es morgen ausprobieren. –

0

Eine mögliche Lösung, wenn Sie die Berichtsdaten auf der Seite nicht benötigen, ist Bericht after_save -> { self.system.touch } # in Report, wenn es aktualisiert wird. Dies führt dazu, dass die updated_at des Systems die Zeit annimmt, zu der der Bericht aktualisiert wurde.

Dies bedeutet, dass Sie das System einfach sortieren können, indem Sie es aktualisieren, ohne dass Sie überhaupt beitreten.

Diese Lösung setzt voraus, dass es keine andere Möglichkeit gibt, System zu aktualisieren.Wenn es dann können Sie eine Zeit Cache Spalte angeben, die Sie bestellen auf wie after_save -> { self.system.touch(:report_cached_updated_at) }

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

+0

Vielen Dank, das vielversprechend aussieht. Ich verweise auf die Berichtsdaten in meiner Sicht, aber nur auf das Datum des letzten Berichts, das in dieser Lösung dasselbe ist wie das system_updated at. –

+0

Kein Problem :) Das habe ich letzte Woche auf einer has_many: through-Relation für mich selbst gelöst, die den neuesten Stand der Dinge zeigen wollte. Denken Sie daran zu akzeptieren, wenn es für Sie funktioniert, viel Glück! – BookOfGreg

0

A window function könnte auch für Sie durchführen. Nicht sicher, wie dies in Schienen zu implementieren, aber die Abfrage der letzte Bericht für jedes System erhalten könnte wie folgt aussehen:

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) 

Die Prüfung auf null ist da, weil Sie links haben in Ihrem Beispiel anschließen, so dass Sie wollen, müssen Systeme, auch wenn es keinen Bericht gibt.

oder einfacher (aber nicht so sicher Syntax ist rechts):

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

Schätzen Sie Ihre Eingabe. Aus technischer Sicht denke ich, Philip Couling und deine Antwort sind die reinsten. Wenn ich auf den Leistungszuwachs schaue, lehne ich mich zu BookofGreg hin. Der tägliche automatische Import von Berichten erfolgt über eine API von Drittanbietern und besteht aus 87000 Datensätzen. Neue Systeme werden automatisch zur (statischen) Systemtabelle hinzugefügt. Also denke ich, dass das Vermeiden eines Joins ganz gut zu meinen Bedürfnissen passt :-) –