どちらのSQLクエリが高速ですか?結合基準またはWhere句でフィルタリングしますか?


98

これら2つのクエリを比較します。結合基準またはWHERE句にフィルターを配置する方が高速ですか。可能な限り早い時点で結果セットが減少するため、結合基準の方が高速であると常に感じていましたが、確かではありません。

いくつかのテストを作成して確認しますが、どちらを読んだ方がわかりやすいかについても意見を聞きたかったのです。

クエリ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? */

クエリ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

編集

私はいくつかのテストを実行し、結果はそれが実際には非常に近いことを示していますが、WHERE句は実際には少し高速です!=)

WHERE句にフィルタを適用する方が理にかなっていることに私は完全に同意します。パフォーマンスへの影響について知りたくなりました。

ELAPSED TIME WHERE CRITERIA: 143016 ms
ELAPSED TIME JOIN CRITERIA: 143256 ms

テスト

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

10
データに応じて、WHERE対JOIN基準は異なる結果セットを返す可能性があります。
OMGポニー

4
@OMGポニーは非常に真実ですが、多くの場合、そうではありません。
Jon Erickson、2010年

2
私は5%以下の違いを違いとは呼びません-それらは同じです。2 %%の差の有意性を求めて、テストを1000回実行して、ランダムではないことを確認します。
TomTom

メリットは、結合前にデータをフィルタリングすることです。したがって、x.IDの場合は、a.IDを使用するよりも改善が見られる可能性があります
MikeT

回答:


65

パフォーマンスに関しては、それらは同じです(そして同じ計画を作成します)

論理的には、あなたが交換した場合、まだセンスを持って運転するべきであるINNER JOINとしますLEFT JOIN

あなたの場合、これは次のようになります:

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

またはこれ:

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

前者のクエリでは、a.id以外の実際の一致は返されない1ため、後者の構文(を使用WHERE)の方が論理的に一貫性があります。


セットを描くとき、​​2番目のケースがより一貫している理由を理解しました。前のクエリでは、制約a.id = 1は交差にのみ適用され、交差を除く左側の部分には適用されません。
FtheBuilder 2016

1
最初の例では、行が存在する可能性がありますがa.id != 1、もう1つの例では、行のみが存在しますa.id = 1
FtheBuilder 2016

1
あなたの言語は不明瞭です。「論理的には、...でも意味のある操作を行う必要があります」と「論理的に一貫性のある」は意味がありません。言い換えてもらえますか?
philipxy 2017

24

内部結合の場合、基準をどこに置いてもかまいません。SQLコンパイラーは、両方を実行計画に変換します。実行プランでは、フィルターは結合の下で発生します(つまり、フィルター式が結合条件にあるかのように)。

フィルターの場所によってクエリのセマンティクスが変わるため、外部結合は別の問題です。


したがって、内部結合では、最初にフィルターを計算してからフィルターの出力を他のテーブルと結合しますか、それとも最初に2つのテーブルを結合してからフィルターを適用しますか?
Ashwin

@Remus Rusanu-外部結合の場合にセマンティクスがどのように変更されるかについて詳しく教えてください。フィルターの位置に基づいて異なる結果が得られますが、理由がわかりません
Ananth

3
@Ananthと外部結合を使用すると、JOIN条件が一致しない結合テーブルのすべての列でNULLが取得されます。フィルターはNULLを満たさず、行を排除し、OUTER結合を有効にしてINNER結合にします。
Remus Rusanu 2017年

@Ananth私はあなたのコメントに基づいて必要な最適化を達成しました。私の変更はWHERE x.TableAID = a.IDまたはx.TableAID is nullからON x.TableAID = a.IDへの変更でした。OUTER結合でフィルターの場所を変更すると、コンパイラーは、結合してからフィルターではなく、フィルターしてから結合することを認識します。Nullと一致する必要がないため、その列のインデックスを使用することもできました。クエリの応答が61秒から2秒に変更されました。
ベングリプカ

10

2つの方法に関する限り。

  • JOIN / ONはテーブルを結合するためのものです
  • 結果をフィルタリングするための場所

あなたはそれらを異なって使うことができますが、それはいつも私には匂いのように思えます。

パフォーマンスに問題がある場合は対処してください。次に、そのような「最適化」を調べることができます。


2

クエリオプティマイザーは1セント以上の価値があります。


実際のワークロードでは、それらは同一ではないと確信しています。データがほとんどない場合、問題は無意味です。
eKek0 2010年

2
実際のワークロードで確認してください。基本的に-同じ実行プランを生成する場合、それらのパフォーマンスは同じです。少なくとも通常/単純なケース(つまり、14テーブルを結合するケースではない)では、それらが同一であると確信しています;)
TomTom

1

postgresqlでも同じです。これを知っているのはexplain analyze、各クエリを実行すると、計画は同じになるためです。この例を見てみましょう:

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

どちらも最小コストと最大コストが同じで、クエリプランも同じです。また、上のクエリでもteam_score_2が「フィルター」として適用されることに注意してください。


0

この結合の配置がパフォーマンスの決定要因になることは、ほとんどありません。私はtsqlの実行計画に詳しくありませんが、同様の計画に合わせて自動的に最適化される可能性があります。


0

ルール#0:いくつかのベンチマークを実行して確認してください!どちらが速いかを実際に判断する唯一の方法は、それを試すことです。これらのタイプのベンチマークは、SQLプロファイラーを使用して非常に簡単に実行できます。

また、JOINとWHERE句を使用して記述されたクエリの実行プランを調べ、どのような違いがあるかを確認します。

最後に、他の人が言ったように、これら2つは、SQL Serverに組み込まれているオプティマイザを含め、適切なオプティマイザと同じように扱う必要があります。


ただし、内部結合のみ。結果セットは、外部結合では大きく異なります。
HLGEM 2010年

もちろん。幸い、提供されている例では内部結合を使用しています。
3Dave

1
残念ながら、問題は内部結合ではなく結合についてです。
ポール、

はい、デイビッド、質問は参加についてです。質問をサポートするサンプルは、たまたま内部結合を使用しています。
ポール、

0

速いですか?試してみてください。

どちらが読みやすいですか?移動された条件は実際には結合とは何の関係もないので、最初の方は「正しい」ように見えます。


0

1つ目は、データに対してより具体的なフィルターを作成するためです。ただし、データやサーバーハードウェアなどのサイズによっては実行プランが大きく異なるため、他の最適化と同様に実行プランが表示されます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.