Come filtrare i risultati SQL in una relazione has-many-through


100

Supponendo che ho i tavoli student, clube student_club:

student {
    id
    name
}
club {
    id
    name
}
student_club {
    student_id
    club_id
}

Voglio sapere come trovare tutti gli studenti sia nel club di calcio (30) che di baseball (50).
Sebbene questa query non funzioni, è la cosa più vicina che ho finora:

SELECT student.*
FROM   student
INNER  JOIN student_club sc ON student.id = sc.student_id
LEFT   JOIN club c ON c.id = sc.club_id
WHERE  c.id = 30 AND c.id = 50

Risposte:


145

Ero curioso. E come tutti sappiamo, la curiosità ha la reputazione di uccidere i gatti.

Allora, qual è il modo più veloce per scuoiare un gatto?

L'ambiente preciso della pelle di gatto per questo test:

  • PostgreSQL 9.0 su Debian Squeeze con RAM e impostazioni decenti.
  • 6.000 studenti, 24.000 iscrizioni a club (dati copiati da un database simile con dati di vita reale).
  • Leggera deviazione dallo schema di denominazione nella domanda: student.idè student.stud_ided club.idè club.club_idqui.
  • Ho chiamato le query in base al loro autore in questo thread, con un indice in cui ce ne sono due.
  • Ho eseguito tutte le query un paio di volte per popolare la cache, quindi ho scelto la migliore delle 5 con EXPLAIN ANALYZE.
  • Indici pertinenti (dovrebbero essere ottimali, a patto che manchino delle conoscenze preliminari su quali club verranno interrogati):

    ALTER TABLE student ADD CONSTRAINT student_pkey PRIMARY KEY(stud_id );
    ALTER TABLE student_club ADD CONSTRAINT sc_pkey PRIMARY KEY(stud_id, club_id);
    ALTER TABLE club       ADD CONSTRAINT club_pkey PRIMARY KEY(club_id );
    CREATE INDEX sc_club_id_idx ON student_club (club_id);

    club_pkeynon è richiesto dalla maggior parte delle query qui.
    Le chiavi primarie implementano automaticamente indici univoci in PostgreSQL.
    L'ultimo indice serve a compensare questo noto difetto degli indici multi-colonna su PostgreSQL:

Un indice B-tree a più colonne può essere utilizzato con condizioni di query che coinvolgono qualsiasi sottoinsieme delle colonne dell'indice, ma l'indice è più efficiente quando sono presenti vincoli sulle colonne iniziali (più a sinistra).

risultati:

Autonomia totale da EXPLAIN ANALYZE.

1) Martin 2: 44,594 ms

SELECT s.stud_id, s.name
FROM   student s
JOIN   student_club sc USING (stud_id)
WHERE  sc.club_id IN (30, 50)
GROUP  BY 1,2
HAVING COUNT(*) > 1;

2) Erwin 1: 33.217 ms

SELECT s.stud_id, s.name
FROM   student s
JOIN   (
   SELECT stud_id
   FROM   student_club
   WHERE  club_id IN (30, 50)
   GROUP  BY 1
   HAVING COUNT(*) > 1
   ) sc USING (stud_id);

3) Martin 1: 31,735 ms

SELECT s.stud_id, s.name
   FROM   student s
   WHERE  student_id IN (
   SELECT student_id
   FROM   student_club
   WHERE  club_id = 30
   INTERSECT
   SELECT stud_id
   FROM   student_club
   WHERE  club_id = 50);

4) Derek: 2,287 ms

SELECT s.stud_id,  s.name
FROM   student s
WHERE  s.stud_id IN (SELECT stud_id FROM student_club WHERE club_id = 30)
AND    s.stud_id IN (SELECT stud_id FROM student_club WHERE club_id = 50);

5) Erwin 2: 2.181 ms

SELECT s.stud_id,  s.name
FROM   student s
WHERE  EXISTS (SELECT * FROM student_club
               WHERE  stud_id = s.stud_id AND club_id = 30)
AND    EXISTS (SELECT * FROM student_club
               WHERE  stud_id = s.stud_id AND club_id = 50);

6) Sean: 2.043 ms

SELECT s.stud_id, s.name
FROM   student s
JOIN   student_club x ON s.stud_id = x.stud_id
JOIN   student_club y ON s.stud_id = y.stud_id
WHERE  x.club_id = 30
AND    y.club_id = 50;

Gli ultimi tre si comportano più o meno allo stesso modo. 4) e 5) risultano nello stesso piano di query.

Aggiunte tardive:

Fantastico SQL, ma le prestazioni non possono tenere il passo.

7) ypercube 1: 148.649 ms

SELECT s.stud_id,  s.name
FROM   student AS s
WHERE  NOT EXISTS (
   SELECT *
   FROM   club AS c 
   WHERE  c.club_id IN (30, 50)
   AND    NOT EXISTS (
      SELECT *
      FROM   student_club AS sc 
      WHERE  sc.stud_id = s.stud_id
      AND    sc.club_id = c.club_id  
      )
   );

8) ypercube 2: 147,497 ms

SELECT s.stud_id,  s.name
FROM   student AS s
WHERE  NOT EXISTS (
   SELECT *
   FROM  (
      SELECT 30 AS club_id  
      UNION  ALL
      SELECT 50
      ) AS c
   WHERE NOT EXISTS (
      SELECT *
      FROM   student_club AS sc 
      WHERE  sc.stud_id = s.stud_id
      AND    sc.club_id = c.club_id  
      )
   );

Come previsto, quei due si comportano quasi allo stesso modo. I risultati del piano di query nelle scansioni di tabelle, il pianificatore non trova un modo per utilizzare gli indici qui.


9) wildplasser 1: 49.849 ms

WITH RECURSIVE two AS (
   SELECT 1::int AS level
        , stud_id
   FROM   student_club sc1
   WHERE  sc1.club_id = 30
   UNION
   SELECT two.level + 1 AS level
        , sc2.stud_id
   FROM   student_club sc2
   JOIN   two USING (stud_id)
   WHERE  sc2.club_id = 50
   AND    two.level = 1
   )
SELECT s.stud_id, s.student
FROM   student s
JOIN   two USING (studid)
WHERE  two.level > 1;

Fancy SQL, prestazioni decenti per un CTE. Piano di query molto esotico.
Ancora una volta, sarebbe interessante come la 9.1 gestisce questo. Presto aggiornerò il cluster db usato qui alla 9.1. Forse rieseguirò l'intera faccenda ...


10) wildplasser 2: 36.986 ms

WITH sc AS (
   SELECT stud_id
   FROM   student_club
   WHERE  club_id IN (30,50)
   GROUP  BY stud_id
   HAVING COUNT(*) > 1
   )
SELECT s.*
FROM   student s
JOIN   sc USING (stud_id);

Variante CTE della query 2). Sorprendentemente, può risultare in un piano di query leggermente diverso con gli stessi identici dati. Ho trovato una scansione sequenziale su student, in cui la variante della sottoquery utilizzava l'indice.


11) ypercube 3: 101,482 ms

Un'altra aggiunta tardiva @ypercube. È davvero sorprendente quanti modi ci siano.

SELECT s.stud_id, s.student
FROM   student s
JOIN   student_club sc USING (stud_id)
WHERE  sc.club_id = 10                 -- member in 1st club ...
AND    NOT EXISTS (
   SELECT *
   FROM  (SELECT 14 AS club_id) AS c  -- can't be excluded for missing the 2nd
   WHERE  NOT EXISTS (
      SELECT *
      FROM   student_club AS d
      WHERE  d.stud_id = sc.stud_id
      AND    d.club_id = c.club_id
      )
   )

12) erwin 3: 2.377 ms

@ ypercube's 11) è in realtà solo l'approccio inverso che stravolge la mente di questa variante più semplice, che mancava ancora. Esegue quasi veloce quanto i migliori gatti.

SELECT s.*
FROM   student s
JOIN   student_club x USING (stud_id)
WHERE  sc.club_id = 10                 -- member in 1st club ...
AND    EXISTS (                        -- ... and membership in 2nd exists
   SELECT *
   FROM   student_club AS y
   WHERE  y.stud_id = s.stud_id
   AND    y.club_id = 14
   )

13) erwin 4: 2.375 ms

Difficile da credere, ma ecco un'altra variante veramente nuova. Vedo il potenziale per più di due abbonamenti, ma si colloca anche tra i migliori gatti con solo due.

SELECT s.*
FROM   student AS s
WHERE  EXISTS (
   SELECT *
   FROM   student_club AS x
   JOIN   student_club AS y USING (stud_id)
   WHERE  x.stud_id = s.stud_id
   AND    x.club_id = 14
   AND    y.club_id = 10
   )

Numero dinamico di iscrizioni al club

In altre parole: numero variabile di filtri. Questa domanda richiedeva esattamente due iscrizioni al club. Ma molti casi d'uso devono prepararsi per un numero variabile.

Discussione dettagliata in questa risposta successiva correlata:


1
Brandstetter, ottimo lavoro. Ho iniziato una taglia su questa domanda per darti credito extra (ma devo aspettare 24 ore). Ad ogni modo, mi chiedo come vanno queste domande quando inizi ad aggiungere più club_id invece di solo due ...
Xeoncross

@Xeoncross: Complimenti per il tuo gesto generoso. :) Con più club_id sospetto che 1) e 2) si avvicineranno in velocità, ma dovrebbe essere un numero maggiore per far cadere la classifica.
Erwin Brandstetter

se hai più di un paio di mazze, crea un'altra tabella che contenga quelle mazze. Quindi unisciti a quel tavolo nella tua selezione.
Paul Morgan

@Erwin: Thnx (per i benchmark). Non pignoleria, ma forse puoi provare quelle query (intendo tutte, non solo le mie) con un (student_id, club_id)indice (o il contrario).
ypercubeᵀᴹ

3
Sbaglio a pensare che qualcosa al di sotto di 200 ms sia una prestazione accettabile, dato il dominio in questione e la dimensione del campione? Per interesse personale, ho eseguito i miei test su SQL Server 2008 R2 utilizzando gli stessi indici di struttura e (penso) la diffusione dei dati ma scalando fino a un milione di studenti (un insieme ragionevolmente grande per il dominio dato, credo) e non c'era ancora C'è molto da separare i diversi approcci, IMO. Ovviamente, quelli basati sulla divisione relazionale potrebbero avere come target una tabella di base, dando loro il vantaggio dell '"estensibilità".
giorno del

18
SELECT s.*
FROM student s
INNER JOIN student_club sc_soccer ON s.id = sc_soccer.student_id
INNER JOIN student_club sc_baseball ON s.id = sc_baseball.student_id
WHERE 
 sc_baseball.club_id = 50 AND 
 sc_soccer.club_id = 30

10
select *
from student
where id in (select student_id from student_club where club_id = 30)
and id in (select student_id from student_club where club_id = 50)

Questa query funziona bene, ma qualcosa mi dà fastidio nel dover chiedere all'RDBMS di controllare così tanti indici * il numero di club.
Xeoncross

6
Mi piace di più questa query perché assomiglia a uno stile pulito, è come Python in sql. Scambierei volentieri 0.44 ms (diff con la query di Sean) per questo tipo di codice.
MGP

5

Se vuoi solo student_id, allora:

    Select student_id
      from student_club
     where club_id in ( 30, 50 )
  group by student_id
    having count( student_id ) = 2

Se hai bisogno anche del nome dello studente, allora:

Select student_id, name
  from student s
 where exists( select *
                 from student_club sc
                where s.student_id = sc.student_id
                  and club_id in ( 30, 50 )
             group by sc.student_id
               having count( sc.student_id ) = 2 )

Se hai più di due club in una tabella club_selection, allora:

Select student_id, name
  from student s
 where exists( select *
                 from student_club sc
                where s.student_id = sc.student_id
                  and exists( select * 
                                from club_selection cs
                               where sc.club_id = cs.club_id )
             group by sc.student_id
               having count( sc.student_id ) = ( select count( * )
                                                   from club_selection ) )

I primi due sono inclusi / uguali alla mia query 1. Ma il terzo affronta la domanda aggiunta di @Xeoncross nei commenti sopra. Voterò per quella parte senza gli stupidi.
Erwin Brandstetter

Grazie per il commento ma sto anche dimostrando un po 'di formattazione. Lo lascio "così com'è".
Paul Morgan

4
SELECT *
FROM   student
WHERE  id IN (SELECT student_id
              FROM   student_club
              WHERE  club_id = 30
              INTERSECT
              SELECT student_id
              FROM   student_club
              WHERE  club_id = 50)  

O una soluzione più generale più facile da estendere ai nclub e che evita INTERSECT(non disponibile in MySQL) e IN(poiché le prestazioni di questo fanno schifo in MySQL )

SELECT s.id,
       s.name
FROM   student s
       join student_club sc
         ON s.id = sc.student_id
WHERE  sc.club_id IN ( 30, 50 )
GROUP  BY s.id,
          s.name
HAVING COUNT(DISTINCT sc.club_id) = 2  

Senza dubbio, la tua seconda risposta è la migliore per le query generate dal codice. Scriverò seriamente 10 join o sottoquery per trovare la divisione relazionale di 10 criteri? Diamine no, userò invece questa brillante soluzione. Grazie per avermi insegnato cosa HAVINGfa in MySQL.
Eric L.

4

Un altro CTE. Sembra pulito, ma probabilmente genererà lo stesso piano di un groupby in una normale sottoquery.

WITH two AS (
    SELECT student_id FROM tmp.student_club
    WHERE club_id IN (30,50)
    GROUP BY student_id
    HAVING COUNT(*) > 1
    )
SELECT st.* FROM tmp.student st
JOIN two ON (two.student_id=st.id)
    ;

Per coloro che vogliono testare, una copia della mia cosa generate testdata:

DROP SCHEMA tmp CASCADE;
CREATE SCHEMA tmp;

CREATE TABLE tmp.student
    ( id INTEGER NOT NULL PRIMARY KEY
    , sname VARCHAR
    );

CREATE TABLE tmp.club
    ( id INTEGER NOT NULL PRIMARY KEY
    , cname VARCHAR
    );

CREATE TABLE tmp.student_club
    ( student_id INTEGER NOT NULL  REFERENCES tmp.student(id)
    , club_id INTEGER NOT NULL  REFERENCES tmp.club(id)
    );

INSERT INTO tmp.student(id)
    SELECT generate_series(1,1000)
    ;

INSERT INTO tmp.club(id)
    SELECT generate_series(1,100)
    ;

INSERT INTO tmp.student_club(student_id,club_id)
    SELECT st.id  , cl.id
    FROM tmp.student st, tmp.club cl
    ;

DELETE FROM tmp.student_club
WHERE random() < 0.8
    ;

UPDATE tmp.student SET sname = 'Student#' || id::text ;
UPDATE tmp.club SET cname = 'Soccer' WHERE id = 30;
UPDATE tmp.club SET cname = 'Baseball' WHERE id = 50;

ALTER TABLE tmp.student_club
    ADD PRIMARY KEY (student_id,club_id)
    ;

Sì, è in effetti solo una sottoquery con gruppo di come nella mia prima versione. Stesso piano di query + overhead CTE si traducono in stesse prestazioni + un po 'per il CTE. Bella configurazione di prova, però.
Erwin Brandstetter

Non so se c'è un sovraccarico CTE. La distribuzione dei dati di test è molto importante. Così è la disponibilità delle statistiche: dopo VACUUM ANALYZE il tempo di esecuzione è passato da 67,4 a 1,56 ms. Solo hash e bitmap coinvolti nel QP.
wildplasser

Questo è speciale nel tuo caso, dopo aver eliminato l'80% di una grande tabella e aver aggiornato molto, hai avuto più tuple morte di qualsiasi altra cosa. Non c'è da stupirsi, l'analisi del vuoto aiuta molto. Ho eseguito entrambe le varianti con e senza CTE e sorprendentemente i piani di query non erano identici. o meglio ancora, aprirò una chat room per questo.
Erwin Brandstetter

Non preoccuparti, sapevo dell'80% dei file morti ... Penso che anche le statistiche contino. Ma l'istogramma è piuttosto "piatto", dato l'eliminazione casuale. Forse è solo la stima delle pagine necessarie che cambia abbastanza da far decidere al pianificatore di cambiare piano.
wildplasser

3

Quindi c'è più di un modo per scuoiare un gatto .
Aggiungerò altri due per renderlo, beh, più completo.

1) GRUPPO prima, UNISCITI dopo

Supponendo un modello di dati sano in cui (student_id, club_id)è unico in student_club. La seconda versione di Martin Smith è in qualche modo simile, ma si unisce prima, gruppi dopo. Questo dovrebbe essere più veloce:

SELECT s.id, s.name
  FROM student s
  JOIN (
   SELECT student_id
     FROM student_club
    WHERE club_id IN (30, 50)
    GROUP BY 1
   HAVING COUNT(*) > 1
       ) sc USING (student_id);

2) ESISTE

E ovviamente c'è il classico EXISTS. Simile alla variante di Derek con IN. Semplice e veloce. (In MySQL, questo dovrebbe essere un po 'più veloce della variante con IN):

SELECT s.id, s.name
  FROM student s
 WHERE EXISTS (SELECT 1 FROM student_club
               WHERE  student_id = s.student_id AND club_id = 30)
   AND EXISTS (SELECT 1 FROM student_club
               WHERE  student_id = s.student_id AND club_id = 50);

3

Poiché nessuno ha aggiunto questa versione (classica):

SELECT s.*
FROM student AS s
WHERE NOT EXISTS
      ( SELECT *
        FROM club AS c 
        WHERE c.id IN (30, 50)
          AND NOT EXISTS
              ( SELECT *
                FROM student_club AS sc 
                WHERE sc.student_id = s.id
                  AND sc.club_id = c.id  
              )
      )

o simili:

SELECT s.*
FROM student AS s
WHERE NOT EXISTS
      ( SELECT *
        FROM
          ( SELECT 30 AS club_id  
          UNION ALL
            SELECT 50
          ) AS c
        WHERE NOT EXISTS
              ( SELECT *
                FROM student_club AS sc 
                WHERE sc.student_id = s.id
                  AND sc.club_id = c.club_id  
              )
      )

Un altro tentativo con un approccio leggermente diverso. Ispirato da un articolo in Explain Extended: Più attributi in una tabella EAV: GROUP BY vs. NOT EXISTS :

SELECT s.*
FROM student_club AS sc
  JOIN student AS s
    ON s.student_id = sc.student_id
WHERE sc.club_id = 50                      --- one option here
  AND NOT EXISTS
      ( SELECT *
        FROM
          ( SELECT 30 AS club_id           --- all the rest in here
                                           --- as in previous query
          ) AS c
        WHERE NOT EXISTS
              ( SELECT *
                FROM student_club AS scc 
                WHERE scc.student_id = sc.id
                  AND scc.club_id = c.club_id  
              )
      )

Un altro approccio:

SELECT s.stud_id
FROM   student s

EXCEPT

SELECT stud_id
FROM 
  ( SELECT s.stud_id, c.club_id
    FROM student s 
      CROSS JOIN (VALUES (30),(50)) c (club_id)
  EXCEPT
    SELECT stud_id, club_id
    FROM student_club
    WHERE club_id IN (30, 50)   -- optional. Not needed but may affect performance
  ) x ;   

+1 .. belle aggiunte alla non così completa collezione di pelli di gatto! :) Li ho aggiunti al benchmark.
Erwin Brandstetter

Non è una lotta leale :) Il grande vantaggio di una divisione relazionale come questaèil divisore può essere una tabella di base in modo che alterare il divisore sia molto economico, es. Contrastare l'aggiornamento delle righe in una tabella di base mirata dalla stessa query con la modifica dell'SQL interrogare ogni volta.
giorno del

@ErwinBrandstetter: sarebbe possibile aggiungere la terza variazione nei tuoi test?
ypercubeᵀᴹ

@ypercube: hai capito. Versione piuttosto contorta. :)
Erwin Brandstetter

1
@Erwin: Quando riesci a perdere tempo su questo, puoi anche provare ad avere due chiavi UNICHE, su entrambe (stud_id, club_id)e (club_id, stud_id)(o Primaria e Unica)? Continuo a pensare che per alcune di queste query, la differenza da 2 a 140 ms sia troppo alta per essere spiegata dalle differenze nei piani di esecuzione.
ypercubeᵀᴹ

2
WITH RECURSIVE two AS
    ( SELECT 1::integer AS level
    , student_id
    FROM tmp.student_club sc0
    WHERE sc0.club_id = 30
    UNION
    SELECT 1+two.level AS level
    , sc1.student_id
    FROM tmp.student_club sc1
    JOIN two ON (two.student_id = sc1.student_id)
    WHERE sc1.club_id = 50
    AND two.level=1
    )
SELECT st.* FROM tmp.student st
JOIN two ON (two.student_id=st.id)
WHERE two.level> 1

    ;

Questo sembra funzionare abbastanza bene, poiché la scansione CTE evita la necessità di due sottoquery separate.

C'è sempre un motivo per fare un uso improprio delle query ricorsive!

(BTW: mysql non sembra avere query ricorsive)


+1 per aver trovato un altro mezzo decente per arrivarci! Ho aggiunto la tua query al benchmark. Spero che per te sia ok. :)
Erwin Brandstetter

Va bene. Ma era inteso come uno scherzo, ovviamente. CTE si comporta effettivamente bene se vengono aggiunti più record di studenti * randagi. (Per i test ho usato 1000 studenti * 100 club e cancellato l'80% in modo casuale)
wildplasser

1

Piani di query diversi nelle query 2) e 10)

Ho provato in un db di vita reale, quindi i nomi differiscono dalla lista dei gatti. È una copia di backup, quindi non è cambiato nulla durante tutte le esecuzioni di test (eccetto modifiche minori ai cataloghi).

Domanda 2)

SELECT a.*
FROM   ef.adr a
JOIN (
    SELECT adr_id
    FROM   ef.adratt
    WHERE  att_id IN (10,14)
    GROUP  BY adr_id
    HAVING COUNT(*) > 1) t using (adr_id);

Merge Join  (cost=630.10..1248.78 rows=627 width=295) (actual time=13.025..34.726 rows=67 loops=1)
  Merge Cond: (a.adr_id = adratt.adr_id)
  ->  Index Scan using adr_pkey on adr a  (cost=0.00..523.39 rows=5767 width=295) (actual time=0.023..11.308 rows=5356 loops=1)
  ->  Sort  (cost=630.10..636.37 rows=627 width=4) (actual time=12.891..13.004 rows=67 loops=1)
        Sort Key: adratt.adr_id
        Sort Method:  quicksort  Memory: 28kB
        ->  HashAggregate  (cost=450.87..488.49 rows=627 width=4) (actual time=12.386..12.710 rows=67 loops=1)
              Filter: (count(*) > 1)
              ->  Bitmap Heap Scan on adratt  (cost=97.66..394.81 rows=2803 width=4) (actual time=0.245..5.958 rows=2811 loops=1)
                    Recheck Cond: (att_id = ANY ('{10,14}'::integer[]))
                    ->  Bitmap Index Scan on adratt_att_id_idx  (cost=0.00..94.86 rows=2803 width=0) (actual time=0.217..0.217 rows=2811 loops=1)
                          Index Cond: (att_id = ANY ('{10,14}'::integer[]))
Total runtime: 34.928 ms

Domanda 10)

WITH two AS (
    SELECT adr_id
    FROM   ef.adratt
    WHERE  att_id IN (10,14)
    GROUP  BY adr_id
    HAVING COUNT(*) > 1
    )
SELECT a.*
FROM   ef.adr a
JOIN   two using (adr_id);

Hash Join  (cost=1161.52..1261.84 rows=627 width=295) (actual time=36.188..37.269 rows=67 loops=1)
  Hash Cond: (two.adr_id = a.adr_id)
  CTE two
    ->  HashAggregate  (cost=450.87..488.49 rows=627 width=4) (actual time=13.059..13.447 rows=67 loops=1)
          Filter: (count(*) > 1)
          ->  Bitmap Heap Scan on adratt  (cost=97.66..394.81 rows=2803 width=4) (actual time=0.252..6.252 rows=2811 loops=1)
                Recheck Cond: (att_id = ANY ('{10,14}'::integer[]))
                ->  Bitmap Index Scan on adratt_att_id_idx  (cost=0.00..94.86 rows=2803 width=0) (actual time=0.226..0.226 rows=2811 loops=1)
                      Index Cond: (att_id = ANY ('{10,14}'::integer[]))
  ->  CTE Scan on two  (cost=0.00..50.16 rows=627 width=4) (actual time=13.065..13.677 rows=67 loops=1)
  ->  Hash  (cost=384.68..384.68 rows=5767 width=295) (actual time=23.097..23.097 rows=5767 loops=1)
        Buckets: 1024  Batches: 1  Memory Usage: 1153kB
        ->  Seq Scan on adr a  (cost=0.00..384.68 rows=5767 width=295) (actual time=0.005..10.955 rows=5767 loops=1)
Total runtime: 37.482 ms

@wildplasser: guarda i piani di query divergenti! Inaspettato per me. pg 9.0. La chat room era ingombrante, quindi abuso di una risposta qui.
Erwin Brandstetter

Scene strane. Fondamentalmente lo stesso QP qui (9.0.1-beta-qualcosa) per il CTE: seq scan + bitmap invece di una index scan + merge. Forse un difetto nell'euristica dei costi dell'ottimizzatore?
Produrrò

1

@ erwin-brandstetter Per favore, confronta questo:

SELECT s.stud_id, s.name
FROM   student s, student_club x, student_club y
WHERE  x.club_id = 30
AND    s.stud_id = x.stud_id
AND    y.club_id = 50
AND    s.stud_id = y.stud_id;

È come il numero 6) di @sean, solo più pulito, immagino.


2
Devi sapere che la @notifica funziona solo nei commenti, non nelle risposte. Mi sono imbattuto in questo post per caso. Il piano e le prestazioni della query sono identici alla query di Sean. È effettivamente lo stesso, ma la query di Sean con JOINsintassi esplicita è la forma generalmente preferita, perché è più chiara. +1 per l'ennesima risposta valida, però!
Erwin Brandstetter

0
-- EXPLAIN ANALYZE
WITH two AS (
    SELECT c0.student_id
    FROM tmp.student_club c0
    , tmp.student_club c1
    WHERE c0.student_id = c1.student_id
    AND c0.club_id = 30
    AND c1.club_id = 50
    )
SELECT st.* FROM tmp.student st
JOIN two ON (two.student_id=st.id)
    ;

Il piano di query:

 Hash Join  (cost=1904.76..1919.09 rows=337 width=15) (actual time=6.937..8.771 rows=324 loops=1)
   Hash Cond: (two.student_id = st.id)
   CTE two
     ->  Hash Join  (cost=849.97..1645.76 rows=337 width=4) (actual time=4.932..6.488 rows=324 loops=1)
           Hash Cond: (c1.student_id = c0.student_id)
           ->  Bitmap Heap Scan on student_club c1  (cost=32.76..796.94 rows=1614 width=4) (actual time=0.667..1.835 rows=1646 loops=1)
                 Recheck Cond: (club_id = 50)
                 ->  Bitmap Index Scan on sc_club_id_idx  (cost=0.00..32.36 rows=1614 width=0) (actual time=0.473..0.473 rows=1646 loops=1)                     
                       Index Cond: (club_id = 50)
           ->  Hash  (cost=797.00..797.00 rows=1617 width=4) (actual time=4.203..4.203 rows=1620 loops=1)
                 Buckets: 1024  Batches: 1  Memory Usage: 57kB
                 ->  Bitmap Heap Scan on student_club c0  (cost=32.79..797.00 rows=1617 width=4) (actual time=0.663..3.596 rows=1620 loops=1)                   
                       Recheck Cond: (club_id = 30)
                       ->  Bitmap Index Scan on sc_club_id_idx  (cost=0.00..32.38 rows=1617 width=0) (actual time=0.469..0.469 rows=1620 loops=1)
                             Index Cond: (club_id = 30)
   ->  CTE Scan on two  (cost=0.00..6.74 rows=337 width=4) (actual time=4.935..6.591 rows=324 loops=1)
   ->  Hash  (cost=159.00..159.00 rows=8000 width=15) (actual time=1.979..1.979 rows=8000 loops=1)
         Buckets: 1024  Batches: 1  Memory Usage: 374kB
         ->  Seq Scan on student st  (cost=0.00..159.00 rows=8000 width=15) (actual time=0.093..0.759 rows=8000 loops=1)
 Total runtime: 8.989 ms
(20 rows)

Quindi sembra ancora volere la scansione seq su studente.


Non vedo l'ora di vedere se il problema è stato risolto nella 9.1.
Erwin Brandstetter

0
SELECT s.stud_id, s.name
FROM   student s,
(
select x.stud_id from 
student_club x 
JOIN   student_club y ON x.stud_id = y.stud_id
WHERE  x.club_id = 30
AND    y.club_id = 50
) tmp_tbl
where tmp_tbl.stud_id = s.stud_id
;

Uso della variante più veloce (Mr. Sean nella tabella Mr. Brandstetter). Può essere una variante con un solo join alla matrice student_club ha il diritto di vivere. Quindi, la query più lunga avrà solo due colonne da calcolare, l'idea è di rendere la query sottile.


1
Sebbene questo snippet di codice possa risolvere la domanda, includere una spiegazione aiuta davvero a migliorare la qualità del tuo post. Ricorda che stai rispondendo alla domanda per i lettori in futuro, non solo alla persona che lo chiede ora! Si prega di modificare la risposta di aggiungere una spiegazione, e dare un'indicazione di ciò si applicano le limitazioni e le assunzioni.
BrokenBinary
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.