sql >> データベース >  >> RDS >> Database

SQL SELECT DISTINCT:パフォーマンスのベストプラクティス

    結果の重複を削除する必要がある場合、SQL DISTINCTは良い(または悪い)ですか?

    良いと言う人もいて、重複が表示されたらDISTINCTを追加します。悪いと言う人もいて、集計関数なしでGROUPBYを使用することを提案しています。重複を削除する必要がある場合、DISTINCTとGROUPBYは同じであると言う人もいます。

    この投稿では、正解を得るために詳細を詳しく説明します。したがって、最終的には、ニーズに基づいて最適なキーワードを使用します。始めましょう。

    SQLSELECTDISTINCTステートメントの基本についての簡単なリマインダー

    深く掘り下げる前に、SQLSELECTDISTINCTステートメントが何であるかを思い出してみましょう。データベーステーブルにはさまざまな理由で重複する値が含まれる可能性がありますが、一意の値のみを取得したい場合があります。この場合、SELECTDISTINCTが便利です。このDISTINCT句により、SELECTステートメントは一意のレコードのみをフェッチします。

    ステートメントの構文は単純です:

    SELECT DISTINCT column
    FROM table_name
    WHERE [condition];

    ここでは、WHERE条件はオプションです。

    このステートメントは、単一の列と複数の列の両方に適用されます。複数の列に適用されるこのステートメントの構文は次のとおりです。

    SELECT DISTINCT
    	column_name1,
    	column_name2,
    	column_nameN.
    FROM
    	table_name;

    複数の列をクエリするシナリオでは、ステートメントで定義されたすべての列の値の組み合わせを使用して、一意性を判断することをお勧めします。

    それでは、SELECTDISTINCTステートメントを適用する実際の使用法と落とし穴を見てみましょう。

    SQLDISTINCTが重複を削除するためにどのように機能するか

    答えを見つけるのはそれほど難しいことではありません。 SQL Serverは、必要な結果を得るためにクエリがどのように処理されるかを確認するための実行プランを提供してくれました。

    次のセクションでは、DISTINCTを使用する場合の実行プランに焦点を当てます。 Ctrl-Mを押す必要があります 以下のクエリを実行する前に、SQL ServerManagementStudioで。または、[実際の実行プランを含める]をクリックします ツールバーから。

    SQLDISTINCTのクエリプラン

    2つのクエリを比較することから始めましょう。最初のクエリはDISTINCTを使用せず、2番目のクエリは使用します。

    USE AdventureWorks
    GO
    
    -- Without DISTINCT. Duplicates included
    SELECT Lastname FROM Person.Person;
    -- With DISTINCT. Duplicates removed
    SELECT DISTINCT Lastname FROM Person.Person;
    

    実行計画は次のとおりです。

    図1は私たちに何を示しましたか?

    • DISTINCTキーワードがなければ、クエリは簡単です。
    • DISTINCTを追加すると、追加のステップが表示されます。
    • DISTINCTを使用する場合のクエリコストは、DISTINCTを使用しない場合よりも高くなります。
    • どちらにもインデックススキャン演算子があります。クエリには特定のWHERE句がないため、これは理解できます。
    • 余分なステップであるStreamAggregate演算子は、重複を削除するために使用されます。

    STATISTICS IOをチェックすると、論理読み取りの数は同じ(107)になります。それでも、レコードの数は大きく異なります。最初のクエリで19,972行が返されます。一方、2番目のクエリでは1,206行が返されます。

    したがって、いつでもDISTINCTを追加することはできません。ただし、一意の値が必要な場合、これは必要なオーバーヘッドです。

    一意の値を出力するために使用される演算子があります。それらのいくつかを調べてみましょう。

    STREAM AGGREGATE

    これは、図1で見た演算子です。単一の入力を受け入れ、集計結果を出力します。図1では、入力はインデックススキャンオペレーターからのものです。ただし、StreamAggregateには並べ替えられた入力が必要です。

    図1に示すように、 IX_Person_LastName_FirstName_MiddleNameを使用します。 、名前の一意でないインデックス。インデックスはすでにレコードを名前で並べ替えているため、StreamAggregateは入力を受け入れます。インデックスがないと、クエリオプティマイザはプランで追加のSort演算子を使用することを選択できます。そして、それはより高価になります。または、ハッシュマッチを使用できます。

    HASH MATCH(AGGREGATE)

    DISTINCTで使用されるもう1つの演算子は、ハッシュマッチです。この演算子は、結合と集計に使用されます。

    DISTINCTを使用する場合、Hash Matchは結果を集計して、一意の値を生成します。これが1つの例です。

    USE AdventureWorks
    GO
    
    -- Get unique first names
    SELECT DISTINCT Firstname FROM Person.Person;
    

    そして、これが実行計画です:

    しかし、なぜStream Aggregateを使用しないのですか?

    同じ名前のインデックスが使用されていることに注意してください。そのインデックスはでソートされます 最初。つまり、 クエリのみが並べ替えられなくなります。

    ハッシュマッチ(集計)は、重複を削除するための次の論理的な選択肢です。

    HASH MATCH(FLOW DISTINCT)

    Hash Match(Aggregate)はブロッキング演算子です。したがって、入力ストリーム全体を処理した出力は生成されません。行数を制限すると(DISTINCTでTOPを使用する場合など)、それらの行が使用可能になるとすぐに一意の出力が生成されます。それがハッシュマッチ(フローディスティンクト)のすべてです。

    USE AdventureWorks
    GO
    
    SELECT DISTINCT TOP 100
     sod.ProductID
    ,soh.OrderDate
    ,soh.ShipDate
    FROM Sales.SalesOrderDetail sod
    INNER JOIN Sales.SalesOrderHeader soh ON sod.SalesOrderID = soh.SalesOrderID;
    

    クエリはDISTINCTとともにTOP100を使用します。実行計画は次のとおりです。

    重複を削除するオペレーターがいない場合

    うん。これが発生する可能性があります。以下の例を検討してください。

    USE AdventureWorks
    GO
    
    SELECT DISTINCT
     BusinessEntityID
    FROM Person.Person;
    

    次に、実行計画を確認します。

    BusinessEntityID 列は主キーです。その列はすでに一意であるため、DISTINCTを適用しても意味がありません。 SELECTステートメントからDISTINCTを削除してみてください–実行プランは図4と同じです。

    一意のインデックスを持つ列でDISTINCTを使用する場合も同様です。

    SQLDISTINCTはSELECTリストのすべての列で機能します

    これまでのところ、例では1つの列のみを使用しています。ただし、DISTINCTは、SELECTリストで指定したすべての列で機能します。

    これが例です。このクエリにより、3つの列すべての値が一意になることが確認されます。

    USE AdventureWorks
    GO
    
    SELECT DISTINCT 
     Lastname 
    ,FirstName
    ,MiddleName
    FROM Person.Person;
    

    図5の結果セットの最初の数行に注目してください。

    最初の数行はすべて一意です。 DISTINCTキーワードは、ミドルネームが 列も考慮されます。赤で囲まれた2つの名前に注目してください。 を検討する および それらを複製するだけです。ただし、ミドルネームを追加する ミックスにすべてを変えました。

    一意の姓名を取得したいが、結果にミドルネームを含めたい場合はどうなりますか?

    2つのオプションがあります:

    • WHERE句を追加して、NULLのミドルネームを削除します。これにより、ミドルネームがNULLのすべての名前が削除されます。
    • または、にGROUPBY句を追加します および 列。次に、ミドルネームでMIN集計関数を使用します 桁。これにより、姓と名が同じミドルネームが1つ取得されます。

    SQLDISTINCTとGROUPBYの比較

    集計関数なしでGROUPBYを使用すると、DISTINCTのように機能します。どうやって知るの?調べる方法の1つは、例を使用することです。

    USE AdventureWorks
    GO
    
    -- using DISTINCT
    SELECT DISTINCT
     soh.TerritoryID
    ,st.Name
    FROM Sales.SalesOrderHeader soh
    INNER JOIN Sales.SalesTerritory st ON soh.TerritoryID = st.TerritoryID;
    
    -- using GROUP BY
    SELECT
     soh.TerritoryID
    ,st.Name
    FROM Sales.SalesOrderHeader soh
    INNER JOIN Sales.SalesTerritory st ON soh.TerritoryID = st.TerritoryID
    GROUP BY
     soh.TerritoryID
    ,st.Name;
    

    それらを実行し、実行計画を確認します。下のスクリーンショットのようですか?

    それらはどのように比較されますか?

    • 同じプラン演算子とシーケンスがあります。
    • それぞれのオペレーターコストとクエリコストは同じです。

    QueryPlanHashを確認した場合 2つのSELECT演算子のプロパティは、同じです。したがって、クエリオプティマイザは同じプロセスを使用して同じ結果を返しました。

    結局、一意の値を返す点でGROUPBYを使用する方がDISTINCTよりも優れているとは言えません。上記の例を使用してDISTINCTをGROUPBYに置き換えることで、これを証明できます。

    どちらを使用するかは、好みの問題になります。私はDISTINCTが好きです。クエリの意図を明示的に伝え、一意の結果を生成します。そして私にとって、GROUP BYは、集計関数を使用して結果をグループ化するためのものです。その意図も明確で、キーワード自体と一致しています。他の誰かが私のクエリをいつか維持するかどうかはわかりません。したがって、コードは明確である必要があります。

    しかし、それで話は終わりではありません。

    SQLDISTINCTがGROUPBYと同じでない場合

    私は自分の意見を表明しましたが、それからこれですか?

    それは本当です。それらは常に同じではありません。この例を考えてみましょう。

    -- using DISTINCT
    SELECT DISTINCT
     soh.TerritoryID
    ,(SELECT name FROM Sales.SalesTerritory st WHERE st.TerritoryID = soh.TerritoryID) AS TerritoryName
    FROM Sales.SalesOrderHeader soh;
    
    -- using GROUP BY
    SELECT
     soh.TerritoryID
    ,(SELECT name FROM Sales.SalesTerritory st WHERE st.TerritoryID = soh.TerritoryID) AS TerritoryName
    FROM Sales.SalesOrderHeader soh
    GROUP BY
    soh.TerritoryID;
    

    結果セットはソートされていませんが、行は前の例と同じです。唯一の違いは、サブクエリの使用です:

    違いは明らかです:オペレーター、クエリコスト、全体的な計画。今回は、GROUP BYがわずか28%のクエリコストで勝ちます。しかし、これが問題です。

    目的は、それらが異なる可能性があることを示すことです。それで全部です。これは決して推奨事項ではありません。結合を使用すると、実行プランが向上します(図6を再度参照してください)。

    ボトムライン

    これまでに学んだことは次のとおりです。

    • DISTINCTは、重複を削除するためのプラン演算子を追加します。
    • 集計関数のないDISTINCTとGROUPBYは、同じプランになります。つまり、ほとんどの場合同じです。
    • サブクエリがSELECTリストに含まれている場合、DISTINCTとGROUPBYのプランが異なる場合があります。

    では、SQL DISTINCTは、結果の重複を削除するのに良いのか悪いのか?

    結果はそれが良いことを示しています。計画は同じであるため、GROUPBYより良くも悪くもありません。ただし、実行計画を確認するのは良い習慣です。最初から最適化を考えてください。そうすれば、DISTINCTとGROUP BYの違いに遭遇した場合、それらを見つけることができます。

    さらに、最新のツールを使用すると、このタスクがはるかに簡単になります。たとえば、Devartの人気のある製品dbForge SQL Completeには、SSMS結果グリッドの準備完了結果セットの集計関数の値を計算する特定の機能があります。 DISTINCT値もそこにあります。

    投稿が好きですか?次に、お気に入りのソーシャルメディアプラットフォームで共有して、その言葉を広めてください。

    詳細情報の関連記事
    • SQL GROUP BY:プロのように結果をグループ化するための3つの簡単なヒント
    • SQL INSERT INTO SELECT:重複を処理する5つの簡単な方法
    • SQL集計関数とは何ですか? (初心者向けの簡単なヒント)
    • SQLクエリの最適化:クエリを強化するための5つのコアファクト


    1. ODBCとは何ですか?

    2. ProfessionalSSRSレポートのストアドプロシージャの書き方

    3. 挿入と更新の両方のMySQLFireトリガー

    4. MySQL-条件付き外部キー制約