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

SQL結合の究極のガイド:外部結合–パート2

    今日はアウタージョインが中心です。そして、これはSQL結合の究極のガイドのパート2です。パート1を見逃した場合は、こちらのリンクをご覧ください。

    見た目では、アウターはインナーの反対です。ただし、このように外部結合を検討すると、混乱する可能性があります。その上、外部という単語を含める必要はありません。 構文で明示的に。オプションです!

    ただし、詳しく説明する前に、外部結合に関するnullについて説明しましょう。

    ヌルと外部結合

    2つのテーブルを結合すると、いずれかのテーブルの値の1つがnullになる可能性があります。内部結合の場合、nullを含むレコードは一致せず、破棄されて結果セットに表示されません。一致しないレコードを取得する場合、唯一のオプションはOUTERJOINです。

    アントニムに戻ると、それは内部結合の反対ではありませんか?次のセクションで説明するように、完全ではありません。

    SQL ServerOUTERJOINのすべて

    外部結合の理解は、出力から始まります。期待できることの完全なリストは次のとおりです。

    • 結合条件または述語に一致するすべてのレコード。これは、INNER JOIN出力と同様に、ONキーワードの直後の式です。この問題を内側の行と呼びます 。
    • からのNULL以外の値 からのnullの対応物を含むテーブル テーブル。この問題を外側の行と呼びます 。
    • からのNULL以外の値 からのnullの対応物を含むテーブル テーブル。これは外側の行の別の形式です。
    • 最後に、上記のすべての組み合わせである可能性があります。

    そのリストから、OUTERJOINは内側と外側の行を返すと言えます。 。

    • 内部–内部結合の正確な結果が可能であるため 戻ってきました。
    • 外側–外側の行も 戻ってきました。

    INNERJOINとの違いです。

    内部結合は内部行のみを返します。外部結合は、内部行と外部行の両方を返すことができます

    「できる」と「できる」を使用したことに注意してください。内側の行と外側の行の両方を返すかどうかは、WHERE句(またはWHERE句を含めるかどうか)によって異なります。

    しかし、SELECTステートメントから、左または右のテーブルをどのように判断できますか ?良い質問です!

    結合の左テーブルと右テーブルのどちらであるかを知る方法

    この質問には例を挙げて答えることができます:

    SELECT *
    FROM Table1 a
    LEFT OUTER JOIN Table2 b on a.column1 = b.column1
    

    上記の例から、 Table1 は左側のテーブルで、 Table2 正しいテーブルです。では、別の例を見てみましょう。今回はシンプルなマルチジョインです。

    SELECT *
    FROM Table1 a
    LEFT OUTER JOIN Table2 b on a.column1 = b.column1
    LEFT OUTER JOIN Table3 c on b.column2 = c.column1
    

    この場合、左または右を知るために、結合は2つのテーブルで機能することを忘れないでください。

    表1 はまだ左側のテーブルであり、 Table2 正しいテーブルです。これは、2つのテーブルを結合することを意味します: Table1 およびTable2 Table2に参加するのはどうですか およびTable3表2 左のテーブルになり、 Table3 正しいテーブルです。

    4番目のテーブルを追加すると、 Table3 左のテーブルになり、 Table4 正しいテーブルです。しかし、それだけではありません。別のテーブルをTable1に結合できます 。次に例を示します:

    SELECT *
    FROM Table1 a
    LEFT OUTER JOIN Table2 b on a.column1 = b.column1
    LEFT OUTER JOIN Table3 c on b.column2 = c.column1
    LEFT OUTER JOIN Table4 d on c.column1 = d.column2
    LEFT OUTER JOIN Table5 e on a.column2 = e.column1
    

    表1 は左側のテーブルで、 Table5 正しいテーブルです。他のテーブルでも同じことができます。

    さて、上記の期待される出力のリストに戻りましょう。これらから外部結合タイプを導出することもできます。

    外部結合の種類

    OUTERJOINの出力に基づいて3つのタイプがあります。

    LEFT OUTER JOIN(LEFT JOIN)

    LEFT JOINは、から内部行+NULL以外の値を返します 右側のテーブルのnullに対応するテーブル。したがって、左側のテーブルがnull以外の値を持つ結合内の2つのテーブルの支配的であるため、これはLEFTJOINです。

    左外部結合の例1
    -- Return all customerIDs with orders and no orders
    
    USE AdventureWorks
    GO
    
    SELECT
     c.CustomerID
    ,soh.OrderDate
    FROM Sales.Customer c
    LEFT OUTER JOIN Sales.SalesOrderHeader soh ON c.CustomerID = soh.CustomerID 
    

    上記の例では、顧客 は左側のテーブルで、 SalesOrderHeader 正しいテーブルです。クエリの結果は32,166レコードです。 –内側の行と外側の行の両方が含まれます。その一部を図1で見ることができます:

    外側の行のみ、または注文のない顧客を返したいとします。これを行うには、WHERE句を追加して、 SalesOrderHeaderからのnullを含む行のみを含めます。 。

    SELECT
     c.CustomerID
    ,soh.OrderDate
    FROM Sales.Customer c
    LEFT OUTER JOIN Sales.SalesOrderHeader soh ON c.CustomerID = soh.CustomerID
    WHERE soh.SalesOrderID IS NULL

    得られた結果セットは701レコードです 。それらはすべてnullのOrderDate 図1から。

    内側の行のみを取得すると、結果は31,465レコードになります。 。 WHERE句を変更してそれらのSalesOrderIDsを含めることでそれを行うことができます nullではありません。または、結合をINNER JOINに変更して、WHERE句を削除することもできます。

    WHERE句のない最初の例の出力からチェックアウトするかどうかを確認するために、レコードを合計してみましょう。

    内側の行 外列 合計行数
    31,465レコード 701レコード 32,166レコード

    上記の32,166レコードの合計行から、最初の例の結果でチェックアウトしていることがわかります。これは、LEFTOUTERJOINがどのように機能するかも示しています。

    左外部結合の例2

    今回の例はマルチジョインです。 OUTERキーワードを廃止していることにも注意してください。

    -- show the people with and without addresses from AdventureWorks
    USE AdventureWorks
    GO
    
    SELECT
     P.FirstName
    ,P.MiddleName
    ,P.LastName
    ,a.AddressLine1
    ,a.AddressLine2
    ,a.City
    ,adt.Name AS AddressType
    FROM Person.Person p
    LEFT JOIN Person.BusinessEntityAddress bea ON P.BusinessEntityID = bea.BusinessEntityID
    LEFT JOIN Person.Address a ON bea.AddressID = a.AddressID
    LEFT JOIN person.AddressType adt ON bea.AddressTypeID = adt.AddressTypeID 
    

    19,996レコードを生成しました。以下の図2の出力の一部を確認できます。 nullのレコードAddressLine1 外側の行です。その上には内側の列があります。

    RIGHT OUTER JOIN(RIGHT JOIN)

    RIGHT JOINは、から内部行+NULL以外の値を返します 左側のテーブルのnullに対応するテーブル。

    右外部結合の例1
    -- From the product reviews, return the products without product reviews
    USE AdventureWorks
    GO
    
    SELECT
    P.Name
    FROM Production.ProductReview pr
    RIGHT OUTER JOIN Production.Product p ON pr.ProductID = p.ProductID
    WHERE pr.ProductReviewID IS NULL 
    

    図3は、結果セットの501レコードのうち10レコードを示しています。

    上記の例では、 ProductReview は左側の表で、製品 正しいテーブルです。これはRIGHTOUTERJOINであるため、右側のテーブルのNULL以外の値を含める予定です。

    ただし、LEFTJOINとRIGHTJOINのどちらを選択するかはあなた次第です。なんで? LEFTJOINでもRIGHTJOINでもクエリを表現でき、同じ結果が得られるからです。 LEFTJOINで試してみましょう。

    -- return the products without product reviews using LEFT OUTER JOIN
    USE AdventureWorks
    GO
    
    SELECT
    P.Name
    FROM Production.Product p
    LEFT OUTER JOIN Production.ProductReview pr ON pr.ProductID = p.ProductID
    WHERE pr.ProductReviewID IS NULL
    

    上記を実行してみると、図3と同じ結果が得られます。しかし、クエリオプティマイザーはそれらを異なる方法で処理すると思いますか?図4の両方の実行計画で調べてみましょう。

    これに慣れていない場合は、実行プランにいくつかの驚きがあります。

    1. 図は同じように見えます。ショープランの比較を試してください。 、同じ QueryPlanHashが表示されます 。
    2. マージ結合のある上の図に注意してください。 RIGHT OUTER JOINを使用しましたが、SQLServerはそれをLEFTOUTERJOINに変更しました。また、左右のテーブルを切り替えました。これにより、LEFTJOINを使用した2番目のクエリと同じになります。

    ご覧のとおり、結果は同じです。したがって、どの外部結合がより便利になるかを選択してください。

    SQLServerがRIGHTJOINをLEFTJOINに変更したのはなぜですか?

    データベースエンジンは、論理結合を表現する方法に従う必要はありません。可能な限り最速の方法で正しい結果を生成できる限り、変更を加えます。ショートカットも。

    RGHT JOINが悪く、LEFTJOINが良いと結論付けないでください。

    右外部結合の例2

    以下の例をご覧ください:

    -- Get the unassigned addresses and the address types with no addresses
    SELECT
     P.FirstName
    ,P.MiddleName
    ,P.LastName
    ,a.AddressLine1
    ,a.AddressLine2
    ,a.City
    ,adt.Name AS AddressType
    FROM Person.Person p
    RIGHT JOIN Person.BusinessEntityAddress bea ON P.BusinessEntityID = bea.BusinessEntityID
    RIGHT JOIN Person.Address a ON bea.AddressID = a.AddressID
    RIGHT JOIN person.AddressType adt ON bea.AddressTypeID = adt.AddressTypeID
    WHERE P.BusinessEntityID IS NULL 
    

    以下の図5に示すように、このクエリから取得できるものは2つあります。

    クエリ結果は次のようになります。

    1. 割り当てられていないアドレス–これらのレコードは名前がnullのレコードです。
    2. アドレスのないアドレスタイプ。アーカイブ、請求、およびプライマリアドレスタイプには、対応するアドレスがありません。それらはレコード817から819までです。

    フルアウタージョイン(フルジョイン)

    FULL JOINは、左右の内側の行と外側の行の組み合わせを返します。

    -- Get people with and without addresses, unassigned addresses, and address types without addresses
    SELECT
     P.FirstName
    ,P.MiddleName
    ,P.LastName
    ,a.AddressLine1
    ,a.AddressLine2
    ,a.City
    ,adt.Name AS AddressType
    FROM Person.Person p
    FULL JOIN Person.BusinessEntityAddress bea ON P.BusinessEntityID = bea.BusinessEntityID
    FULL JOIN Person.Address a ON bea.AddressID = a.AddressID
    FULL JOIN person.AddressType adt ON bea.AddressTypeID = adt.AddressTypeID
    

    結果セットには、20,815レコードが含まれます。予想どおり、これはINNER JOIN、LEFT JOIN、およびRIGHTJOINの結果セットからのレコードの総数です。

    LEFTおよびRIGHTJOINには、WHERE句が含まれており、左または右のテーブルにnullが含まれる結果のみを表示します。

    INNER JOIN 左参加
    (a.AddressIDがNULLの場合)
    右参加
    (P.BusinessEntityIDがNULLの場合)
    合計(完全参加と同じ)
    18,798レコード 1,198レコード 819レコード 20,815レコード

    FULL JOINは、大きなテーブルから膨大な結果セットを生成する可能性があることに注意してください。したがって、必要な場合にのみ使用してください。

    OUTERJOINの実用的な使用法

    OUTER JOINを使用できる場合と使用する必要がある場合に、それでも躊躇する場合は、ここにいくつかのアイデアがあります。

    内部行と外部行の両方を出力する外部結合

    例:

    • 有料および未払いの顧客注文のアルファベット順リスト。
    • 遅刻のある、または遅刻の記録がない従業員のアルファベット順のリスト。
    • 最新の保険契約を更新した保険契約者と更新しなかった保険契約者のリスト。

    外部行のみを出力する外部結合

    例:

    • 遅刻ゼロ賞の遅刻記録のない従業員のアルファベット順リスト
    • 顧客がいない地域のリスト
    • 特定の製品を販売していない販売代理店のリスト
    • 特定の期間に販売注文がない日付など、欠落している値から結果を取得する(以下の例)
    • 親子関係に子がないノード(以下の例)

    欠落している値から結果を取得する

    レポートを作成する必要があるとします。そのレポートには、注文がなかった特定の期間の各月の日数を表示する必要があります。 SalesOrderHeader AdventureWorks OrderDatesが含まれています 、ただし、注文のない日付はありません。何ができますか?

    1。期間内のすべての日付のテーブルを作成する

    以下のサンプルスクリプトは、2014年全体の日付の表を作成します。

    DECLARE @StartDate date = '20140101', @EndDate date = '20141231';
    
    CREATE TABLE dbo.Dates
    (
    	d DATE NOT null PRIMARY KEY
    )
    
    WHILE @StartDate <= @EndDate
    BEGIN
      INSERT Dates([d]) SELECT @StartDate;
      SET @StartDate = DATEADD(DAY, 1, @StartDate);
    END
    
    SELECT d FROM Dates ORDER BY [d];
    
    2。 LEFT JOINを使用して、注文のない日を出力します
    SELECT
     MONTH(d.d) AS [month]
    ,YEAR(d.d) AS [year]
    ,COUNT(*) AS NoOrderDays
    FROM Dates d
    LEFT JOIN Sales.SalesOrderHeader soh ON d.d = soh.OrderDate
    WHERE soh.OrderDate IS NULL
    GROUP BY YEAR(d.d), MONTH(d.d)
    ORDER BY [year], [month]
    

    上記のコードは、注文が行われていない日数をカウントします。 SalesOrderHeader 注文の日付が含まれています。したがって、結合で返されたnullは、注文がない日としてカウントされます。

    一方、正確な日付を知りたい場合は、カウントとグループ化を削除できます。

    SELECT
     d.d
    ,soh.OrderDate
    FROM Dates d
    LEFT JOIN Sales.SalesOrderHeader soh ON d.d = soh.OrderDate
    WHERE soh.OrderDate IS NULL

    または、特定の期間の注文をカウントして、注文がゼロの日付を確認する場合は、次の方法で行います。

    SELECT DISTINCT
     D.d AS SalesDate
    ,COUNT(soh.OrderDate) AS NoOfOrders
    FROM Dates d
    LEFT JOIN Sales.SalesOrderHeader soh ON d.d = soh.OrderDate
    WHERE d.d BETWEEN '02/01/2014' AND '02/28/2014'
    GROUP BY d.d
    ORDER BY d.d
    

    上記のコードは、2014年2月の注文をカウントします。結果を参照してください:

    2014年2月3日を強調するのはなぜですか?私のAdventureWorksのコピーには、その日付の販売注文はありません。

    ここで、 COUNT(soh.OrderDate)に注目してください。 コードで。後で、これが非常に重要である理由を明らかにします。

    親子関係で子のないノードを取得する

    親子関係に子がないノードを知る必要がある場合があります。

    HierarchyIDに関する記事で使用したデータベースを使用してみましょう。自己結合を使用して、親子関係テーブルに子のないノードを取得する必要があります。

    SELECT 
     r1.RankParentId
    ,r1.Rank AS RankParent
    ,r.RankId
    FROM Ranks r
    RIGHT JOIN Ranks r1 ON r.RankParentId = r1.RankId
    WHERE r.RankId is NULL 
    

    OUTERJOINを使用する際の注意事項

    OUTERJOINはINNERJOINのように内部行を返す可能性があるため、混乱する可能性があります。パフォーマンスの問題も忍び寄る可能性があります。そのため、以下の3つのポイントに注意してください(時々戻ってきます。若くならないので、忘れてしまいます)。

    WHERE句でnull以外の値を使用してLEFTJOINの右側のテーブルをフィルタリングする

    LEFT OUTER JOINを使用したが、WHERE句でnull以外の値を使用して右側のテーブルをフィルタリングした場合は、問題になる可能性があります。その理由は、機能的にはINNERJOINと同等になるからです。以下の例を検討してください:

    USE AdventureWorks
    GO
    
    SELECT
     P.FirstName
    ,P.MiddleName
    ,P.LastName
    ,a.AddressLine1
    ,a.AddressLine2
    ,a.City
    ,adt.Name AS AddressType
    FROM Person.Person p
    LEFT JOIN Person.BusinessEntityAddress bea ON P.BusinessEntityID = bea.BusinessEntityID
    LEFT JOIN Person.Address a ON bea.AddressID = a.AddressID
    LEFT JOIN person.AddressType adt ON bea.AddressTypeID = adt.AddressTypeID
    WHERE bea.AddressTypeID = 5 
    

    上記のコードから、2つのテーブルを調べてみましょう: Person およびBusinessEntityAddress は左側のテーブルで、 BusinessEntityAddress 正しいテーブルです。

    LEFT JOINが使用されるため、nullの BusinessEntityIDを想定します。 BusinessEntityAddressのどこか 。ここで、WHERE句に注目してください。 AddressTypeIDを使用して適切なテーブルをフィルタリングします =5. BusinessEntityAddressのすべての外側の行を完全に破棄します 。

    これは、次のいずれかになります。

    • 開発者は結果で何かをテストしていますが、それを削除するのを忘れていました。
    • INNER JOINが意図されていましたが、何らかの理由でLEFTJOINが使用されました。
    • 開発者は、LEFTJOINとINNERJOINの違いを理解していません。彼は、2つのうちのいずれかが機能すると想定していますが、この場合、結果は同じであるため、問題ではありません。

    上記の3つはどれも悪いですが、3番目のエントリには別の意味があります。上記のコードを同等のINNERJOINと比較してみましょう:

    SELECT
     P.FirstName
    ,P.MiddleName
    ,P.LastName
    ,a.AddressLine1
    ,a.AddressLine2
    ,a.City
    ,adt.Name AS AddressType
    FROM Person.Person p
    INNER JOIN Person.BusinessEntityAddress bea ON P.BusinessEntityID = bea.BusinessEntityID
    INNER JOIN Person.Address a ON bea.AddressID = a.AddressID
    INNER JOIN person.AddressType adt ON bea.AddressTypeID = adt.AddressTypeID
    WHERE bea.AddressTypeID = 5
    

    結合のタイプを除いて、前のコードと同じように見えます。結果も同じですが、STATISTICSIOの論理読み取りに注意する必要があります。

    図7では、最初のI/O統計はINNERJOINの使用によるものです。論理読み取りの合計は177です。ただし、2番目の統計は論理読み取り値が223の高いLEFT JOINに対するものです。したがって、この例でLEFT JOINを誤って使用すると、SQLServerからより多くのページまたはリソースが必要になります。したがって、実行速度が遅くなります。

    持ち帰り

    内側の行を出力する場合は、INNERJOINを使用します。それ以外の場合は、LEFTJOINの右側のテーブルをnull以外の値でフィルタリングしないでください。これが発生した場合、INNERJOINを使用する場合よりもクエリが遅くなります。

    ボーナスのヒント :この状況は、左側のテーブルがnull以外の値でフィルタリングされている場合のRIGHTJOINでも発生します。

    マルチジョインでのジョインタイプの不適切な使用

    すべてを取得したいとします。 ベンダーとそれぞれの製品発注書の数。コードは次のとおりです:

    USE AdventureWorks
    GO
    
    SELECT
     v.BusinessEntityID
    ,v.Name AS Vendor
    ,pod.ProductID
    ,pod.OrderQty
    FROM Purchasing.Vendor v
    LEFT JOIN Purchasing.PurchaseOrderHeader poh ON v.BusinessEntityID = poh.VendorID
    LEFT JOIN Purchasing.PurchaseOrderDetail pod ON poh.PurchaseOrderID = pod.PurchaseOrderID 
    

    上記のコードは、発注書のあるベンダーとないベンダーの両方を返します。図8は、上記のコードの実際の実行プランを示しています。

    すべての発注書に保証された発注書の詳細があると考えると、INNERJOINの方が適しています。しかし、それは本当にそうですか?

    まず、INNERJOINを使用して変更されたコードを作成しましょう。

    USE AdventureWorks
    GO
    
    SELECT
     v.BusinessEntityID
    ,v.Name AS Vendor
    ,pod.ProductID
    ,pod.OrderQty
    FROM Purchasing.Vendor v
    LEFT JOIN Purchasing.PurchaseOrderHeader poh ON v.BusinessEntityID = poh.VendorID
    INNER JOIN Purchasing.PurchaseOrderDetail pod ON poh.PurchaseOrderID = pod.PurchaseOrderID 
    

    上記の要件は「すべての」ベンダーを示していることを忘れないでください。前のコードではLEFTJOINを使用したため、発注書が返されることなくベンダーを取得できます。これは、 PurchaseOrderIDがnullであるためです。 。

    結合をINNERJOINに変更すると、すべてのnullPurchaseOrderIDs。が破棄されます。 また、すべてのnull VendorIDをキャンセルします ベンダーから テーブル。事実上、それは内部結合になります。

    それは正しい仮定ですか?実行計画は答えを明らかにします:

    ご覧のとおり、すべてのテーブルはINNERJOINを使用して処理されました。したがって、私たちの仮定は正しいです。しかし、最悪の場合、注文のないベンダーが含まれていなかったため、結果セットは正しくありません。

    持ち帰り

    前の場合と同様に、INNER JOINを使用する場合は、それを使用します。しかし、ここでのような状況に遭遇した場合の対処方法はわかっています。

    この場合、INNER JOINは、リレーションシップの最上位テーブルまでのすべての外側の行を破棄します。他の参加者がLEFTJOINであっても、問題はありません。実行計画でそれを証明しました。

    外部結合でのCOUNT()の誤った使用

    日付ごとの注文数と図6の結果をカウントするサンプルコードを覚えていますか?

    ここでは、2014年2月3日が強調表示されている理由と、 COUNT(soh.OrderDate)との関係を明確にします。 。

    COUNT(*)を使おうとすると、その日の注文数は1になりますが、これは間違いです。その日に注文はありません。したがって、OUTER JOINでCOUNT()を使用する場合は、正しい列を使用してカウントしてください。

    この場合、 soh.OrderDate nullでもそうでなくてもかまいません。 nullでない場合、COUNT()はその行をカウントに含めます。 COUNT(*)は、nullを含むすべてをカウントします。そして結局、間違った結果になります。

    OUTERJOINのポイント

    ポイントをまとめましょう:

    • OUTER JOINは、内側の行と外側の行の両方を返すことができます。内側の行は、INNERJOINの結果と同様の結果です。外側の行は非null値であり、結合条件に基づいて対応するnull値があります。
    • OUTER JOINは、LEFT、RIGHT、またはFULLにすることができます。それぞれに例がありました。
    • OUTER JOINによって返される外側の行は、さまざまな実用的な方法で使用できます。このようなものをいつ使用できるかについてのアイデアがありました。
    • OUTERJOINの使用にも注意が必要です。バグやパフォーマンスの問題を回避するために、上記の3つのポイントに注意してください。

    このシリーズの最後のパートでは、CROSSJOINについて説明します。それで、それまで。そして、この投稿が気に入ったら、ソーシャルメディアのボタンをクリックして愛を分かち合ってください。ハッピーコーディング!


    1. ホスト「xxx.xx.xxx.xxx」はこのMySQLサーバーへの接続を許可されていません

    2. SQLite Length()のしくみ

    3. Oracleで月の最終日を取得する方法

    4. JavaOracle例外-リスト内の式の最大数は1000です