親子表での内部結合の使用

JOINは、2つ以上の表の行を、それらの表の関連列に基づいて結合するために使用します。階層表では、子表は親表の主キー列を継承します。これは暗黙的に行われ、子のCREATE TABLE文に親列が含まれることはありません。階層内のすべての表には、同じシャード・キー列があります。

内部結合は、Oracle NoSQL Database Cloud Service内の同じ表階層に属する表を結合するために使用される結合のタイプの1つです。

内部結合の概要

内部結合は、関連する列またはそれらの間のフィールドに適用される結合述語に基づいて、2つ以上の表の行を結合することによって新しい行を生成する操作です。結果セットには、結合述語を満たす結合された行のみが含まれます。

概念的には、内部結合は次のように機能します。

3つの表A、BおよびCの内部結合を実行する必要があるとします。表AとBが最初に結合されます。つまり、表AにN個の行とn個の列があり、表BにM個の行とm個の列がある場合、表Aのすべての行が表Bのすべての行と結合されます。したがって、結果の表ABには、(N * M)行と(n + m)列があります。同様に、表ABが表Cと結合され、表ABCが形成されるようになりました。次に、WHERE句の結合述語が表ABCに適用されます。結合述語には、結合表のすべてのシャード・キー列間の等価述語が含まれている必要があることに注意してください。最終結果セットには、参加表の一致する行のみが含まれます。

結合する表は、SELECT文のFROM句およびWHERE句の結合述語で指定します。結合述語は、結合する1つ以上の表の列またはフィールドを参照し、それらに適用する必要があるフィルタ条件を指定する述語です。内部結合の場合、WHERE句には、参加表のすべてのシャード・キーに対する等価述語が含まれている必要があります。

'SELECT'句で'*'を使用すると、表内のすべてのフィールドが返される場合、結果セット内のフィールドの順序は、FROM句で表を指定した順序によって異なります。SELECT句でフィールドのリストを指定した場合、結果セット内のフィールドの順序はSELECT句で指定されているとおりになります。

内部結合の実行中に、次のことが適用されます。

内部結合は、主に次の点でNESTED TABLESおよび左外部結合とは異なります。

基本的に、それらの間に祖先と子孫の関係を持つ表は、3つのタイプの結合のいずれかを使用して結合できます。ユース・ケースに基づいて、いずれかを使用できます。結合する表が祖先と子孫の関係にない場合、内部結合を使用する必要があります。

内部結合の使用例

航空会社手荷物の追跡アプリケーションについて考えてみます。航空券番号ごとに、乗客とその手荷物が関連付けられています。ルート表はticketで、2つの子表passengerInfoおよびbaggageInfoがあります。passengerInfo表には乗客の詳細が含まれ、baggageInfoには乗客がチェックインしたバッグの詳細が含まれます。これらのバッグは、複数の中間ステーションを介して輸送を介して追跡されます。このトラッキング情報は、baggageInfo表の子であるflightlegsという表に取得されます。

スクリプトparentchildtbls_loaddata.sqlをダウンロードして、次に示すように実行します。このスクリプトは、例で使用されている表を作成し、表にデータをロードします。

parentchildtbls_loaddata.sqlには、次の内容が含まれています:

### Begin Script ###
load -file parentchild.ddl
import -table ticket -file ticket.json
import -table ticket.bagInfo -file bagInfo.json
import -table ticket.passengerInfo -file passengerInfo.json
import -table ticket.bagInfo.flightLegs -file flightLegs.json
### End Script ###

作成された表は次のとおりです。

内部結合のSQL問合せの例をいくつか見てみましょう。

例1 - チケット番号が1762324912391の乗客の詳細をフェッチする。

SELECT fullname, contactPhone, gender FROM ticket a,ticket.passengerInfo b WHERE
      a.ticketNo=b.ticketNo AND a.ticketNo=1762324912391

説明:これは、親テーブルチケットをその子テーブルpassengerInfoと結合し、その結果を制限するためにフィルタを適用する内部結合の例です。ここでのシャード・キーはticketNoです。ルート表の作成時にシャード・キーが明示的に指定されていない場合、ルート表の主キーはシャード・キーとみなされます。このシャード・キーは、すべての子孫表に継承されます。

出力:

{"fullname":"Elane Lemons","contactPhone":"600-918-8404","gender":"F"}
1 row returned

例2 - チケットを発行したすべての乗客の手荷物詳細をフェッチします。

SELECT * FROM ticket a, ticket.bagInfo b WHERE a.ticketNo=b.ticketNo

説明:これは、親表ticketをその子表bagInfoと結合する内部結合の一例です。

出力:

{"a":{"ticketNo":1762324912391,"confNo":"LN0C8R"},"b":{"ticketNo":1762324912391,"id":79039899168383,"tagNum":1765780623244,"routing":"MXP/CDG/SLC/BZN","lastActionCode":"OFFLOAD","lastActionDesc":"OFFLOAD","lastSeenStation":"BZN","lastSeenTimeGmt":"2019-03-15T10:13:00.0000Z","bagArrivalDate":"2019-03-15T10:13:00.0000Z"}}
{"a":{"ticketNo":1762355527825,"confNo":"HJ4J4P"},"b":{"ticketNo":1762355527825,"id":79039899197492,"tagNum":17657806232501,"routing":"BZN/SEA/CDG/MXP","lastActionCode":"OFFLOAD","lastActionDesc":"OFFLOAD","lastSeenStation":"MXP","lastSeenTimeGmt":"2019-03-22T10:17:00.0000Z","bagArrivalDate":"2019-03-22T10:17:00.0000Z"}}
{"a":{"ticketNo":1762344493810,"confNo":"LE6J4Z"},"b":{"ticketNo":1762344493810,"id":79039899165297,"tagNum":17657806255240,"routing":"MIA/LAX/MEL","lastActionCode":"OFFLOAD","lastActionDesc":"OFFLOAD","lastSeenStation":"MEL","lastSeenTimeGmt":"2019-02-01T16:13:00.0000Z","bagArrivalDate":"2019-02-01T16:13:00.0000Z"}}
{"a":{"ticketNo":1762376407826,"confNo":"ZG8Z5N"},"b":{"ticketNo":1762376407826,"id":7903989918469,"tagNum":17657806240229,"routing":"JFK/MAD","lastActionCode":"OFFLOAD","lastActionDesc":"OFFLOAD","lastSeenStation":"MAD","lastSeenTimeGmt":"2019-03-07T13:51:00.0000Z","bagArrivalDate":"2019-03-07T13:51:00.0000Z"}}
{"a":{"ticketNo":1762392135540,"confNo":"DN3I4Q"},"b":{"ticketNo":1762392135540,"id":79039899156435,"tagNum":17657806224224,"routing":"GRU/ORD/SEA","lastActionCode":"OFFLOAD","lastActionDesc":"OFFLOAD","lastSeenStation":"SEA","lastSeenTimeGmt":"2019-02-15T21:21:00.0000Z","bagArrivalDate":"2019-02-15T21:21:00.0000Z"}}
5 rows returned

例3 - チケット番号1762344493810の乗客のバッグのフライトレッグ詳細を取得します。

SELECT * FROM ticket a, ticket.bagInfo.flightLegs b WHERE a.ticketNo=b.ticketNo AND
      a.ticketNo=1762344493810

説明:これは、親表ticketをその子孫flightlegsと結合する内部結合の一例です。子孫表は、表の下の任意の階層レベルにできます(たとえば、flightLegsticketの子であるbagInfoの子であるため、flightLegsticketの子孫です)。結果は、特定のチケット番号でフィルタ処理されます。

出力:

{"a":{"ticketNo":1762344493810,"confNo":"LE6J4Z"},"b":{"ticketNo":1762344493810,"id":79039899165297,"flightNo":"BM604","flightDate":"2019-02-01T06:00:00.0000Z","fltRouteSrc":"MIA","fltRouteDest":"LAX","estimatedArrival":"2019-02-01T11:00:00.0000Z","actions":[{"actionAt":"MIA","actionCode":"ONLOAD to LAX","actionTime":"2019-02-01T06:13:00Z"},{"actionAt":"MIA","actionCode":"BagTag Scan at MIA","actionTime":"2019-02-01T05:47:00Z"},{"actionAt":"MIA","actionCode":"Checkin at MIA","actionTime":"2019-02-01T04:38:00Z"}]}}
{"a":{"ticketNo":1762344493810,"confNo":"LE6J4Z"},"b":{"ticketNo":1762344493810,"id":79039899165297,"flightNo":"BM667","flightDate":"2019-02-01T06:13:00.0000Z","fltRouteSrc":"LAX","fltRouteDest":"MEL","estimatedArrival":"2019-02-01T16:15:00.0000Z","actions":[{"actionAt":"MEL","actionCode":"Offload to Carousel at MEL","actionTime":"2019-02-01T16:15:00Z"},{"actionAt":"LAX","actionCode":"ONLOAD to MEL","actionTime":"2019-02-01T15:35:00Z"},{"actionAt":"LAX","actionCode":"OFFLOAD from LAX","actionTime":"2019-02-01T15:18:00Z"}]}}
2 rows returned

例4 - チケット番号が1762355527825の乗客のすべての手荷物の中継地の数を確認します。乗客用に複数のバッグがチェックインされている場合は、すべてのバッグのホップ数が表示されます。

SELECT b.id,count(*) AS NUMBER_HOPS FROM ticket a, ticket.bagInfo.flightLegs b WHERE a.ticketNo=b.ticketNo AND a.ticketNo=1762355527825 GROUP BY
    b.id

説明:(GROUP BYを使用して)手荷物IDに基づいてデータをグループ化し、それぞれの手荷物の飛行区間間の数を取得します(count()を使用します)。また、特定のチケット番号の結果をフィルタ処理します。

出力:

{"id":79039899197492,"NUMBER_HOPS":3}
1 row returned

例5 - すべての乗客のチケット番号、乗客名、およびバッグの詳細を取得します。

SELECT a.ticketNo, b.fullName, c.bagArrivalDate FROM ticket a, ticket.passengerInfo b, ticket.bagInfo c WHERE a.ticketNo = b.ticketNo AND b.ticketNo=c.ticketNo

説明:これは、3つのテーブル、つまり親テーブル ticket、および兄弟テーブル passengerInfobagInfoの内部結合の例です。

出力:

{"ticketNo":1762324912391,"fullName":"Elane Lemons","bagArrivalDate":"2019-03-15T10:13:00.0000Z"}
{"ticketNo":1762355527825,"fullName":"Doris Martin","bagArrivalDate":"2019-03-22T10:17:00.0000Z"}
{"ticketNo":1762344493810,"fullName":"Adam Phillips","bagArrivalDate":"2019-02-01T16:13:00.0000Z"}
{"ticketNo":1762392135540,"fullName":"Adelaide Willard","bagArrivalDate":"2019-02-15T21:21:00.0000Z"}
{"ticketNo":1762376407826,"fullName":"Dierdre Amador","bagArrivalDate":"2019-03-07T13:51:00.0000Z"}
5 rows returned

例6 - 乗客の名前、最後に見たバッグが「MEL」のステーションをフェッチする

SELECT a.fullName FROM ticket.passengerInfo a, ticket.bagInfo b WHERE a.ticketNo = b.ticketNo AND b.lastSeenStation = "MEL"

説明:これは、兄弟表 passengerInfoおよび bagInfoの内部結合の例です。「MEL」駅で最後にバッグが見られた乗客の名前が戻ります。

出力:

{"fullName":"Adam Phillips"}
1 row returned

例7 - フライト・ルート宛先が「MEL」の乗客の名前をフェッチする

SELECT a.fullName FROM ticket.passengerInfo a, ticket.bagInfo.flightlegs b WHERE a.ticketNo = b.ticketNo AND b.fltRouteDest  = "MEL"

説明:これは、祖先と子孫の関係にない2つのテーブル passengerInfoflightlegsの内部結合です。

出力:

{"fullName":"Adam Phillips"}

祖先と子孫の関係にない表間のこのような結合は、左外部結合およびNESTED TABLESでは実行できません。

問合せAPIの例

問合せを実行するには、NoSQLHandle.query() APIを使用します。

こちらにあるサンプルの中にフル・コードTableJoins.javaをダウンロードします。

/* fetch rows based on joins*/
private static void fetchRows(NoSQLHandle handle,String sql_stmt) throws Exception {
   try (
      QueryRequest queryRequest = new QueryRequest().setStatement(sql_stmt);
      QueryIterableResult results = handle.queryIterable(queryRequest)) {
         System.out.println("Query results:");
         for (MapValue res : results) {
            System.out.println("\t" + res);
         }
      }
}

/* fetching rows using inner join*/
String sql_stmt_innerjoin ="SELECT * FROM ticket a, ticket.bagInfo.flightLegs b WHERE a.ticketNo=b.ticketNo";
System.out.println("Fetching data using inner join:");
fetchRows(handle,sql_stmt_innerjoin);

問合せを実行するには、borneo.NoSQLHandle.query()メソッドを使用します。

こちらにあるサンプル中からフル・コードTableJoins.pyをダウンロードします。

# Fetch data from the table based on joins
def fetch_data(handle,sqlstmt):
   request = QueryRequest().set_statement(sqlstmt)
   print('Query results for: ' + sqlstmt)
   result = handle.query(request)
   for r in result.get_results():
      print('\t' + str(r))

sql_stmt_ij='SELECT * FROM ticket a, ticket.bagInfo.flightLegs b WHERE a.ticketNo=b.ticketNo'
print('Fetching data using Inner Join ')
fetch_data(handle,sql_stmt_ij)

問合せを実行するには、Client.Query関数を使用します。

こちらにあるサンプル中からフル・コードTableJoins.goをダウンロードします。

func fetchData(client *nosqldb.Client, err error,
               tableName string, querystmt string)(){
   prepReq := &nosqldb.PrepareRequest{ Statement: querystmt,}

   prepRes, err := client.Prepare(prepReq)
   if err != nil {
      fmt.Printf("Prepare failed: %v\n", err)
      return
   }

   queryReq := &nosqldb.QueryRequest{
		 PreparedStatement: &prepRes.PreparedStatement,}
   var results []*types.MapValue

   for {
      queryRes, err := client.Query(queryReq)
      if err != nil {
         fmt.Printf("Query failed: %v\n", err)
	  return
      }
      res, err := queryRes.GetResults()

      if err != nil {
         fmt.Printf("GetResults() failed: %v\n", err)
  	return
      }

      results = append(results, res...)
      if queryReq.IsDone() {
         break
      }
   }
   for i, r := range results {
      fmt.Printf("\t%d: %s\n", i+1,
                  jsonutil.AsJSON(r.Map()))
   }
}

querystmt_ij:= "SELECT * FROM ticket a, ticket.bagInfo.flightLegs b WHERE a.ticketNo=b.ticketNo"
fmt.Println("Fetching data using Inner Join")
fetchData(client, err,querystmt_ij)

問合せを実行するには、queryメソッドを使用します。

JavaScript: こちらのサンプルの中からフル・コードTableJoins.jsをダウンロードします。

//fetches data from the table
async function fetchData(handle,querystmt) {
   const opt = {};
   try {
      do {
         const result = await handle.query(querystmt, opt);
         for(let row of result.rows) {
            console.log('  %O', row);
         }
         opt.continuationKey = result.continuationKey;
      } while(opt.continuationKey);
   } catch(error) {
      console.error('  Error: ' + error.message);
   }
}

const stmt_ij = 'SELECT * FROM ticket a, ticket.bagInfo.flightLegs b WHERE a.ticketNo=b.ticketNo';
console.log("Fetching data using Inner Join");
await fetchData(handle,stmt_ij);

TypeScript: こちらにあるサンプルの中央からフル・コードTableJoins.tsをダウンロードします。

interface StreamInt {
   acct_Id: Integer;
   profile_name: String;
   account_expiry: TIMESTAMP;
   acct_data: JSON;
}
/* fetches data from the table */
async function fetchData(handle: NoSQLClient,querystmt: string) {
   const opt = {};
   try {
      do {
         const result = await handle.query<StreamInt>(querystmt, opt);
         for(let row of result.rows) {
            console.log('  %O', row);
         }
         opt.continuationKey = result.continuationKey;
      } while(opt.continuationKey);
   } catch(error) {
      console.error('  Error: ' + error.message);
   }
}

const stmt_ij = 'SELECT * FROM ticket a, ticket.bagInfo.flightLegs b WHERE a.ticketNo=b.ticketNo';
console.log("Fetching data using Inner Join");
await fetchData(handle,stmt_ij);

問合せを実行するには、QueryAsyncメソッドをコールするか、GetQueryAsyncEnumerableメソッドをコールして、結果の非同期列挙可能性に対して反復処理します。

こちらにあるサンプル中からフル・コードTableJoins.csをダウンロードします。

private static async Task fetchData(NoSQLClient client,String querystmt){
   var queryEnumerable = client.GetQueryAsyncEnumerable(querystmt);
   await DoQuery(queryEnumerable);
}

private static async Task DoQuery(IAsyncEnumerable<QueryResult<RecordValue>> queryEnumerable){
   Console.WriteLine("  Query results:");
   await foreach (var result in queryEnumerable) {
      foreach (var row in result.Row
      {
         Console.WriteLine();
         Console.WriteLine(row.ToJsonString());
      }
   }
}

private const string stmt_ij ="SELECT * FROM ticket a, ticket.bagInfo.flightLegs b WHERE a.ticketNo=b.ticketNo";
Console.WriteLine("Fetching data using Inner Join: ");
await fetchData(client,stmt_ij);