配列およびマップのネスト解除
unnest_syntaxを使用して、配列またはマップを含む行をフラット化します。
ネスト解除の構文
unnest_expression::=expression AS VARNAME
unnest_clause ::= UNNEST((unnest_expression)*)
unnest_syntax ::=(unnest_expression | unnest_clause)*
セマンティクス
SELECT文のFROM句には、式のリスト(関連する変数を含む)を含めることができます。通常、配列またはマップをネスト解除する式を使用し、FROM句では、ネスト解除された配列/マップの値から新しい行のセットを作成します。これらの式の一部またはすべてをUNNEST句に含めることができます。意味的に、ネスト解除演算子はno-opであり、つまり、UNNEST句が使用されているかどうかは、FROM句の結果を変更せず、unnest(expr)はexprと同じです。UNNEST句の目的は、最適化ヒントとして機能することです。具体的には、ネスト解除される配列/マップに索引がある場合、問合せでUNNEST句が使用されている場合のみ、索引を問合せで使用できます。また、このような索引を使用する問合せプロセッサを支援するために、UNNEST句では、その中に現れる式にいくつかの制限があります。
通常、配列/マップ内のフィールドでグループ化する場合は、ネスト解除を使用します。ただし、次の2つの例は、ネスト解除式のセマンティクスを示しています。このため、次の問合せにGROUP BY句は使用されません。
FROM句での単一のネスト解除式の使用(単一のマップのネスト解除):
ノート:
例で使用されているJSON列はマップです。create table sample_unnest ( samp_id INTEGER, samp_data JSON, PRIMARY KEY(samp_id))
INSERT INTO sample_unnest VALUES(1,
{ "episodeID" : 20, "lengthMin" : 40, "minWatched" : 40 })
INSERT INTO sample_unnest VALUES(2,
{ "episodeID" : 25, "lengthMin" : 20, "minWatched" : 18 })
select * from sample_unnest
$s1,unnest($s1.samp_data.values() as $s2) where $s1.samp_id=1
前述のFROM句は、変数$s1
により表sample_unnest
および変数$s2
によりマップsamp_data
の値を参照します。概念的には、FROM句の結果は2列の表temp_tbl
です。列1には表sample_unnest
のすべての行が格納され、列2にはsamp_data
マップの要素のすべての値が格納されます。
表6-4 temp_tbl表内のレコード
$s1 | $s2 |
---|---|
"samp_id":1,"samp_data":{"episodeID":20,"lengthMin":40,"minWatched":40} |
20 |
"samp_id":1,"samp_data":{"episodeID":20,"lengthMin":40,"minWatched":40} |
40 |
"samp_id":1,"samp_data":{"episodeID":20,"lengthMin":40,"minWatched":40} |
40 |
"samp_id":2,"samp_data":{"episodeID":25,"lengthMin":20,"minWatched":18} |
25 |
"samp_id":2,"samp_data":{"episodeID":25,"lengthMin":20,"minWatched":18} |
20 |
"samp_id":2,"samp_data":{"episodeID":25,"lengthMin":20,"minWatched":18} |
18 |
$s1.samp_id=1
)になる行については、対応する行が問合せ結果に含まれます。条件は最初の3行についてTRUE、最後の3行ではFALSEと評価されます。
表6-5 問合せ出力
$s1 | $s2 |
---|---|
{"samp_id":1, "samp_data":{"episodeID":20,"lengthMin":40,"minWatched":40}} |
20 |
{"samp_id":1, "samp_data":{"episodeID":20,"lengthMin":40,"minWatched":40}} |
40 |
{"samp_id":1, "samp_data":{"episodeID":20,"lengthMin":40,"minWatched":40}} |
40 |
FROM句での2つのネスト解除式の使用(配列および配列の下にネストされているマップをネスト解除するため)
ノート:
例で使用されているJSON列はマップの配列です。create table sample_unnest (
samp_id INTEGER, samp_data JSON, PRIMARY KEY(samp_id))
INSERT INTO sample_unnest VALUES(1,
{
"episodes":[
{
"episodeID" : 10,
"lengthMin" : 40,
"minWatched" : 25
},
{
"episodeID" : 20,
"lengthMin" : 35,
"minWatched" : 30
}
]
}
)
INSERT INTO sample_unnest VALUES(2,
{
"episodes":[
{
"episodeID" : 30,
"lengthMin" : 40,
"minWatched" : 25
},
{
"episodeID" : 40,
"lengthMin" : 35,
"minWatched" : 30
}
]
}
)
INSERT INTO sample_unnest VALUES(3,
{
"episodes":[
{
"episodeID" : 10,
"lengthMin" : 40,
"minWatched" : 25
},
{
"episodeID" : 20,
"lengthMin" : 35,
"minWatched" : 30
}
]
}
)
SELECT n.samp_id as customer,
$epi.episodeID,$epi.minWatched AS length,$epiVal AS episode_details FROM sample_unnest n,
unnest(n.samp_data.episodes[] AS $epi, $epi.values() AS $epiVal)
(sample_unnest n, unnest(n.samp_data.episodes[] AS $epi)
の最初の2つの式の結果である、最初の表temp_tbl
1のレコード表6-6 temp_tbl1内のレコード
n | $epi |
---|---|
"s1":{"samp_id":1,"samp_data":{"episodes":[{"episodeID":10,"lengthMin":40,"minWatched":25},{"episodeID":20,"lengthMin":35,"minWatched":30}]} |
{"episodeID":10,"lengthMin":40,"minWatched":25}, |
"s1":{"samp_id":1,"samp_data":{"episodes":[{"episodeID":10,"lengthMin":40,"minWatched":25},{"episodeID":20,"lengthMin":35,"minWatched":30}]} |
{"episodeID":20,"lengthMin":35,"minWatched":30} |
"s1":{"samp_id":2,"samp_data":{"episodes":[{"episodeID":30,"lengthMin":40,"minWatched":25},{"episodeID":40,"lengthMin":35,"minWatched":30}]} |
{"episodeID":30,"lengthMin":40,"minWatched":25}, |
"s1":{"samp_id":2,"samp_data":{"episodes":[{"episodeID":30,"lengthMin":40,"minWatched":25},{"episodeID":40,"lengthMin":35,"minWatched":30}]} |
{"episodeID":40,"lengthMin":35,"minWatched":30} |
"s1":{"samp_id":3,"samp_data":{"episodes":[{"episodeID":10,"lengthMin":40,"minWatched":25},{"episodeID":20,"lengthMin":35,"minWatched":30}]} |
{"episodeID":10,"lengthMin":40,"minWatched":25}, |
"s1":{"samp_id":3,"samp_data":{"episodes":[{"episodeID":10,"lengthMin":40,"minWatched":25},{"episodeID":20,"lengthMin":35,"minWatched":30}]} |
{"episodeID":20,"lengthMin":35,"minWatched":30} |
$epi.values()as $epiVal
)
表6-7 temp_tbl2内のレコード
n | $epi | $epival |
---|---|---|
{"s1":{"samp_id":1,"samp_data":{"episodes":[{"episodeID":10,"lengthMin":40,"minWatched":25},{"episodeID":20,"lengthMin":35,"minWatched":30}]} |
{"episodeID":10,"lengthMin":40,"minWatched":25}, |
10 |
{"s1":{"samp_id":1,"samp_data":{"episodes":[{"episodeID":10,"lengthMin":40,"minWatched":25},{"episodeID":20,"lengthMin":35,"minWatched":30}]} |
{"episodeID":10,"lengthMin":40,"minWatched":25} |
40 |
{"s1":{"samp_id":1,"samp_data":{"episodes":[{"episodeID":10,"lengthMin":40,"minWatched":25},{"episodeID":20,"lengthMin":35,"minWatched":30}]} |
{"episodeID":10,"lengthMin":40,"minWatched":25} |
25 |
{"s1":{"samp_id":1,"samp_data":{"episodes":[{"episodeID":10,"lengthMin":40,"minWatched":25},{"episodeID":20,"lengthMin":35,"minWatched":30}]}} |
{"episodeID":20,"lengthMin":35,"minWatched":30} |
20 |
{"s1":{"samp_id":1,"samp_data":{"episodes":[{"episodeID":10,"lengthMin":40,"minWatched":25},{"episodeID":20,"lengthMin":35,"minWatched":30}]}} |
{"episodeID":20,"lengthMin":35,"minWatched":30} |
35 |
{"s1":{"samp_id":1,"samp_data":{"episodes":[{"episodeID":10,"lengthMin":40,"minWatched":25},{"episodeID":20,"lengthMin":35,"minWatched":30}]}} |
{"episodeID":20,"lengthMin":35,"minWatched":30} |
30 |
{"s1":{"samp_id":2,"samp_data":{"episodes":[{"episodeID":30,"lengthMin":40,"minWatched":25},{"episodeID":40,"lengthMin":35,"minWatched":30}]}} |
{"episodeID":30,"lengthMin":40,"minWatched":25} |
30 |
{"s1":{"samp_id":2,"samp_data":{"episodes":[{"episodeID":30,"lengthMin":40,"minWatched":25},{"episodeID":40,"lengthMin":35,"minWatched":30}]}} |
{"episodeID":30,"lengthMin":40,"minWatched":25} |
40 |
{"s1":{"samp_id":2,"samp_data":{"episodes":[{"episodeID":30,"lengthMin":40,"minWatched":25},{"episodeID":40,"lengthMin":35,"minWatched":30}]}} |
{"episodeID":30,"lengthMin":40,"minWatched":25} |
25 |
{"s1":{"samp_id":2,"samp_data":{"episodes":[{"episodeID":30,"lengthMin":40,"minWatched":25},{"episodeID":40,"lengthMin":35,"minWatched":30}]}} |
{"episodeID":40,"lengthMin":35,"minWatched":30} |
40 |
{"s1":{"samp_id":2,"samp_data":{"episodes":[{"episodeID":30,"lengthMin":40,"minWatched":25},{"episodeID":40,"lengthMin":35,"minWatched":30}]}} |
{"episodeID":40,"lengthMin":35,"minWatched":30} |
35 |
{"s1":{"samp_id":2,"samp_data":{"episodes":[{"episodeID":30,"lengthMin":40,"minWatched":25},{"episodeID":40,"lengthMin":35,"minWatched":30}]}} |
{"episodeID":40,"lengthMin":35,"minWatched":30} |
30 |
{"s1":{"samp_id":3,"samp_data":{"episodes":[{"episodeID":10,"lengthMin":40,"minWatched":25},{"episodeID":20,"lengthMin":35,"minWatched":30}]} |
{"episodeID":10,"lengthMin":40,"minWatched":25} |
10 |
{"s1":{"samp_id":3,"samp_data":{"episodes":[{"episodeID":10,"lengthMin":40,"minWatched":25},{"episodeID":20,"lengthMin":35,"minWatched":30}]} |
{"episodeID":10,"lengthMin":40,"minWatched":25} |
40 |
{"s1":{"samp_id":3,"samp_data":{"episodes":[{"episodeID":10,"lengthMin":40,"minWatched":25},{"episodeID":20,"lengthMin":35,"minWatched":30}]} |
{"episodeID":10,"lengthMin":40,"minWatched":25} |
25 |
{"s1":{"samp_id":3,"samp_data":{"episodes":[{"episodeID":10,"lengthMin":40,"minWatched":25},{"episodeID":20,"lengthMin":35,"minWatched":30}]} |
{"episodeID":20,"lengthMin":35,"minWatched":30} |
20 |
{"s1":{"samp_id":3,"samp_data":{"episodes":[{"episodeID":10,"lengthMin":40,"minWatched":25},{"episodeID":20,"lengthMin":35,"minWatched":30}]} |
{"episodeID":20,"lengthMin":35,"minWatched":30} |
35 |
{"s1":{"samp_id":3,"samp_data":{"episodes":[{"episodeID":10,"lengthMin":40,"minWatched":25},{"episodeID":20,"lengthMin":35,"minWatched":30}]} |
{"episodeID":20,"lengthMin":35,"minWatched":30} |
30 |
表6-8 問合せ結果
顧客 | episode_id | 長さ | episode_details |
---|---|---|---|
2 | 30 | 25 | 30 |
2 | 30 | 25 | 40 |
2 | 30 | 25 | 25 |
2 | 40 | 30 | 40 |
2 | 40 | 30 | 35 |
2 | 40 | 30 | 30 |
1 | 10 | 25 | 10 |
1 | 10 | 25 | 40 |
1 | 10 | 25 | 25 |
1 | 20 | 30 | 20 |
1 | 20 | 30 | 35 |
1 | 20 | 30 | 30 |
3 | 10 | 25 | 10 |
3 | 10 | 25 | 40 |
3 | 10 | 25 | 25 |
3 | 20 | 30 | 20 |
3 | 20 | 30 | 35 |
3 | 20 | 30 | 30 |
UNNEST句での式の使用の制限
- UNNEST句で使用するすべての式は、パス式である必要があります。
- パス式内の配列フィルタまたはマップ・フィルタ・ステップでは、述語は許可されません。
- 式は変数で開始する必要があります。式リストの最初の式である場合、その式はターゲット表に関連付けられた表の別名である必要があります。
- ネストされた配列をネスト解除すると、各パス式は1レベル深くネスト解除し、前のレベルのネスト解除によって生成された値に対して動作します。これらの値は、前のパス式に関連付けられた変数で表されます。そのため、各式の開始変数は、前の式に関連付けられている変数である必要があります。これは、UNNEST句の式のリストの最初の式である場合は適用されません。
- 式は[]または.values()ステップで終了する必要があります。
- UNNEST句内で定義された変数は、FROM句の残りの部分で参照できません。それらは、UNNEST句内およびFROM句外で参照できます。