以下の高階関数(higher order function)のサンプルノートブックをウォークスルーします。
ネストされたデータを持つテーブルの作成
テーブルとは言っていますが、実際には一時ビューです。
CREATE
OR REPLACE TEMPORARY VIEW nested_data AS
SELECT
id AS key,
ARRAY(
CAST(RAND(1) * 100 AS INT),
CAST(RAND(2) * 100 AS INT),
CAST(RAND(3) * 100 AS INT),
CAST(RAND(4) * 100 AS INT),
CAST(RAND(5) * 100 AS INT)
) AS values,
ARRAY(
ARRAY(
CAST(RAND(1) * 100 AS INT),
CAST(RAND(2) * 100 AS INT)
),
ARRAY(
CAST(RAND(3) * 100 AS INT),
CAST(RAND(4) * 100 AS INT),
CAST(RAND(5) * 100 AS INT)
)
) AS nested_values
FROM
range(5)
SELECT * FROM nested_data
シンプルな例
基本的な変換処理でコンセプトの基礎を学びましょう。このケースでは、高階関数transform
が配列values
に対してイテレーションを行い、関連づけられたラムダ関数をそれぞれの要素に適用し、新たな配列を作成します。ラムダ関数element + 1
は、それぞれの要素をどの様に操作するのかを指定します。このSQLは以下の様になります
SELECT
key,
values,
TRANSFORM(values, value -> value + 1) AS values_plus_one
FROM
nested_data
変換処理TRANSFORM(values, value -> value + 1)
は2つのコンポーネントから構成されます:
-
TRANSFORM(values..)
は高階関数です。これは、入力として配列と匿名関数を受け取ります。内部的には、新規配列のセットアップ、それぞれの要素への匿名関数の適用、出力配列への結果の割り当てを行います。 -
value -> value + 1
は匿名関数です。この関数は、シンボル->
で区切られた2つのコンポーネントから構成されます。- 引数のリスト。この場合は引数は1つの
value
です。(x, y) -> x + y
のように、括弧で囲まれたカンマ区切りの引数リストを作成することで複数の引数もサポートしています。 - 本体。新たな値を計算するために引数と外部の変数を使用できるエクスプレッションです。この場合、
argument
の値に1
を加算しています。
- 引数のリスト。この場合は引数は1つの
変数のキャプチャ
ラムダ関数では引数だけではなく、他の変数を使用することもできます。これはキャプチャと呼ばれます。トップレベルで定義されている変数や中間のラムダ関数で定義されている変数を使用することができます。例えば、以下の変換処理ではkey
(トップレベル)変数を、配列values
のそれぞれの要素に加算しています。
SELECT
key,
values,
TRANSFORM(values, value -> value + key) AS values_plus_key
FROM
nested_data
トップレベルの変数key
の値を配列の各要素に加算しています。
ネスト化
深くネストされたデータを変換したい場合、ネストされたラムダ関数を使用することができます。以下の例では、integerの配列の配列を変換し、ネストされた配列のそれぞれの要素に、key
(トップレベル)カラムの値と中間配列のサイズを加算しています。
SELECT
key,
nested_values,
TRANSFORM(nested_values, values -> TRANSFORM(values, value -> value + key + SIZE(values))) AS new_nested_values
FROM
nested_data
サポートされる関数
transform(array<T>, function<T, U>): array<U>
入力であるarray<T>
のそれぞれの要素にfunction<T, U>
を適用することでarray<U>
を変換します。
これは機能的にはmap
と同じものとなります。(キーバリューエクスプレッションからmapを作成する)mapエクスプレッションとの混乱を避けるためにtransform
と名付けられています。
以下のクエリーでは、それぞれの要素にkey
の値を加算することで配列values
を変換しています。
SELECT key,
values,
TRANSFORM(values, value -> value + key) transformed_values
FROM nested_data
exists(array<T>, function<T, V, Boolean>): Boolean
入力のarray<T>
の要素が述語function<T, Boolean>
に合致するかどうかをテストします。
以下の例では、配列values
に10で割った余りが1になる要素があるかどうかをチェックしています。
SELECT
key,
values,
EXISTS(values, value -> value % 10 == 1) filtered_values
FROM
nested_data
filter(array<T>, function<T, Boolean>): array<T>
入力array<T>
から述語function<T, boolean>
にマッチするもののみを追加することで出力array<T>
にフィルタリングします。
以下の例では、value > 50
を満たす要素のみからなるvalues
配列にフィルタリングしています。
SELECT key,
values,
FILTER(values, value -> value > 50) filtered_values
FROM nested_data
aggregate(array<T>, B, function<B, T, B>, function<B, R>): R
function<B, T, B>
を用いて、要素をバッファーB
にマージし、最終的なバッファーに最後のfunction<B, R>
を適用することで、array<T>
の要素を単一の値R
にまとめます。B
の初期値はゼロエクスプレッションによって決定されます。最後の関数はオプションです。最終化の関数を指定しない場合、何も変化させない関数(id -> id)
が使用されます。
これは、2つのラムダ関数を使う唯一の高階関数です。
以下の例では、values
配列を単一の(sum)の値に合計(集計)しています。最終化関数(summed_values
)のバージョンと、最終化関数なしのsummed_values_simple
バージョンを示しています。
注意 以下で使用しているREDUCE
関数はAGGREGATE
関数と同じものです。
SELECT key,
values,
REDUCE(values, 0, (value, acc) -> value + acc, acc -> acc) summed_values,
REDUCE(values, 0, (value, acc) -> value + acc) summed_values_simple
FROM nested_data
さらに複雑な集計処理を行うこともできます。以下のコードでは、配列の要素のジオメトリックな平均値を計算しています。
SELECT key,
values,
AGGREGATE(values,
(1 AS product, 0 AS N),
(buffer, value) -> (value * buffer.product, buffer.N + 1),
buffer -> Power(buffer.product, 1.0 / buffer.N)) geomean
FROM nested_data