わかりやすい JPA (4):LEFT (OUTER) JOIN

はじめに

これはJPAを解説する連載の4回目です。

前回のINNER JOINに続いて、ここではLEFT OUTER JOINを解説します。LEFT OUTER JOINも2つのエンティティ集合の直積を作成しますが、一方のエンティティ集合だけは、常に、すべての要素が直積の作成に使われます。INNER JOINのように、NULL値を持つエンティティが除外されるということはありません。

解説で出てくるエンティティやクエリの実行については、「連載:わかりやすいJPA」の予備知識 を参照してください。

LEFT JOIN

INNER JOINとは別タイプのJOINとして、LEFT OUTER JOIN (左外部結合)があります。ただ、OUTERは省略してもよいことになっているので、普通はLEFT JOINと表記します。

まず、違いを見るため、前回のSELECT文をLEFT JOINに変えて実行してみましょう。

SELECT e, p  FROM  Employee e  LEFT JOIN e.phones p
※Employeeの出力表示は見やすくするために名前以下を省略しています
[Employee [id: 1, name: John, .......], Phone [id: 1, no: (212)555-1234, type: Office,employee_Id: 1]]
[Employee [id: 1, name: John, .......], Phone [id: 2, no: (212)555-9843, type: Home,employee_Id: 1]]
[Employee [id: 2, name: Rob, ........], Phone [id: 3, no: (315)555-6253, type: Office,employee_Id: 2]]
[Employee [id: 3, name: Peter, ......], Phone [id: 4, no: (516)555-9837, type: Office,employee_Id: 3]]
[Employee [id: 3, name: Peter, ......], Phone [id: 5, no: (516)555-2034, type: Cell,employee_Id: 3]]
[Employee [id: 4, name: Frank, ......], Phone [id: 6, no: (650)555-7583, type: Office,employee_Id: 4]]
[Employee [id: 4, name: Frank, ......], Phone [id: 7, no: (650)555-5345, type: Home,employee_Id: 4]]
[Employee [id: 5, name: Scott, ......], Phone [id: 8, no: (650)555-9386, type: Office,employee_Id: 5]]
[Employee [id: 5, name: Scott, ......], Phone [id: 9, no: (650)555-4885, type: Cell,employee_Id: 5]]
[Employee [id: 6, name: Sue, ........], Phone [id: 10, no: (650)555-3836, type: Office,employee_Id: 6]]
[Employee [id: 6, name: Sue, ........], Phone [id: 11, no: (650)555-0985, type: Home,employee_Id: 6]]
[Employee [id: 6, name: Sue, ........], Phone [id: 12, no: (650)555-1946, type: Cell,employee_Id: 6]]
[Employee [id: 7, name: Stephanie, ..], Phone [id: 13, no: (650)555-4759, type: Office,employee_Id: 7]]
[Employee [id: 7, name: Stephanie, ..], Phone [id: 14, no: (650)555-4757, type: Home,employee_Id: 7]]
[Employee [id: 8, name: Jennifer, ...], Phone [id: 15, no: (650)555-6753, type: Office,employee_Id: 8]]
[Employee [id: 9, name: Sarah, ......], Phone [id: 16, no: (585)555-0693, type: Office,employee_Id: 9]]
[Employee [id: 9, name: Sarah, ......], Phone [id: 17, no: (585)555-3098, type: Home,employee_Id: 9]]
[Employee [id: 9, name: Sarah, ......], Phone [id: 18, no: (585)555-1457, type: Cell,employee_Id: 9]]
[Employee [id: 10, name: Joan, ......], Phone [id: 19, no: (650)555-9838, type: Office,employee_Id: 10]]
[Employee [id: 10, name: Joan, ......], Phone [id: 20, no: (650)555-2346, type: Home,employee_Id: 10]]
[Employee [id: 10, name: Joan, ......], Phone [id: 21, no: (650)555-9874, type: Cell,employee_Id: 10]]
[Employee [id: 11, name: Marcus, ....], null ]
[Employee [id: 12, name: Joe, .......], null ]
[Employee [id: 13, name: Jack, ......], null ]

LEFT JOINでは、左側の Employee e は、常に、すべてのエンティティを取り出します。一方、右側の e.phones p は、対応するエンティティがあれば取り出し、なければ、NULLのまま取り出して直積(デカルト積)を作成し、出力します。

実行結果は、INNER JOINと似ていますが、下の3行に、id=11,12,13の3件のEmployeeエンティティが取り出されていて、対応するPhoneエンティティはNULLになっています。これは、id=11,12,13のEmployeeエンティティでは、phonesがnullだったからです。

このように、OUTER JOINでは左側のエンティティが、常に、すべて直積の要素として使われ、スキップされることはありません。これがINNER JOINとの大きな違いです。

コレクション型ではないメンバとのOUTER JOIN

これまで、INNER/OUTER JOIN では、エンティティが結合する相手は、コレクション型のメンバでした。しかし、非コレクション型のメンバと結合できないわけではありません。

例えば、Employeeエンティティには、Address型 のメンバがあります。これとの LEFT JOINを実行してみましょう。

SELECT  e, a  FROM Employee e LEFT JOIN e.address a

※Employeeの出力表示は見やすくするために後半を省略しています
[Employee [id: 1, name: John, salary: 55000, address_Id: 1, .......], Address [id: 1, street: 123 Apple Tree Cr., city: New York, state: NY, zip: 10001]]
[Employee [id: 2, name: Rob, salary: 53000, address_Id: 2, ........], Address [id: 2, street: 654 Stanton Way., city: Manhattan, state: NY, zip: 10003]]
[Employee [id: 3, name: Peter, salary: 40000, address_Id: 3, ......], Address [id: 3, street: 99 Queen St., city: New York, state: NY, zip: 10001]]
[Employee [id: 4, name: Frank, salary: 41000, address_Id: 4, ......], Address [id: 4, street: 445 McDonell Cr., city: Redwood Shores, state: CA, zip: 90123]]
[Employee [id: 5, name: Scott, salary: 60000, address_Id: 5, ......], Address [id: 5, street: 624 Hamilton Dr., city: San Jose, state: CA, zip: 90123]]
[Employee [id: 6, name: Sue, salary: 62000, address_Id: 6, ........], Address [id: 6, street: 724 Coventry Rd., city: San Jose, state: CA, zip: 90123]]
[Employee [id: 7, name: Stephanie, salary: 54000, address_Id: 7, ..], Address [id: 7, street: 77 Manchester Blvd., city: San Francisco, state: CA, zip: 90123]]
[Employee [id: 8, name: Jennifer, salary: 45000, address_Id: 8, ...], Address [id: 8, street: 53 King St., city: Moorestown, state: NJ, zip: 08057]]
[Employee [id: 9, name: Sarah, salary: 52000, address_Id: 9, ......], Address [id: 9, street: 14 Industrial Ave., city: New York, state: NY, zip: 10001]]
[Employee [id: 10, name: Joan, salary: 59000, address_Id: 10, .....], Address [id: 10, street: 777 High Tech Ln., city: Redwood Shores, state: CA, zip: 90123]]
[Employee [id: 11, name: Marcus, salary: 35000, address_Id: ***, ..], null]
[Employee [id: 12, name: Joe, salary: 36000, address_Id: ***, .....], null]
[Employee [id: 13, name: Jack, salary: 43000, address_Id: ***, ....], null]

Address型メンバは、Employeeエンティティの中に1つしかないので、OUTER JOIN文を実行しても、出力される件数はEmployeeの総件数と同じです。1対1に結びつけるしかないので当然での結果です。

実際、出力結果を見ると、1行目は、id=1のEmployeeにそのAddressメンバのオブジェクトを結合して出力出力しています。2行目も、id=2のEmployeeとそのAddressメンバのオブジェクトです。

最後の3件は、AddressがnullのEmployee(id=11,12,13)も出力されています。これがLEFT JOINの特徴ですね。INNER JOINに変えて実行すると、この3件は出力されません。

なお、次のようにしてもLEFT JOINと同じ結果が出力されます。GlassFishのSQLログでも、全く同じSQLが発行されていました。この書き方は単純なSELECT文に見えますが、本当はLEFT JOINを実行しているわけです。

 SLECT e, e.address FROM Employee e

読者になる

コメントをどうぞ

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

%d人のブロガーが「いいね」をつけました。