2017-03-03 3 views
0

Ich versuche, die Abfrage zu bekommen jedes Jahr mit einer Zählung der Anzahl der Filme in diesem Jahr, die Castings hatte, die alle nicht männlich waren (für jedes Jahr, zählen Sie die Anzahl der Filme in diesem Jahr, das keine Männer hatte).Langsame Leistung mit NOT IN

Dies sind die Tabellen:

ACTOR (id, fname, lname, gender) 
MOVIE (id, name, year) 
CASTS (pid, mid, role) -- pid refers to actor id, mid refers to movie id 

Dies ist, was ich indiziert (id für die Tabellen Primärschlüssel sind, so dass sie bereits indiziert, oder so gehe ich davon aus):

CREATE INDEX gender_index on actor(gender); 
CREATE INDEX movie_name_index on movie(name); 
CREATE INDEX movie_year_index on movie(year); 
CREATE INDEX casts_index on casts(pid, mid, role); 
CREATE INDEX casts_pid_index on casts(pid); 
CREATE INDEX casts_mid_index on casts(mid); 
CREATE INDEX casts_role_index on casts(role); 

Das ist meine Abfrage:

Die Abfrage dauert ewig (und nie abgeschlossen), also wie kann ich mach das schnell? Hilft das mit Hilfe von NOT EXISTS, obwohl ich dachte, der Optimizer kümmert sich darum? Muss ich etwas anderes indizieren? Gibt es eine andere Frage, die besser ist? Ich benutze PostgreSQL, wenn dies einen Unterschied macht. Hier

ist die EXPLAIN:

"GroupAggregate (cost=1512539.61..171886457832.52 rows=61 width=8)" 
" Group Key: m.year" 
" -> Index Scan using movie_year_index on movie m (cost=1512539.61..171886453988.38 rows=768706 width=8)" 
"  Filter: (NOT (SubPlan 1))" 
"  SubPlan 1" 
"   -> Materialize (cost=1512539.18..1732298.66 rows=1537411 width=4)" 
"    -> Unique (cost=1512539.18..1718605.60 rows=1537411 width=4)" 
"      -> Merge Join (cost=1512539.18..1700559.32 rows=7218511 width=4)" 
"       Merge Cond: (m_1.id = c.mid)" 
"       -> Index Only Scan using movie_pkey on movie m_1 (cost=0.43..57863.94 rows=1537411 width=4)" 
"       -> Materialize (cost=1512531.37..1548623.92 rows=7218511 width=4)" 
"         -> Sort (cost=1512531.37..1530577.65 rows=7218511 width=4)" 
"          Sort Key: c.mid" 
"          -> Hash Join (cost=54546.59..492838.95 rows=7218511 width=4)" 
"            Hash Cond: (c.pid = a.id)" 
"            -> Seq Scan on casts c (cost=0.00..186246.43 rows=11445843 width=8)" 
"            -> Hash (cost=35248.91..35248.91 rows=1176214 width=4)" 
"             -> Seq Scan on actor a (cost=0.00..35248.91 rows=1176214 width=4)" 
"               Filter: ((gender)::text = 'M'::text)" 
+5

Was wie plant die 'EXPLAIN' aussehen? – jmelesky

+0

Hinzugefügt EXPLAIN-Ausgabe. – Jack

+0

1) Anstelle des Bündels von (nicht eindeutigen) Indizes: Definieren Sie einige Primärschlüssel und Fremdschlüssel für die Tabellen. Machen Sie sie NOT NULL wo angemessen. 2) 'Vacuum analysis 'alle beteiligten Tabellen. – joop

Antwort

3

würde ich

SELECT m.year, count(m.id) 
FROM movie m 
WHERE NOT EXISTS (
    SELECT NULL 
    FROM casts c, actor a 
    WHERE m.id = c.mid and a.id = c.pid and a.gender = 'M' 
) 
GROUP BY m.year 
ORDER BY m.year 
+0

Warum funktioniert 'NOT EXISTS' besser als' NOT IN'? Sollte der Query Optimizer nicht beides optimieren und keinen Unterschied machen? – Jack

+0

[Siehe diese Frage/Antwort] (http://stackoverflow.com/questions/24929/difference-between-exists-and-in-in-sql) –

+0

@Jack: Überprüfen Sie den Ausführungsplan, und Sie werden wissen –

2

Zuerst versuchen, verwenden Sie die richtige explizite JOIN Syntax. Zweitens verwenden eine korrelierte Unterabfrage statt NOT IN:

SELECT m.year, count(m.id) 
FROM movie m 
WHERE NOT EXISTS (SELECT 
        FROM casts c JOIN 
         actor a 
         ON a.id = c.pid 
        WHERE m.id = c.mid AND a.gender = 'M' 
       ) 
GROUP BY m.year 
ORDER BY m.year; 

aber meine Neigung wäre bedingte Aggregation zu verwenden:

SELECT m.year, SUM(CASE WHEN num_m = 0 THEN 1 ELSE 0 END) as cnt 
FROM (SELECT m.id, m.year, 
      SUM(CASE WHEN a.gender = 'M' THEN 1 ELSE 0 END) as num_m 
     FROM movie m JOIN 
      casts c 
      ON m.id = c.mid JOIN 
      actor a 
      ON a.id = c.pid 
     GROUP BY m.id, m.year 
    ) m 
GROUP BY m.year 
ORDER BY m.year; 
+0

Dies verwirrt mich, warum die Verwendung einer korrelierten Unterabfrage besser funktionieren würde als das 'NOT IN'. Ist es nicht ineffizienter, die innere Abfrage für jede von der äußeren Abfrage verarbeitete Zeile auszuwerten? Und sollte der Abfrageoptimierer das 'NOT IN' nicht optimieren, so dass es den gleichen Abfrageplan wie' NOT EXISTS' gibt? – Jack

+0

@Jack. . . Ihre Version der Abfrage verhindert ziemlich genau, dass der Optimierer Indizes für das "NOT IN" verwendet.Aber noch wichtiger ist, dass 'NOT IN' im Allgemeinen nicht das tut, was Sie wollen, wenn einer der zurückgegebenen Werte' NULL' ist, also ist es meine Gewohnheit, 'NOT EXISTS' mit einer Unterabfrage zu verwenden. –

0

Mit IN führt viel besser als NOT IN. Warum ändern Sie Ihre Abfrage nicht so, dass sie Datensätze enthält, statt sie auszuschließen?

Anstatt also Männchen in der Abfrage auszuschließen ...

SELECT m.year, count(m.id) 
FROM movie as m 
WHERE m.id NOT IN (
    SELECT DISTINCT m.id 
    FROM movie as m, casts as c, actor as a 
    WHERE m.id = c.mid and a.id = c.pid and a.gender = 'M' 
) 
GROUP BY m.year 
ORDER BY m.year 

Nur die Weibchen wählen ..

SELECT m.year, count(m.id) 
FROM movie as m 
WHERE m.id IN (
    SELECT DISTINCT m.id 
    FROM movie as m, casts as c, actor as a 
    WHERE m.id = c.mid and a.id = c.pid and a.gender = 'F' 
) 
GROUP BY m.year 
ORDER BY m.year 
Verwandte Themen