Cara memfilter hasil SQL dalam relasi has-many-through

100

Asumsi saya memiliki tabel student, clubdan student_club:

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

Saya ingin tahu bagaimana menemukan semua siswa di klub sepak bola (30) dan bisbol (50).
Meskipun kueri ini tidak berfungsi, ini adalah hal terdekat yang saya miliki sejauh ini:

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
Xeoncross
sumber

Jawaban:

145

Saya penasaran. Dan seperti yang kita semua tahu, rasa ingin tahu memiliki reputasi untuk membunuh kucing.

Lantas, manakah cara tercepat menguliti kucing?

Lingkungan pengelupasan kucing yang tepat untuk tes ini:

  • PostgreSQL 9.0 di Debian Squeeze dengan RAM dan pengaturan yang layak.
  • 6.000 siswa, 24.000 keanggotaan klub (data disalin dari database yang mirip dengan data kehidupan nyata.)
  • Sedikit pengalihan dari skema penamaan dalam pertanyaan: student.idis student.stud_idand club.idis club.club_idhere.
  • Saya menamai kueri setelah penulisnya di utas ini, dengan indeks di mana ada dua.
  • Saya menjalankan semua kueri beberapa kali untuk mengisi cache, lalu saya memilih yang terbaik dari 5 kueri dengan JELASKAN ANALISIS.
  • Indeks yang relevan (harus optimal - selama kita kekurangan pengetahuan sebelumnya tentang klub mana yang akan ditanyai):

    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_pkeytidak diperlukan oleh sebagian besar kueri di sini.
    Kunci utama menerapkan indeks unik secara otomatis di PostgreSQL.
    Indeks terakhir adalah untuk menutupi kekurangan yang diketahui dari indeks multi-kolom di PostgreSQL:

Indeks pohon-B multikolom dapat digunakan dengan kondisi kueri yang melibatkan subset apa pun dari kolom indeks, tetapi indeks paling efisien ketika ada batasan pada kolom terdepan (paling kiri).

Hasil:

Total waktu proses dari 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 md

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 md

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;

Tiga yang terakhir tampil hampir sama. 4) dan 5) menghasilkan rencana kueri yang sama.

Penambahan Akhir:

SQL mewah, tetapi kinerjanya tidak dapat mengimbangi.

7) ypercube 1: 148.649 md

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

Seperti yang diharapkan, keduanya tampil hampir sama. Hasil rencana kueri dalam pemindaian tabel, perencana tidak menemukan cara untuk menggunakan indeks di sini.


9) wildplasser 1: 49,849 md

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;

SQL yang mewah, performa yang layak untuk CTE. Rencana kueri yang sangat eksotis.
Sekali lagi, akan menarik bagaimana 9.1 menangani ini. Saya akan segera memutakhirkan kluster db yang digunakan di sini menjadi 9.1. Mungkin aku akan memutar ulang seluruh shebang ...


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

Varian CTE dari kueri 2). Anehnya, ini bisa menghasilkan rencana kueri yang sedikit berbeda dengan data yang sama persis. Saya menemukan pemindaian sekuensial student, di mana subkueri-varian menggunakan indeks.


11) ypercube 3: 101,482 md

Tambahan terlambat lainnya @ypercube. Sungguh luar biasa, ada banyak cara.

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 md

@ ypercube's 11) sebenarnya hanyalah pendekatan terbalik yang memutarbalikkan pikiran dari varian yang lebih sederhana ini, yang juga masih hilang. Berkinerja hampir secepat kucing teratas.

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

Sulit dipercaya, tapi inilah varian lain yang benar-benar baru. Saya melihat potensi untuk lebih dari dua keanggotaan, tetapi itu juga termasuk di antara kucing teratas dengan hanya dua.

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
   )

Jumlah dinamis keanggotaan klub

Dengan kata lain: jumlah filter yang bervariasi. Pertanyaan ini menanyakan tepat dua keanggotaan klub. Tetapi banyak kasus penggunaan harus mempersiapkan jumlah yang bervariasi.

Diskusi mendetail dalam jawaban selanjutnya yang terkait ini:

Erwin Brandstetter
sumber
1
Brandstetter, Kerja yang sangat bagus. Saya mulai memberikan hadiah atas pertanyaan ini untuk memberi Anda kredit ekstra (tapi saya harus menunggu 24 jam). Bagaimanapun, saya bertanya-tanya bagaimana kueri ini berjalan ketika Anda mulai menambahkan beberapa club_id daripada hanya dua ...
Xeoncross
@ Xeoncross: Kudos atas sikap dermawan Anda. :) Dengan lebih banyak club_ids saya curiga bahwa 1) dan 2) akan semakin dekat dalam kecepatan, tetapi itu harus menjadi angka yang lebih besar untuk menjatuhkan peringkat.
Erwin Brandstetter
Jika Anda memiliki lebih dari beberapa klub maka buat tabel lain yang berisi klub-klub tersebut. Kemudian gabung ke tabel itu di pilih Anda.
Paul Morgan
@ Erwin: Thnx (untuk benchmark). Bukan nitpicking, tapi mungkin Anda bisa mencoba kueri itu (maksud saya semua, bukan hanya milik saya) dengan (student_id, club_id)indeks (atau sebaliknya).
ypercubeᵀᴹ
3
Apakah saya salah dalam berpikir bahwa apa pun yang di bawah 200 ms adalah kinerja yang dapat diterima, mengingat domain yang dimaksud dan ukuran sampelnya? Untuk kepentingan pribadi, saya melakukan pengujian sendiri pada SQL Server 2008 R2 menggunakan indeks struktur yang sama dan (menurut saya) penyebaran data tetapi menskalakan ke satu juta siswa (saya rasa cukup besar untuk domain yang diberikan) dan masih belum ada Tidak banyak yang memisahkan pendekatan yang berbeda, IMO. Tentu saja, yang didasarkan pada divisi relasional dapat menargetkan tabel dasar, memberi mereka keuntungan dari 'ekstensibilitas'.
onedaywhen
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
Sean
sumber
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)
Derek Kromm
sumber
Kueri ini berfungsi dengan baik, tetapi sesuatu yang mengganggu saya karena harus meminta RDBMS untuk memeriksa begitu banyak indeks * jumlah klub.
Xeoncross
6
Saya paling suka query ini karena menyerupai gaya yang bersih, seperti python di sql. Saya akan dengan senang hati memperdagangkan 0.44ms (berbeda dengan kueri Sean) untuk kode semacam ini.
MGP
5

Jika Anda hanya ingin student_id maka:

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

Jika Anda juga membutuhkan nama dari siswa maka:

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 )

Jika Anda memiliki lebih dari dua klub dalam tabel club_selection maka:

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 ) )
Paul Morgan
sumber
Dua yang pertama termasuk dalam / sama dengan kueri saya 1. Tetapi yang ketiga membahas pertanyaan yang ditambahkan @Xeoncross di komentar di atas. Saya akan memilih bagian itu tanpa penipuan.
Erwin Brandstetter
Terima kasih atas komentarnya tetapi saya juga mendemonstrasikan beberapa pemformatan. Saya akan membiarkannya 'apa adanya'.
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)  

Atau solusi yang lebih umum lebih mudah untuk diperluas ke nklub dan yang menghindari INTERSECT(tidak tersedia di MySQL) dan IN(karena kinerja ini menyebalkan di 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  
Martin Smith
sumber
Tanpa ragu, jawaban kedua Anda adalah yang terbaik untuk kueri yang dihasilkan oleh kode. Apakah saya serius akan menulis 10 penggabungan atau subkueri untuk menemukan pembagian relasional dari 10 kriteria? Heck tidak, saya akan menggunakan solusi brilian ini. Terima kasih telah mengajari saya apa yang HAVINGdilakukan di MySQL.
Eric L.
4

CTE lainnya. Kelihatannya bersih, tapi mungkin akan menghasilkan rencana yang sama seperti groupby di subkueri normal.

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

Bagi yang mau test, copy hasil testdata saya:

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)
    ;
wildplasser
sumber
Ya, itu berlaku hanya subkueri dengan grup dengan suka di versi pertama saya. Rencana kueri yang sama + overhead CTE menghasilkan kinerja yang sama + sedikit untuk CTE. Pengaturan pengujian yang bagus.
Erwin Brandstetter
Saya tidak tahu apakah ada overhead CTE. Distribusi data pengujian sangat penting. Begitu juga ketersediaan statistik: setelah ANALISIS VAKUM, waktu berjalan berubah dari 67,4 menjadi 1,56 ms. Hanya hash dan bitmap yang terlibat dalam QP.
wildplasser
Itu istimewa dalam kasus Anda, setelah menghapus 80% dari tabel besar dan memperbarui banyak, Anda memiliki lebih banyak tupel mati daripada yang lainnya. Tidak heran, analisis vakum sangat membantu. Saya menjalankan kedua varian dengan dan tanpa CTE, dan yang mengejutkan, rencana kueri tidak identik. atau lebih baik lagi, saya akan membuka ruang obrolan untuk itu.
Erwin Brandstetter
Jangan khawatir, saya tahu tentang 80% baris mati ... Saya pikir statistik juga penting. Tetapi histogramnya agak 'datar', diberi penghapusan secara acak. Mungkin hanya perkiraan halaman yang diperlukan yang cukup berubah untuk membuat perencana memutuskan untuk beralih rencana.
wildplasser
3

Jadi, ada lebih dari satu cara untuk menguliti kucing .
Saya akan menambahkan dua lagi untuk membuatnya, lebih lengkap.

1) GRUP terlebih dahulu, GABUNG nanti

Dengan asumsi model data waras mana (student_id, club_id)yang unik di student_club. Versi kedua Martin Smith agak mirip, tetapi dia bergabung lebih dulu, kemudian berkelompok. Ini harus lebih cepat:

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

Dan tentu saja, ada yang klasik EXISTS . Mirip dengan varian Derek dengan IN. Sederhana dan cepat. (Di MySQL, ini seharusnya sedikit lebih cepat daripada varian dengan 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);
Erwin Brandstetter
sumber
3

Karena tidak ada yang menambahkan versi (klasik) ini:

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

atau serupa:

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

Sekali lagi mencoba dengan pendekatan yang sedikit berbeda. Terinspirasi oleh artikel di Explain Extended: Multiple atribut dalam tabel EAV: GRUP OLEH vs. TIDAK ADA :

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

Pendekatan lain:

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 ;   
ypercubeᵀᴹ
sumber
+1 .. tambahan yang bagus untuk koleksi kulit kucing yang tidak begitu lengkap! :) Saya menambahkannya ke benchmark.
Erwin Brandstetter
Ini bukan pertarungan yang adil :) Keuntungan besar dari pembagian relasional seperti ini adalah pembagi dapat menjadi tabel dasar sehingga mengubah pembagi sangat murah yaitu kontras memperbarui baris dalam tabel dasar yang ditargetkan oleh kueri yang sama dengan mengubah SQL kueri setiap kali.
onedaywhen
@ErwinBrandstetter: Apakah mungkin menambahkan variasi ke-3 dalam pengujian Anda?
ypercubeᵀᴹ
@ypercube: Anda mengerti. Versi yang cukup bengkok. :)
Erwin Brandstetter
1
@ Erwin: Ketika Anda berhasil membuang waktu untuk ini, dapatkah Anda juga mencoba memiliki dua Kunci UNIK, pada keduanya (stud_id, club_id)dan (club_id, stud_id)(atau Utama dan Unik)? Saya masih berpikir bahwa untuk beberapa kueri tersebut, perbedaan dari 2 hingga 140 ms terlalu tinggi untuk dijelaskan oleh perbedaan rencana eksekusi.
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

    ;

Ini tampaknya bekerja dengan cukup baik, karena CTE-scan menghindari kebutuhan akan dua subkueri terpisah.

Selalu ada alasan untuk menyalahgunakan kueri rekursif!

(BTW: mysql tampaknya tidak memiliki kueri rekursif)

wildplasser
sumber
1 untuk menemukan cara lain yang setengah jalan untuk itu! Saya menambahkan kueri Anda ke tolok ukur. Semoga tidak masalah bagi Anda. :)
Erwin Brandstetter
Tidak apa-apa. Tapi itu dimaksudkan sebagai lelucon, tentu saja. CTE sebenarnya bekerja dengan baik jika lebih banyak catatan klub siswa yang 'tersesat' ditambahkan. (Untuk pengujian saya menggunakan 1000 siswa * 100 klub, dan menghapus 80% secara acak)
wildplasser
1

Rencana kueri yang berbeda dalam kueri 2) dan 10)

Saya menguji db kehidupan nyata, jadi namanya berbeda dari daftar kulit kucing. Ini adalah salinan cadangan, jadi tidak ada yang berubah selama semua pengujian berjalan (kecuali perubahan kecil pada katalog).

Pertanyaan 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

Pertanyaan 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
Erwin Brandstetter
sumber
@wildplasser: Lihat rencana kueri yang berbeda! Tidak terduga bagi saya. hal 9.0. Ruang obrolan sulit digunakan, jadi saya menyalahgunakan jawaban di sini.
Erwin Brandstetter
Adegan aneh. Pada dasarnya QP yang sama di sini (9.0.1-beta-sesuatu) untuk CTE: seq scan + bitmap daripada indeks scan + merge. Mungkin kekurangan dalam heuristik biaya pengoptimal? Aku akan membuat penyalahgunaan CTE lagi ...
wildplasser
1

@ erwin-brandstetter Tolong, patokan ini:

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;

Ini seperti nomor 6) oleh @sean, hanya lebih bersih, kurasa.

Taai
sumber
2
Anda harus tahu bahwa @-memberitahu hanya berfungsi di komentar, bukan di jawaban. Saya menemukan posting ini secara kebetulan. Rencana kueri dan kinerja kueri Anda identik dengan kueri Sean. Ini secara efektif sama, tetapi kueri Sean dengan JOINsintaks eksplisit adalah bentuk yang umumnya disukai, karena lebih jelas. 1 lagi untuk jawaban valid lainnya!
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)
    ;

Rencana kueri:

 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)

Jadi sepertinya masih menginginkan seq scan pada siswa.

wildplasser
sumber
Tidak sabar untuk melihat apakah itu telah diperbaiki di 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
;

Penggunaan varian tercepat (Mr. Sean di grafik Mr. Brandstetter). Mungkin varian dengan hanya satu gabungan hanya matriks student_club memiliki hak untuk hidup. Jadi, kueri terpanjang hanya akan memiliki dua kolom untuk dihitung, idenya adalah membuat kueri tipis.

Stepan Pavlov
sumber
1
Meskipun cuplikan kode ini dapat menyelesaikan pertanyaan, menyertakan penjelasan sangat membantu meningkatkan kualitas posting Anda. Ingatlah bahwa Anda menjawab pertanyaan untuk pembaca di masa depan, bukan hanya orang yang bertanya sekarang! Harap edit jawaban Anda untuk menambahkan penjelasan, dan berikan indikasi batasan dan asumsi apa yang berlaku.
BrokenBinary