これまでは、それぞれのデータ・アクセス関数が 1 つのテーブルからのみ行を取得していました。この演習では、EGL で SQL テーブル結合を使用して、一度に複数のテーブルを処理する方法を学習します。
SQL テーブル結合の詳細な説明は、このチュートリアルでは割愛します。簡単に述べると、テーブル結合は、2 つ以上の関連テーブルから結果セットを組み立てます。
例えば、データベース内の CUSTOMERS テーブルには、主キーとしてカスタマー ID 番号が含まれ、それと一緒に顧客の名と姓 (および他のいくつかの列) が含まれています。
表 1. CUSTOMER テーブルのサンプル・データ| CUSTOMER_ID |
FIRST_NAME |
LAST_NAME |
| 1 |
Fred |
Filibuster |
| 2 |
Andy |
Lundquist |
| 3 |
Billie |
Kingman |
ORDERS テーブルには、主キーのオーダー ID 番号に加え、オーダー数が含まれています。このテーブルには、発注した顧客の ID 番号も含まれており、その列は
「外部キー」になっています。
表 2. ORDERS テーブルのサンプル・データ| ORDER_ID |
CUSTOMER_ID |
ORDER_AMOUNT |
| 1 |
1 |
111.11 |
| 2 |
1 |
222.22 |
| 3 |
1 |
333.33 |
この場合、CUSTOMER_ID 列が 2 つのテーブル間の関係を作成するため、テーブル結合の適切な候補となります。この演習では、これら 2 つのテーブルをカスタマイズされた SQLRecord パーツで結合し、顧客と顧客が行ったオーダーとを突き合わせる結果セットを生成します。
テーブル結合は複雑なデータベース操作であるため、データベースの動作に精通ししている必要があり、また出力を慎重にテストする必要があります。この演習でわかるように、必ずしも期待どおりのデータを得られるわけではありません。
- 新規 SQLRecord パーツを保持する新規 EGL ソース・ファイルを作成します。
- 「プロジェクト・エクスプローラー」ビューで、EGLSQL プロジェクトを右クリックし、とクリックします。
- 「新規 EGL ソース・ファイル」ウィンドウで、「パッケージ」フィールドに名前として data を入力します。
- 「EGL ソース・ファイル名」フィールドに、records と入力します。
- 「終了」をクリックします。
新規 EGL ソース・ファイルが作成されてエディターで開きます。
- 新規ファイルの package ステートメントの下に、CUSTOMER と ORDERS の両方のテーブルを結合する新規 SQLRecord の名前を入力します。
record OrderCustomerJoin type SQLRecord
{tableNames = [["EGL.CUSTOMER", "C"], ["EGL.ORDERS", "O"]]}
end
このレコードの 1 行目はもう一方の SQLRecord パーツと同じで、レコードの名前と SQLRecord ステレオタイプを指定しています。2 行目は、このレコードが関連するテーブルを、tableNames プロパティーの値として指定しています。前の演習で使用した単一テーブル・レコードでは、このプロパティーに、Orders レコードの ORDERS テーブルなど 1 つのテーブルのみがリストされました。
record Orders type sqlRecord {
tablenames=[["EGL.ORDERS"]]
...
作成している OrderCustomerJoin レコードでは、2 つのテーブルからデータを取得するために、tableNames プロパティーに 2 つのテーブルがリストされる必要があります。わかりやすくするために、レコードにそれぞれのテーブル名の別名 (ORDERS を表す「O」と CUSTOMERS を表す「C」) を含めます。後で説明しますが、これらの別名により、レコード内のテーブル名を簡単に参照できるようになります。
これでレコード・パーツの基本情報が指定されました。残りの情報は EGL によって埋められます。ただし、EGL がどのデータベースから接続情報を取得するのかを最初に指定する必要があります。
- 次のようにして、EGL のデフォルトの SQL データベースとして Derby データベースを設定します。
- とクリックします。
- 「設定」ウィンドウで、「EGL」を
展開して「SQL データベース接続」をクリックします。
- 「接続」リストで、使用するデータベース接続 (EGLDerbyR7 または EGLDerbyR71) を選択します。
- 「OK」をクリックします。
- records.egl ファイルに戻り、レコード名の上にカーソルを置きます。
- OrderCustomerJoin レコードの名前の上にカーソルを置いて右クリックし、とクリックします。 EGL SQL 取得機能
- パスワード・プロンプトで、ユーザー名およびパスワードのデフォルト値をそのまま使用します。 サンプル・データベースでは、特定のユーザー名またはパスワードは必要ありません。 EGL SQL 取得機能により、SQLRecord パーツの残りの部分、例えば、テーブル内の行や keyItems プロパティーに対するフィールドなどが作成されます。
record OrderCustomerJoin type SQLRecord
{tableNames = [["EGL.CUSTOMER", "C"], ["EGL.ORDERS", "O"]],
keyItems=[CUSTOMER_ID, ORDER_ID]}
CUSTOMER_ID int
{column="C.CUSTOMER_ID", isReadOnly=yes};
FIRST_NAME string
{column="C.FIRST_NAME", isReadOnly=yes,
isSqlNullable=yes, sqlVariableLen=yes, maxLen=30};
LAST_NAME string
{column="C.LAST_NAME", isReadOnly=yes,
isSqlNullable=yes, sqlVariableLen=yes, maxLen=30};
...
ORDER_ID int
{column="O.ORDER_ID", isReadOnly=yes};
orders_CUSTOMER_ID int
{column="O.CUSTOMER_ID", isReadOnly=yes,
isSqlNullable=yes};
ORDER_AMOUNT decimal(8,2)
{column="O.ORDER_AMOUNT", isReadOnly=yes,
isSqlNullable=yes};
ORDER_DETAILS string
{column="O.ORDER_DETAILS", isReadOnly=yes,
isSqlNullable=yes, sqlVariableLen=yes, maxLen=111};
end
各フィールドの column プロパティーの値が、tableNames プロパティーで指定したテーブルの別名で始まることに注意してください。
この別名は、特定のフィールドが関連するテーブルを追跡するのに便利です。
- records.egl ファイルを保存しますが、開いたままにします。
- printCustomerOrders という名前のプログラム・パッケージ内に、新規プログラム・パーツを作成します。
- 新規プログラムからデフォルト・コードを除去します。
- プログラムの main 関数で、上で作成した OrderCustomerJoin レコードに基づいて配列変数を作成します。
allCustomerOrders OrderCustomerJoin[0];
Ctrl + スペースを押すことにより、コンテンツ・アシストを使用してレコード・パーツ・タイプを挿入できることを覚えておいてください。
コンテンツ・アシストを使用しない場合は、手動で import ステートメントをファイル上部の package ステートメントのすぐ下に追加します。
import data.OrderCustomerJoin;
- open および forEach ステートメントを使用して、データベースから行を取得し、値をコンソールに出力します。
tempRec OrderCustomerJoin;
open resultSet for tempRec;
forEach (tempRec)
SysLib.writeStdout(tempRec.CUSTOMER_ID :: " " ::
tempRec.LAST_NAME :: " " :: tempRec.ORDER_ID);
end
- プログラムを保存し、生成して実行します。 テーブル結合で陥りやすいエラーについての知識がある場合は、このテーブル結合のエラーに気付き、なぜ出力があまり有用でないかにお気付きになると思われます。
1 Filibuster 1
1 Filibuster 2
1 Filibuster 3
...
1 Filibuster 22
1 Filibuster 23
2 Lundquist 1
2 Lundquist 2
2 Lundquist 3
2 Lundquist 4
...
2 Lundquist 22
2 Lundquist 23
3 Kingman 1
3 Kingman 2
...
データベースは、テーブル間でロジックの相互参照を行いませんでした。代わりに、CUSTOMERS テーブル内の各行と ORDERS テーブル内の各行とを可能な限りの組み合わせでペアにしました。この結果、それぞれの顧客があらゆるオーダーを行ったものとして記録されましたが、もちろんこれは誤っています。
open ステートメントの背後にある SQL コードを明示すると、SQL が Where 文節を使用することなく、両方のテーブルからすべての行を選択していることがわかります。
open resultSet for tempRec with
#sql{
select
C.CUSTOMER_ID, C.FIRST_NAME, C.LAST_NAME, C.PASSWORD,
C.PHONE, C.EMAIL_ADDRESS, C.STREET, C.APARTMENT,
C.CITY, C.STATE, C.POSTALCODE, C.DIRECTIONS,
O.ORDER_ID, O.CUSTOMER_ID, O.ORDER_AMOUNT,
O.ORDER_DETAILS, O.ORDER_DATE, O.ORDER_STATUS
from EGL.CUSTOMER C, EGL.ORDERS O
order by
C.CUSTOMER_ID, O.ORDER_ID asc
};
意味を成すようにテーブルを結合するには、単にテーブルの結合を返すのではなく、論理的にテーブルをマージする方法をデータベースに指示する必要があります。
明示的な SQL コードを編集してこれを行うことができますが、この場合、レコードを使用するたびに編集を行わなければなりません。そうしないと、不正確なデータを取得する危険が生じます。代わりに、デフォルトの選択条件をこのレコードに設定し、暗黙的な SQL コードが常に意味のあるテーブル結合を実行するようにします。
- open ステートメントの背後にある SQL コードを明示した場合は、カーソルをレコード名の上に置いて右クリックし、とクリックして、再度コードを暗黙的にします。
- records.egl ファイルで、レコード・パーツの定義に戻ります。
- defaultSelectCondition プロパティーをレコード・パーツの定義に追加します。
record OrderCustomerJoin type SQLRecord
{tableNames = [["EGL.CUSTOMER", "C"], ["EGL.ORDERS", "O"]],
keyItems=[CUSTOMER_ID, ORDER_ID],
defaultSelectCondition = #sqlCondition{ condition }}
defaultSelectCondition プロパティーでは、ユーザーがこのレコードで get または open を使用するときに用いられる、暗黙的な SQL コードとデフォルトの明示的な SQL コードの Where 文節を指定します。
- #sqlCondition{} ブロック内で、デフォルトの「condition」テキストの代わりに、これら 2 つのテーブル間の SQL テーブル結合で使用する正しい Where 文節を入力します。この際、完全なテーブル名の代わりにテーブル別名を使用するようにします。
defaultSelectCondition = #sqlCondition{ O.CUSTOMER_ID = C.CUSTOMER_ID }}
キーワード WHERE を追加する必要はありません。この場合、EGL が自動的に SQL コードに挿入します。
- ファイルを保管します。 ここで、open ステートメントの背後にある SQL コードを明示すると、次のようになります。
open resultSet for tempRec with
#sql{
select
C.CUSTOMER_ID, C.FIRST_NAME, C.LAST_NAME, C.PASSWORD,
C.PHONE, C.EMAIL_ADDRESS, C.STREET, C.APARTMENT,
C.CITY, C.STATE, C.POSTALCODE, C.DIRECTIONS,
O.ORDER_ID, O.CUSTOMER_ID, O.ORDER_AMOUNT,
O.ORDER_DETAILS, O.ORDER_DATE, O.ORDER_STATUS
from EGL.CUSTOMER C, EGL.ORDERS O
where
O.CUSTOMER_ID = C.CUSTOMER_ID
order by
C.CUSTOMER_ID, O.ORDER_ID asc
};
これで、CUSTOMER と ORDERS の両方のテーブルに同じカスタマー番号がある行のみを返すように制限する Where 文節が SQL コードに含まれました。
- プログラムを保存し、生成して実行します。
有用なテーブル結合により、このプログラムの結果として、それぞれの注文を行った顧客が表示されます。
1 Filibuster 1
1 Filibuster 2
1 Filibuster 3
...
1 Filibuster 11
1 Filibuster 18
2 Lundquist 7
2 Lundquist 8
2 Lundquist 12
2 Lundquist 15
2 Lundquist 20
3 Kingman 13
4 Liebowitz 14
6 Springsteen 22
7 St. Louis 16
8 Sharov 17
9 Hudak 23
これで、この演習で使用した 2 つのファイルのコードが完成しました。いずれかのファイル内に赤い X 記号がマークされたエラーが表示される場合は、
ご使用のコードが次のコード 演習 5 で完成した printCustomerOrders.egl および records.egl ファイル と一致するか確認してください。