2016-06-20 9 views
1

Ich habe eine Konten Tabelle, Einheiten Tabelle und Berichte Tabelle. Ein Konto hat viele Einheiten (der Fremdschlüssel der Einheiten ist account_id) und eine Einheit hat viele Berichte (der Fremdschlüssel der Berichte ist unit_id). Ich mag Kontonamen wählen, die Gesamtzahl der Einheiten für das Konto, und die letzten Bericht Zeit:Unterabfrage in Select-Anweisung aus einer Join-Tabelle

SELECT accounts.name AS account_name, 
COUNT(units.id) AS unit_count, 
(SELECT reports.time FROM reports INNER JOIN units ON units.id = reports.unit_id ORDER BY time desc LIMIT 1) AS last_reported_time 
FROM accounts 
INNER JOIN units ON accounts.id = units.account_id 
INNER JOIN reports ON units.id = reports.unit_id 
GROUP BY account_name, last_reported_time 
ORDER BY unit_count desc; 

Diese Abfrage für immer in Betrieb war, und ich bin nicht sicher, es zu tun, was ich erwarte.

Ein Konto hat viele Einheiten und eine Einheit hat viele Berichte. Ich möchte die Zeit des neuesten Berichts von allen Einheiten anzeigen, die für jedes Konto zugeordnet sind. Ist diese Abfrage korrekt? Wenn nicht, wie kann ich meine Aufgabe erledigen (wenn möglich ohne Verwendung einer Skriptsprache)?

Das Ergebnis ERKLÄREN:

Sort (cost=21466114.58..21466547.03 rows=172980 width=38) 
    Sort Key: (count(public.units.id)) 
    InitPlan 1 (returns $0) 
    -> Limit (cost=0.00..12.02 rows=1 width=8) 
      -> Nested Loop (cost=0.00..928988485.04 rows=77309416 width=8) 
       -> Index Scan Backward using index_reports_on_time on reports (cost=0.00..296291138.34 rows=77309416 width=12) 
       -> Index Scan using units_pkey on units (cost=0.00..8.17 rows=1 width=4) 
         Index Cond: (public.units.id = public.reports.unit_id) 
    -> GroupAggregate (cost=20807359.99..21446321.09 rows=172980 width=38) 
     -> Sort (cost=20807359.99..20966559.70 rows=63679885 width=38) 
       Sort Key: accounts.name, public.units.last_reported_time 
       -> Hash Join (cost=975.50..3846816.82 rows=63679885 width=38) 
        Hash Cond: (public.reports.unit_id = public.units.id) 
        -> Seq Scan on reports (cost=0.00..2919132.16 rows=77309416 width=4) 
        -> Hash (cost=961.43..961.43 rows=1126 width=38) 
          -> Hash Join (cost=16.37..961.43 rows=1126 width=38) 
           Hash Cond: (public.units.account_id = accounts.id) 
           -> Seq Scan on units (cost=0.00..928.67 rows=1367 width=28) 
           -> Hash (cost=11.72..11.72 rows=372 width=18) 
             -> Seq Scan on accounts (cost=0.00..11.72 rows=372 width=18) 
+0

Wie groß sind die einzelnen Tabellen? –

+0

@TimBiegeleisen die Berichtstabelle ist riesig. Etwa 10 GB. Aus diesem Grund habe ich ein LIMIT 1 angegeben. Ich wollte nur nach der Zeit desc und dann auf den neuesten Rekord begrenzen. Die anderen zwei Tabellen sind klein. – Donato

+0

@TimBiegeleisen gibt es eine Lösung, die funktionieren könnte? Ich bin in Ordnung, 10 Minuten auf die Abfrage zu warten, um zu vervollständigen, aber nicht über 2 Stunden. – Donato

Antwort

0

Etwa 95% der Kosten der Abfrage ist hier:

 -> Sort (cost=20807359.99..20966559.70 rows=63679885 width=38) 
      Sort Key: accounts.name, public.units.last_reported_time 
      -> Hash Join (cost=975.50..3846816.82 rows=63679885 width=38) 
       Hash Cond: (public.reports.unit_id = public.units.id) 
       -> Seq Scan on reports (cost=0.00..2919132.16 rows=77309416 width=4) 

Haben Sie einen Index für reports.unit_id? Wenn nicht, sollten Sie unbedingt einen hinzufügen.

Andere als die, die Ausgabespalte unit_count erscheint die Anzahl der Einheiten pro Konto zu geben, aber es nach alle verbindet die Berechnung und dann die Bestellung durch es sehr verschwenderisch ist. Die Unterabfrage in der Auswahlliste ist etwas Rätselhaftes für mich; Ich nehme an, Sie möchten die letzte Berichtszeit für jede Einheit, aber es gibt Ihnen nur die letzte Zeit der Berichterstattung über alle Einheiten kombiniert. Versuchen Sie dies stattdessen:

SELECT a.account_name, u.unit_count, r.last_reported_time 
FROM account a 
JOIN (
    SELECT account_id, COUNT(*) AS unit_count 
    FROM units 
    GROUP BY 1) u ON u.account_id = a.id 
LEFT JOIN (-- allow for units that have not yet submitted a report 
    SELECT u.account_id, max(r.time) AS last_reported_time 
    FROM reports r 
    JOIN units u ON u.id = r.unit_id 
    GROUP BY 1) r ON r.account_id = a.id 
ORDER BY 2 DESC; 
+0

Ich denke, ich habe einen Index: "index_reports_on_unit_id" btree (unit_id). Und ein Fremdschlüssel Einschränkung: "reports_unit_id_fkey" FREMD KEY (unit_id) REFERENZEN units (id) – Donato

Verwandte Themen