SQL から LINQ への変換、パート 8 : 左外部結合と右外部結合 (Bill Horst)

ここでは、このシリーズの以前の投稿 (英語) をお読みになっていることを前提としています。

         

結合について投稿した後で、外部結合について、いくつかの質問を受けました。パート 6 でご覧になったように、VB9 では左外部結合や右外部結合がスムーズにサポートされていません。同じような機能を Group Join で実現する方法をご説明しましたが、VB LINQ で左外部結合、右外部結合、および完全外部結合の結果を正確に得る方法を、皆さんにもう少し詳しくお見せしようと思います。確かに、これらのクエリは、比較的単純な概念の割には非常に複雑です。しかし、残念ながら、VB9 にこれらの機能を実装するためのリソースが VB チームにはありませんでした。このサポートが重要とお考えの場合は、将来のリリースにこれらの機能を含めるよう、Visual Studio チームにご提案ください。ご提案はフィードバック ページから提出していただけます。

 

今回の投稿では左外部結合と右外部結合についてご説明します。完全外部結合については来週の投稿でご説明する予定です。

 

前提条件

 

今回のコード例では、最初の投稿で定義したクラスを多少変更して使用します。下にお見せする宣言では、以前は Nothing 値 (Integer -> Integer? など) を設定できなかったメンバに対して、いくつか Null 許容型を宣言しています。

 

Class Customer

    Public CustomerID As Integer?

    Public ContactName As String

    Public Phone As String

    Public Address As String

    Public City As String

    Public State As String

    Public Zip As String

End Class

 

Class Order

    Public OrderID As Integer?

    Public CustomerID As Integer?

    Public Cost As Single?

    Public Phone As String

    Public OrderDate As DateTime?

    Public ShippingZip As String

    Public ItemName As String

End Class

 

 

また、次のように、CustomerTable 変数と OrderTable 変数を宣言して、いくつかの結果例を示しています。

 

 

Dim CustomerTable As Customer() = { _

    New Customer With {.ContactName = "Bill Horst", .CustomerID = 112}, _

    New Customer With {.ContactName = "John Doe", .CustomerID = 354}, _

    New Customer With {.ContactName = "Jane Doe", .CustomerID = 938}}

 

Dim OrderTable As Order() = { _

    New Order With {.OrderDate = #3/25/1982#, .CustomerID = 112}, _

    New Order With {.OrderDate = #3/13/2005#, .CustomerID = 112}, _

    New Order With {.OrderDate = #9/29/2007#, .CustomerID = 938}, _

    New Order With {.OrderDate = #1/31/2008#, .CustomerID = 444}}

 

 

 

左結合

 

SQL の左外部結合は標準的な内部結合に似ていますが、一致する結果が "右のテーブル" にない場合でも、"左のテーブル" に結果を返します。次にその例をお見せします。

 

SQL

SELECT Contact.ContactName, Shipment.OrderDate

FROM CustomerTable Contact

LEFT OUTER JOIN OrderTable Shipment

   ON Contact.CustomerID = Shipment.CustomerID

 

 

CustomerTable と OrderTable に上記のデータがあると仮定すると、このクエリでは次のような結果が返されます。

 

Results:

ContactName OrderDate

Bill Horst 3/25/1982

Bill Horst 3/13/2005

John Doe NULL

Jane Doe 9/29/2007

 

VB コードでこのクエリを再現するとしたら、まず、CustomerTable と OrderTable との間に Group Join を設定します。集約値を計算するのは嫌なので、Group キーワードを使って、CustomerTable の各メンバに、OrderTable の対応するエントリ配列でエントリを作成します。

 

VB

From Contact In CustomerTable _

Group Join Shipment In OrderTable _

  On Contact.CustomerID Equals Shipment.CustomerID _

  Into RightTableResults = Group

 

Results:

{Contact={Customer}, RightTableResults = {Grouping}}

{Contact={Customer}, RightTableResults = {Order[]}}

{Contact={Customer}, RightTableResults = {Grouping}}

 

 

ただし、配列の連続は避けたいので、From 句を追加して、各 Contact を、対応する配列の個々のメンバとクロス結合します。

 

VB

From Contact In CustomerTable _

Group Join Shipment In OrderTable _

  On Contact.CustomerID Equals Shipment.CustomerID _

  Into RightTableResults = Group _

From Shipment In RightTableResults.DefaultIfEmpty

 

Results:

{Contact = {Customer}, RightTableResults = {Grouping}, Shipment = {Order}}

{Contact = {Customer}, RightTableResults = {Grouping}, Shipment = {Order}}

{Contact = {Customer}, RightTableResults = {Order[]}, Shipment = Nothing}

{Contact = {Customer}, RightTableResults = {Grouping}, Shipment = {Order}}

 

 

次に、必要なフィールド、つまり Contact.ContactName と Shipment.OrderDate だけを Select します。

 

VB

From Contact In CustomerTable _

Group Join Shipment In OrderTable _

  On Contact.CustomerID Equals Shipment.CustomerID _

  Into RightTableResults = Group _

From Shipment In RightTableResults.DefaultIfEmpty _

Select Contact.ContactName, _

       Shipment.OrderDate

 

 

このコードにはまだ問題があります。Shipment = Nothing のエントリに到達すると、Select ステートメントが NullReferenceException をスローするのです。これをどうにかしなくてはなりません。独自のメソッドを定義するなど、いくつかの方法があるのですが、ここで選んだのは三項演算子呼び出しを使用する方法です。

 

VB

From Contact In CustomerTable _

Group Join Shipment In OrderTable _

  On Contact.CustomerID Equals Shipment.CustomerID _

  Into RightTableResults = Group _

From Shipment In RightTableResults.DefaultIfEmpty _

Select Contact.ContactName, _

       OrderDate = _

         If(Shipment Is Nothing, Nothing, _

            If(Shipment Is Nothing, New Order, Shipment).OrderDate)

 

Results:

{ContactName = "Bill Horst", OrderDate = {DateTime}}

{ContactName = "Bill Horst", OrderDate = {DateTime}}

{ContactName = "John Doe", OrderDate = Nothing}

{ContactName = "Jane Doe", OrderDate = {DateTime}}

 

 

このコードは確かにとても複雑ですが、集約関数を使用せずに左外部結合と同じ結果を得られます。LINQ に習熟すれば、もっと簡単な方法で外部結合クエリを変換する方法が見つかるかもしれません。

 

対応する右外部結合は次のようになります。

 

VB

Dim RightJoin = _

    From Shipment In OrderTable _

    Group Join Contact In CustomerTable _

      On Contact.CustomerID Equals Shipment.CustomerID _

      Into RightTableResults = Group _

    From Contact In RightTableResults.DefaultIfEmpty _

    Select ContactName = _

             If(Contact Is Nothing, _

                Nothing, _

                If(Contact Is Nothing, _

                   New Customer, _

                   Contact).ContactName), _

           Shipment.OrderDate

 

 

このシリーズに追加してほしいとお思いのトピックがありましたら、ぜひコメントしてください。次回は完全外部結合の変換についてご説明します。

 

-      VB IDE テスト、Bill Horst

投稿 : 2008 年 1 月 31 日 11:37 AM

VB チームの Web ログ - https://blogs.msdn.com/vbteam/archive/2008/01/31/converting-sql-to-linq-part-8-left-right-outer-join-bill-horst.aspx (英語) より