Kueri SQL mana yang lebih cepat? Filter pada kriteria Bergabung atau klausa Dimana?

99

Bandingkan 2 kueri ini. Apakah lebih cepat menempatkan filter pada kriteria gabungan atau di WHEREklausa. Saya selalu merasa lebih cepat pada kriteria join karena mengurangi hasil yang ditetapkan secepat mungkin, tapi saya tidak tahu pasti.

Saya akan membuat beberapa tes untuk dilihat, tetapi saya juga ingin mendapatkan pendapat tentang mana yang lebih jelas untuk dibaca juga.

Pertanyaan 1

SELECT      *
FROM        TableA a
INNER JOIN  TableXRef x
        ON  a.ID = x.TableAID
INNER JOIN  TableB b
        ON  x.TableBID = b.ID
WHERE       a.ID = 1            /* <-- Filter here? */

Pertanyaan 2

SELECT      *
FROM        TableA a
INNER JOIN  TableXRef x
        ON  a.ID = x.TableAID
        AND a.ID = 1            /* <-- Or filter here? */
INNER JOIN  TableB b
        ON  x.TableBID = b.ID

EDIT

Saya menjalankan beberapa tes dan hasilnya menunjukkan bahwa sebenarnya sangat dekat, tetapi WHEREklausulnya sebenarnya sedikit lebih cepat! =)

Saya sangat setuju bahwa lebih masuk akal untuk menerapkan filter pada WHEREklausa, saya hanya ingin tahu tentang implikasi kinerjanya.

WAKTU TERLALU DI MANA KRITERIA: 143016 ms TERLALU
WAKTU BERGABUNG KRITERIA: 143256 ms

UJI

SET NOCOUNT ON;

DECLARE @num    INT,
        @iter   INT

SELECT  @num    = 1000, -- Number of records in TableA and TableB, the cross table is populated with a CROSS JOIN from A to B
        @iter   = 1000  -- Number of select iterations to perform

DECLARE @a TABLE (
        id INT
)

DECLARE @b TABLE (
        id INT
)

DECLARE @x TABLE (
        aid INT,
        bid INT
)

DECLARE @num_curr INT
SELECT  @num_curr = 1
        
WHILE (@num_curr <= @num)
BEGIN
    INSERT @a (id) SELECT @num_curr
    INSERT @b (id) SELECT @num_curr
    
    SELECT @num_curr = @num_curr + 1
END

INSERT      @x (aid, bid)
SELECT      a.id,
            b.id
FROM        @a a
CROSS JOIN  @b b

/*
    TEST
*/
DECLARE @begin_where    DATETIME,
        @end_where      DATETIME,
        @count_where    INT,
        @begin_join     DATETIME,
        @end_join       DATETIME,
        @count_join     INT,
        @curr           INT,
        @aid            INT

DECLARE @temp TABLE (
        curr    INT,
        aid     INT,
        bid     INT
)

DELETE FROM @temp

SELECT  @curr   = 0,
        @aid    = 50

SELECT  @begin_where = CURRENT_TIMESTAMP
WHILE (@curr < @iter)
BEGIN
    INSERT      @temp (curr, aid, bid)
    SELECT      @curr,
                aid,
                bid
    FROM        @a a
    INNER JOIN  @x x
            ON  a.id = x.aid
    INNER JOIN  @b b
            ON  x.bid = b.id
    WHERE       a.id = @aid
        
    SELECT @curr = @curr + 1
END
SELECT  @end_where = CURRENT_TIMESTAMP

SELECT  @count_where = COUNT(1) FROM @temp
DELETE FROM @temp

SELECT  @curr = 0
SELECT  @begin_join = CURRENT_TIMESTAMP
WHILE (@curr < @iter)
BEGIN
    INSERT      @temp (curr, aid, bid)
    SELECT      @curr,
                aid,
                bid
    FROM        @a a
    INNER JOIN  @x x
            ON  a.id = x.aid
            AND a.id = @aid
    INNER JOIN  @b b
            ON  x.bid = b.id
    
    SELECT @curr = @curr + 1
END
SELECT  @end_join = CURRENT_TIMESTAMP

SELECT  @count_join = COUNT(1) FROM @temp
DELETE FROM @temp

SELECT  @count_where AS count_where,
        @count_join AS count_join,
        DATEDIFF(millisecond, @begin_where, @end_where) AS elapsed_where,
        DATEDIFF(millisecond, @begin_join, @end_join) AS elapsed_join
Jon Erickson
sumber
10
Bergantung pada data, kriteria WHERE vs JOIN dapat mengembalikan kumpulan hasil yang berbeda.
OMG Ponies
4
@OMG Pony sangat benar, tetapi seringkali tidak juga.
Jon Erickson
2
Saya tidak akan menyebut perbedaan di bawah 5% sebagai perbedaan- mereka sama. Anda ingin signifikansi untuk perbedaan 2 %% dengan lebih baik menjalankan pengujian 1000 kali untuk memastikannya tidak hanya acak.
TomTom
Manfaatnya adalah memfilter data sebelum bergabung jadi jika itu x.ID maka Anda akan lebih cenderung melihat peningkatan daripada dengan a.ID
MikeT

Jawaban:

66

Dari segi kinerja, mereka sama (dan menghasilkan rencana yang sama)

Logikanya, Anda harus membuat operasi yang masih masuk akal jika Anda mengganti INNER JOINdengan file LEFT JOIN.

Dalam kasus Anda ini akan terlihat seperti ini:

SELECT  *
FROM    TableA a
LEFT JOIN
        TableXRef x
ON      x.TableAID = a.ID
        AND a.ID = 1
LEFT JOIN
        TableB b
ON      x.TableBID = b.ID

atau ini:

SELECT  *
FROM    TableA a
LEFT JOIN
        TableXRef x
ON      x.TableAID = a.ID
LEFT JOIN
        TableB b
ON      b.id = x.TableBID
WHERE   a.id = 1

Kueri sebelumnya tidak akan mengembalikan kecocokan aktual apa pun a.idselain dari 1, jadi sintaksis terakhir (dengan WHERE) secara logis lebih konsisten.

Quassnoi
sumber
Ketika saya menggambar set, saya mengerti mengapa kasus kedua lebih konsisten. Dalam kueri sebelumnya, batasan a.id = 1hanya berlaku untuk persimpangan, bukan bagian kiri tidak termasuk persimpangan.
FtheBuilder
1
Pada contoh pertama mungkin ada baris di mana a.id != 1, yang lain hanya memiliki baris di mana a.id = 1.
FtheBuilder
1
Bahasa Anda tidak jelas. "Secara logis, Anda harus membuat operasi yang masih masuk akal jika ..." dan "secara logis lebih konsisten" tidak masuk akal. Bisakah Anda mengubah kalimatnya?
philipxy
24

Untuk gabungan dalam tidak masalah di mana Anda meletakkan kriteria Anda. Kompilator SQL akan mengubah keduanya menjadi rencana eksekusi di mana pemfilteran terjadi di bawah gabungan (mis. Seolah-olah ekspresi filter muncul dalam kondisi gabungan).

Gabungan luar adalah masalah yang berbeda, karena tempat filter mengubah semantik kueri.

Remus Rusanu
sumber
Jadi dalam gabungan dalam itu pertama-tama menghitung filter dan kemudian menggabungkan output dari filter dengan tabel lain atau apakah itu pertama bergabung dengan dua tabel dan kemudian menerapkan filter?
Ashwin
@Remus Rusanu - bisakah Anda menjelaskan tentang bagaimana semantik diubah jika Outer-join? Saya mendapatkan hasil yang berbeda berdasarkan posisi filter, tetapi tidak dapat memahami alasannya
Ananth
3
@Ananth dengan gabungan luar Anda mendapatkan NULL untuk semua kolom dari tabel yang digabungkan di mana kondisi JOIN tidak cocok. Filter tidak akan memenuhi NULL dan menghilangkan baris, mengubah OUTER join menjadi gabungan INNER.
Remus Rusanu
@Ananth Saya mencapai pengoptimalan yang saya butuhkan berdasarkan komentar Anda. Perubahan saya adalah dari WHERE x.TableAID = a.ID atau x.TableAID null menjadi ON x.TableAID = a.ID. Mengubah lokasi filter pada OUTER join beri tahu kompiler untuk Filter lalu Gabung daripada Gabung lalu Filter. Itu juga dapat menggunakan indeks pada kolom itu karena tidak harus cocok dengan Null. Respon kueri berubah dari 61 detik menjadi 2 detik.
Ben Gripka
10

Sejauh kedua metode berjalan.

  • JOIN / ON untuk menggabungkan tabel
  • DI MANA untuk memfilter hasil

Meskipun Anda dapat menggunakannya secara berbeda, itu selalu terasa seperti bau bagi saya.

Tangani kinerja saat itu menjadi masalah. Kemudian Anda dapat melihat "optimisasi" tersebut.

Robin Day
sumber
2

Dengan pengoptimal kueri apa pun bekerja satu sen .... mereka identik.

TomTom
sumber
Saya cukup yakin bahwa, dengan beban kerja apa pun, keduanya tidak identik. Jika Anda hampir tidak memiliki data, maka pertanyaan itu tidak berguna.
eKek0
2
Lihat di bawah beban kerja nyata. Pada dasarnya - jika mereka menghasilkan rencana eksekusi yang sama, mereka ... identik dalam kinerja. Setidaknya untuk kasus normal / sederhana (bukan yang bergabung dengan 14 tabel) saya cukup yakin keduanya identik;)
TomTom
1

Di postgresql mereka sama. Kami tahu ini karena jika Anda melakukan explain analyzepada setiap kueri, rencananya akan sama. Ambil contoh ini:

# explain analyze select e.* from event e join result r on e.id = r.event_id and r.team_2_score=24;

                                                  QUERY PLAN                                                   
---------------------------------------------------------------------------------------------------------------
 Hash Join  (cost=27.09..38.22 rows=7 width=899) (actual time=0.045..0.047 rows=1 loops=1)
   Hash Cond: (e.id = r.event_id)
   ->  Seq Scan on event e  (cost=0.00..10.80 rows=80 width=899) (actual time=0.009..0.010 rows=2 loops=1)
   ->  Hash  (cost=27.00..27.00 rows=7 width=8) (actual time=0.017..0.017 rows=1 loops=1)
         Buckets: 1024  Batches: 1  Memory Usage: 9kB
         ->  Seq Scan on result r  (cost=0.00..27.00 rows=7 width=8) (actual time=0.006..0.008 rows=1 loops=1)
               Filter: (team_2_score = 24)
               Rows Removed by Filter: 1
 Planning time: 0.182 ms
 Execution time: 0.101 ms
(10 rows)

# explain analyze select e.* from event e join result r on e.id = r.event_id where r.team_2_score=24;
                                                  QUERY PLAN                                                   
---------------------------------------------------------------------------------------------------------------
 Hash Join  (cost=27.09..38.22 rows=7 width=899) (actual time=0.027..0.029 rows=1 loops=1)
   Hash Cond: (e.id = r.event_id)
   ->  Seq Scan on event e  (cost=0.00..10.80 rows=80 width=899) (actual time=0.010..0.011 rows=2 loops=1)
   ->  Hash  (cost=27.00..27.00 rows=7 width=8) (actual time=0.010..0.010 rows=1 loops=1)
         Buckets: 1024  Batches: 1  Memory Usage: 9kB
         ->  Seq Scan on result r  (cost=0.00..27.00 rows=7 width=8) (actual time=0.006..0.007 rows=1 loops=1)
               Filter: (team_2_score = 24)
               Rows Removed by Filter: 1
 Planning time: 0.140 ms
 Execution time: 0.058 ms
(10 rows)

Keduanya memiliki biaya min dan maks yang sama serta paket kueri yang sama. Juga, perhatikan bahwa bahkan di kueri teratas, team_score_2 akan diterapkan sebagai 'Filter'.

Peter Graham
sumber
0

Sangat tidak mungkin bahwa penempatan sambungan ini akan menjadi faktor penentu kinerja. Saya tidak terlalu paham dengan perencanaan eksekusi untuk tsql, tetapi kemungkinan besar mereka akan dioptimalkan secara otomatis ke rencana serupa.

Joseph Mastey
sumber
0

Aturan # 0: Jalankan beberapa tolok ukur dan lihat! Satu-satunya cara untuk benar-benar mengetahui mana yang lebih cepat adalah dengan mencobanya. Jenis tolok ukur ini sangat mudah dilakukan menggunakan SQL profiler.

Juga, periksa rencana eksekusi untuk kueri yang ditulis dengan JOIN dan dengan klausa WHERE untuk melihat perbedaan apa yang menonjol.

Akhirnya, seperti yang dikatakan orang lain, keduanya harus diperlakukan secara identik oleh pengoptimal yang layak, termasuk yang dibangun ke dalam SQL Server.

Simpan
sumber
Tapi hanya untuk bagian dalam. Hasil yang ditetapkan akan sangat berbeda untuk gabungan keluar.
HLGEM
Tentu saja. Untungnya, contoh yang diberikan menggunakan gabungan dalam.
Simpan
1
Sayangnya pertanyaannya adalah tentang join, bukan inner join.
Paul
Ya David, pertanyaannya adalah tentang bergabung. Sampel yang mendukung pertanyaan kebetulan menggunakan gabungan dalam.
Paul
0

Apakah lebih cepat? Cobalah dan lihat.

Mana yang lebih mudah dibaca? Yang pertama bagi saya terlihat lebih "benar", karena kondisi pindah tidak ada hubungannya dengan sambungan.

David M
sumber
0

Saya rasa itu yang pertama, karena itu membuat filter yang lebih spesifik atas data. Tetapi Anda harus melihat rencana eksekusi , seperti halnya pengoptimalan apa pun, karena ini bisa sangat berbeda bergantung pada ukuran data, perangkat keras server, dll.

eKek0
sumber