はじめに
本記事は、社内の非エンジニア向けの BigQuery SQL入門ドキュメント の BigQueryの関数編 を公開したものです
良く使う関数はデータや分析の特性によって異なるので、あくまでも独断と偏見に基づく関数セレクションです
カテゴリ内の関数の並び順はアルファベット順ではなく、個人的に良く使っていると思う順です
慣れてきたら BigQuery の公式リファレンス を確認することをおすすめします
MySQL や PostgreSQL の利用経験はあるが BigQueryは初めてという社内メンバーをメインターゲットにしているので、そことの違いを補足しています
分析関数はBigQuery固有のものでもないので、他サイト等の解説を参考ください
https://cloud.google.com/bigquery/docs/reference/standard-sql/analytic-function-concepts
https://resanaplaza.com/2021/10/17/【ひたすら図で説明】一番やさしい-sql-window-関数(分/
https://qiita.com/tlokweng/items/fc13dc30cc1aa28231c5
変換関数
https://cloud.google.com/bigquery/docs/reference/standard-sql/conversion_functions
typenameへの型変換を行う
format_clause でフォマットすることも可能
CASTで良く使うデータ型
種類
型
数値型
● INT64(INT, SMALLINT, INTEGER, BIGINT, TINYINT, BYTEINT) ● NUMERIC(DECIMAL) ● BIGNUMERIC(BIGDECIMAL) ● FLOAT64
文字列型
● STRING
日付型
● DATE
日時型
● DATETIME
時刻型
● TIME
例
format_date
cast_format
2008-12-25 Thursday Thu.
2008-12-25 THURSDAY THU.
日付関数
https://cloud.google.com/bigquery/docs/reference/standard-sql/date_functions
指定したタイムゾーンの現在日付を返す
MySQLとPostgreSQLにもある関数
MySQLで良く使われる CURDATE は使えない
例
指定した粒度まで日付を切り詰める
日別データを、週、月、年で集計する際に活用できる非常に便利な関数
PostgreSQLにもある関数
例
year
month
week
week_monday
2008-01-01
2008-12-01
2008-12-21
2008-12-22
指定した時間間隔を DATE に追加する
MySQLの + interval 1 day や PostgreSQLの + cast('1 days' as INTERVAL) は使えないが、日数だけであれば +/- 演算子が利用可能
MySQLにもある関数
例
例
指定した日付の一部に対応する値が返す(part で単位を年、月、日などが指定できる)
MySQLとPostgreSQLにもある関数
MySQLで使える year, month, day は使えない
PostgreSQLで使えるdate_part は使えない
例
day
day_of_week
day_of_year
week
week_monday
iso_week
month
quarter
year
25
5
360
51
51
52
12
4
2008
日付の差分を返す(date_part で単位を年、月、日などが指定できる)
MySQLの DATEDIFF や PERIODDIFF は使えない
PostgreSQLのように +/- で日数やINTERVAL型のようには使えない
例
days_diff
weeks_diff
weeks_diff2
months_diff
months_diff2
years_diff
years_diff2
1
0
1
0
1
0
1
DATE型の値を返す
MySQLにもある関数
PostgreSQLの make_date 相当
例
date_ymd
date_dt
date_jst
2008-12-25
2008-12-25
2008-12-25
指定された形式 に従って日付をフォーマットする
MySQL の DATE_FORMAT 相当となるが、引数の順番もフォーマットの指定方法も異なる
PostgreSQLの TO_CHAR 相当となるが、引数の順番もフォーマットの指定方法も異なる
特に変換しなくても、SpreadやExcel等のツールでは日付型で認識され、週、月単位への切り詰めは DATE_TRUNC があり、 EXTRACT もあるので、利用するケースはあまりないかも
例
format_date
cast_format
2008-12-25 Thursday Thu.
2008-12-25 THURSDAY THU.
date_expression の date_part 単位での最終日取得する
デフォルトでは月の最終日
MySQLにもある関数
例
例
date1
date2
date3
2008-12-25
2008-12-15
2008-12-15
日時関数
https://cloud.google.com/bigquery/docs/reference/standard-sql/datetime_functions
日付関数 と同様のため説明は省略
タイムスタンプ関数
https://cloud.google.com/bigquery/docs/reference/standard-sql/timestamp_functions#timezone_definitions
日付関数 と同様のため説明は省略
文字列関数
https://cloud.google.com/bigquery/docs/reference/standard-sql/string_functions
指定した値(複数可)を 1 つに連結する
BYTES か、STRING にキャスト可能な型であれば、value にそのまま指定できる
連結演算子(||) でも同じことが可能
MySQLとPostgreSQLにもある関数
例
string1
string2
string3
A-1
A-1
A-1
文字列型の文字数、バイト型のバイト数を返す
文字列のバイト数は BYTE_LENGTH でも可能
MySQLのLENGTHはバイト単位なので、CHAR_LENGTH相当
PostgreSQLにもある関数
例
characters
string_example
bytes_example
byte_length_example
abcあ
4
6
6
文字列/バイトの先頭から指定文字数/バイト数の文字列を返す
例
文字列/バイトの末尾から指定文字数/バイト数の文字列を返す
例
value に指定した文字列/バイトの position 文字目から length 文字数/バイト数の文字列を返す
例
original_value 内に出現するすべての from_value を to_value に置き換える
例
replace_example
A001_001_01
value が re2正規表現 regexp に対して部分一致である場合にTRUE を返す
例
re2正規表現 regexp と一致する value 内の部分文字列を返す
一致がない場合、NULL を返す
position を指定すると、value のこの位置から検索を開始する(それ以外は先頭から)
複数一致する場合は最初の一致を返すが、occurrence でそれ以外も指定可能
複数一致する場合に全部取得する場合は REGEXP_EXTRACT_ALL を使うと Array で取得できる
例
re2正規表現 regexp と一致する value のすべての部分文字列を replacement に置き換えた 文字列を返す
replacement 引数内でバックスラッシュでエスケープされた数字(\1~\9)を使用してグループと一致するテキストを regexp パターン内に挿入できる(\0 は一致するテキスト全体)
例
html
<h1>Heading</h1>
<h1>Another heading</h1>
format_string_expressionをdata_type_expressionとしてフォーマットする
数値のカンマ区切りや、数値の頭ゼロ埋め等が可能
C言語 の printf 関数と同様の仕様
PostgreSQLにもある関数
MySQLの FORMAT 相当だが引数の指定方法が異なる
例
format1
format2
format3
format4
123,456,789
+123,456,789
001
123,456,789.000
specifier
説明
d または i
10 進の整数
f または F
整数.小数。大文字小文字は非有限値の表記違い。
s
文字列
flag
説明
0
widthが指定されている場合スペースではなく、ゼロ(0)で数字の左側にパディング
'
10進数の場合はカンマ区切り形式へ
+
正の数値であっても、結果の前にプラス記号またはマイナス記号(+ または -)を強制。デフォルトでは、負の数にのみ - 記号が前に付けられる。
数値を指定すると、この数値よりも短い場合、結果は空白スペースを使用してパディングされる。(flagで0指定した場合はゼロ(0)でパディングされる。
小数点の後に出力される桁数を 小数に .数値 で指定できる(デフォルトは6)
delimiter 引数を使用して value を分割した Array を返す
例
前後の空白を除去する
set_of_characters_to_remove で空白以外の文字列も指定可能
例
Unicode正規化 された文字列を返す
「1」「1」「①」など等価な文字の表記を統一できるのでNLPの前処理などで使う
大文字小文字を区別する場合は NORMALIZE を使う
normalization_mode の違いは難しいが、NFKC を使うケースが多いかと
例
value
NFC
NFD
NFKC
NFKD
value_length
NFC_length
NFD_length
NFKC_length
NFKD_length
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
①
①
①
1
1
1
1
1
1
1
a
a
a
a
a
1
1
1
1
1
A
a
a
a
a
1
1
1
1
1
a
a
a
a
a
1
1
1
1
1
A
a
a
a
a
1
1
1
1
1
ガ
ガ
ガ
ガ
ガ
2
2
2
1
2
ガ
ガ
ガ
ガ
ガ
1
1
2
1
2
㈱
㈱
㈱
(株)
(株)
1
1
1
3
3
(株)
(株)
(株)
(株)
(株)
3
3
3
3
3
(株)
(株)
(株)
(株)
(株)
3
3
3
3
3
神
神
神
神
神
1
1
1
1
1
神
神
神
神
神
1
1
1
1
1
㍍
㍍
㍍
メートル
メートル
1
1
1
4
4
JSON関数
https://cloud.google.com/bigquery/docs/reference/standard-sql/json_functions
下記はレガシーなので利用非推奨
JSON_EXTRACT
JSON_EXTRACT_SCALAR
JSON_EXTRACT_ARRAY
JSON_EXTRACT_STRING_ARRAY
JSON文字列又はJSON型の値からスカラー値を抽出する
最も外側の引用符を削除し、戻り値のエスケープを解除する
例
json_text
student_name
students
{"class" : {"students" : [{"name" : "Jane"}]}}
Jane
(null)
{"class" : {"students" : []}}
(null)
(null)
{"class" : {"students" : [{"name" : "John"}, {"name": "Jamie"}]}}
John
(null)
JSON文字列又はJSON型の値からスカラー値以外(配列やオブジェクトなど)の値を抽出する
例
json_text
student_name
students
{"class" : {"students" : [{"name" : "Jane"}]}}
"Jane"
[{"name":"Jane"}]
{"class" : {"students" : []}}
(null)
[]
{"class" : {"students" : [{"name" : "John"}, {"name": "Jamie"}]}}
"John"
[{"name":"John"},{"name":"Jamie"}]
JSON文字列又はJSON型の値からスカラー値(文字列、数値、またはブール値)の配列を抽出する
例
JSON文字列又はJSON型の値から、配列やオブジェクトなどの JSON 値の配列と、文字列、数値、ブール値などの JSON スカラー値を抽出
例
Net関数
https://cloud.google.com/bigquery/docs/reference/standard-sql/net_functions
URLやIPアドレスを加工するときに便利な関数がある
例
集計関数
https://cloud.google.com/bigquery/docs/reference/standard-sql/aggregate_functions
ここであげている集計関数は全てOVER句を指定すると分析関数 として利用可能
例
抽出元テーブル
customer_id
amount
order_date
payment_method
1
100
2008-12-25
credit
2
200
2008-12-25
bank
3
300
2008-12-25
credit
1
1000
2009-12-26
credit
2
2000
2009-12-26
bank
3
3000
2009-12-26
credit
1
10000
2010-12-24
invoice
SELECT
customer_id,
COUNT ( * ) AS count_order,
COUNT ( DISTINCT payment_method) AS count_distinct_payment_method,
SUM ( amount) AS sum_amount,
AVG ( amount) AS avg_amount,
MIN ( order_date) AS min_order_date,
MAX ( order_date) AS max_order_date,
STRING_AGG( payment_method) as payment_methods,
COUNT ( CASE WHEN payment_method = 'credit' THEN 1 END ) as count_credit,
COUNT ( IF ( payment_method = 'bank' , 1 , NULL ) ) as count_bank,
COUNTIF( payment_method = 'invoice' ) as count_invoice,
SUM ( CASE WHEN payment_method = 'credit' THEN amount ELSE 0 END ) AS sum_credit_amount,
SUM ( IF ( payment_method = 'bank' , amount, 0 ) ) AS sum_bank_amount,
SUM ( IF ( payment_method = 'invoice' , amount, 0 ) ) AS sum_bank_amount
FROM
UNNEST( [
struct( 1 AS customer_id, 100 AS amount, DATE '2008-12-25' as order_date, 'credit' as payment_method) ,
struct( 2 AS customer_id, 200 AS amount, DATE '2008-12-25' as order_date, 'bank' as payment_method) ,
struct( 3 AS customer_id, 300 AS amount, DATE '2008-12-25' as order_date, 'credit' as payment_method) ,
struct( 1 AS customer_id, 1000 AS amount, DATE '2009-12-26' as order_date, 'credit' as payment_method) ,
struct( 2 AS customer_id, 2000 AS amount, DATE '2009-12-26' as order_date, 'bank' as payment_method) ,
struct( 3 AS customer_id, 3000 AS amount, DATE '2009-12-26' as order_date, 'credit' as payment_method) ,
struct( 1 AS customer_id, 10000 AS amount, DATE '2010-12-24' as order_date, 'invoice' as payment_method)
] ) AS orders
GROUP BY
customer_id
customer_id
count_order
count_distinct_payment_method
sum_amount
avg_amount
min_order_date
max_order_date
payment_methods
count_credit
count_bank
count_invoice
sum_credit_amount
sum_bank_amount
sum_bank_amount_1
1
3
2
11100
3700.0
2008-12-25
2010-12-24
credit,credit,invoice
2
0
1
1100
0
10000
2
2
1
2200
1100.0
2008-12-25
2009-12-26
bank,bank
0
2
0
0
2200
0
3
2
1
3300
1650.0
2008-12-25
2009-12-26
credit,credit
2
0
0
3300
0
0
COUNT(*) の場合は行数、 expression の場合は NULL 以外の行数を返す
条件式(CASEやIF)を使ってNULLにすることで条件付きカウントが可能
COUNTIF を使うと条件付きカウントをすっきり書ける場合もある
COUNT(*) は性能悪いので COUNT(0) や COUNT(1) を使うと良いというデマがあるがそんなことはないので COUNT(*) を使うで良い
DISTINCT を指定すると重複しない値の数を返す
NULL 以外の値の合計を返す
expression に条件式を書くことで条件付き合計が可能
非 NULL 値を結合して得られた値(STRING または BYTES のいずれか)を返す
DISTINCT を指定すると重複を除いた値を集約して結果を返す
ORDER BY で値の順序を指定できる
LIMIT で出力数の最大値を指定できる
mysql の GROUP_CONCAT 相当
ナビゲーション関数
https://cloud.google.com/bigquery/docs/reference/standard-sql/navigation_functions
分析関数 のサブセット
現在の行を基にしてウィンドウ フレーム内の別の行に対していくつかの value_expression が計算される
現在のウィンドウ フレーム内の最初の行の value_expression の値を返す
IGNORE NULLS を指定すると NULL 値が除外される
現在のウィンドウ フレーム内の最後の行の value_expression の値を返す
IGNORE NULLS を指定すると NULL 値が除外される
FIRST_VALUE の ORDER BY の ASC/DESC で対応した方がパフォーマンスが良いケースが多い
現在のウィンドウ フレーム内の N 番目の行の value_expression の値を返す
IGNORE NULLS を指定すると NULL 値が除外される
先行する 1 つの行の value_expression の値をデフォルトで1つ返す
offset の値を変更すると、どれだけ後の行が返されるか指定できる
指定したオフセットの行がウィンドウ枠内に存在しない場合は、オプションの default_expression が使用さる
後続の 1 つの行の value_expression の値をデフォルトで1つ返す
offset の値を変更すると、どれだけ後の行が返されるか指定できる
指定したオフセットの行がウィンドウ枠内に存在しない場合は、オプションの default_expression が使用さる
value_expression に対して指定されたパーセンタイル値を計算
percentile に 0.5 を指定すると 中央値(Median)となる
percentile に 0.25 を指定すると 第一四分位となる
percentile に 0.75 を指定すると 第三四分位となる
番号付け関数
https://cloud.google.com/bigquery/docs/reference/standard-sql/numbering_functions
分析関数 のサブセット
指定されたウィンドウ内の行の位置に基づいて、各行に整数値を割り当てる
各パーティションの各行の順位(1開始)を返す
同一順位が複数あると、次のランク値がその分飛ぶ
各パーティションの各行の順位(1開始)を返す
同一順位が複数あても次のランク値は+1だけされる
ソートしたデータを constant_integer_expression で指定された数のグループに均等にグルーピングする
デシル分析をする際に便利
統計集計関数
https://cloud.google.com/bigquery/docs/reference/standard-sql/statistical_aggregate_functions
BigQueryの統計処理の関数はそこまで充実していないため、BigQuery上で t検定等したい場合は、UDFで外部JavaScript(Jstatなど)を利用する方法がある
https://lab.mo-t.com/blog/bigquery-udf
CORR ピアソン相関係数
近似集計関数
https://cloud.google.com/bigquery/docs/reference/standard-sql/approximate_aggregate_functions
正確な結果ではなく、近似的な結果だが、性能が良い関数
expression の最頻値(MODE)の STRUCT型(value, count)の配列を返す
number パラメータで、返される要素の数を指定できる
weight の合計の 最大値 の expression の STRUCT型(value, sum)の配列を返す
number パラメータで、返される要素の数を指定できる
expression の値のグループに対する近似境界の値の配列を返す
number で作成する変位値の数を指定できる(配列の要素数は number + 1)
最初の要素は近似最小値であり、最後の要素は近似最大値
中央値は PERCENTILE_CONT (0.5, x) よりも APPROX_QUANTILES(x, 2)[OFFSET(1)] の方が高速
配列関数
https://cloud.google.com/bigquery/docs/reference/standard-sql/array_functions
array_expression 内の要素を delimiter で連結した文字列を返す
NULL値はデフォルト無視されが、 null_text を指定することもできる
例
text
text_null
coffee,tea,milk
coffee,tea,milk
cake,pie
cake,pie,NULL
start_date から end_date のデフォルト1日間隔の日付の配列を返す
INTERVAL で間隔は指定できる
UNNEST と合わせてカレンダーテープルを作る際に便利
例
date
2016-10-05
2016-10-06
2016-10-07
2016-10-08
SAFE.接頭辞
https://cloud.google.com/bigquery/docs/reference/standard-sql/functions-reference?hl=ja#safe_prefix
関数の先頭に SAFE. 接頭辞を付けると、エラーではなく NULL が返される
便利な演算子
関数ではないが使い方を忘れがちな演算子をいくつか
ARRAY を受け取り、ARRAY 内の各要素を 1 行にしてテーブルを返す
STRUCT型のARRAYの場合、STRUCT 内の各フィールドについてそれぞれ別個の列が生成される
WITH OFFSET 句は、オフセット値を含む別の列を返す
FROM句での利用例
IN 演算子での利用例
ネストされた配列のフラット化例
入力テーブル(nested)
WITH base AS (
SELECT
offset + 1 as id,
* except ( offset ) ,
orders
FROM
UNNEST( ARRAY< STRUCT< customer_id INT64, amount INT64, order_date DATE , payment_method STRING>> [
( 1 , 100 , '2008-12-25' , 'credit' ) ,
( 2 , 200 , '2008-12-25' , 'bank' ) ,
( 3 , 300 , '2008-12-25' , 'credit' ) ,
( 1 , 1000 , '2009-12-26' , 'credit' ) ,
( 2 , 2000 , '2009-12-26' , 'bank' ) ,
( 3 , 3000 , '2009-12-26' , 'credit' ) ,
( 1 , 10000 , '2010-12-24' , 'invoice' )
] ) AS orders WITH OFFSET
) ,
nested AS (
SELECT
customer_id
, ARRAY_AGG( struct( id, amount, order_date, payment_method) ) AS orders
FROM
base
GROUP BY
customer_id
)
SELECT
o. id,
n. customer_id,
o. amount,
o. order_date,
o. payment_method
FROM
nested n
CROSS JOIN UNNEST( n. orders) AS o
ORDER BY
o. id
id
customer_id
amount
order_date
payment_method
1
1
100
2008-12-25
credit
2
2
200
2008-12-25
bank
3
3
300
2008-12-25
credit
4
1
1000
2009-12-26
credit
5
2
2000
2009-12-26
bank
6
3
3000
2009-12-26
credit
7
1
10000
2010-12-24
invoice
行を列に変換する
Rのtidyrのspread, pivot_wider 相当
例
WITH produce AS (
SELECT
*
FROM
UNNEST( ARRAY< STRUCT< product STRING, sales INT64, quarter STRING, year INT64>> [
( 'Kale' , 51 , 'Q1' , 2020 ) ,
( 'Kale' , 23 , 'Q2' , 2020 ) ,
( 'Kale' , 45 , 'Q3' , 2020 ) ,
( 'Kale' , 3 , 'Q4' , 2020 ) ,
( 'Kale' , 70 , 'Q1' , 2021 ) ,
( 'Kale' , 85 , 'Q2' , 2021 ) ,
( 'Apple' , 77 , 'Q1' , 2020 ) ,
( 'Apple' , 0 , 'Q2' , 2020 ) ,
( 'Apple' , 1 , 'Q1' , 2021 )
] ) AS produce
)
SELECT
*
FROM
produce
PIVOT (
SUM ( sales)
FOR quarter IN ( 'Q1' , 'Q2' , 'Q3' , 'Q4' )
)
product
year
Q1
Q2
Q3
Q4
Kale
2020
51
23
45
3
Kale
2021
70
85
Apple
2020
77
0
Apple
2021
1
列を行に変換する
Rのtidyrの gather, pivot_longer 相当
例
WITH produce AS (
SELECT
*
FROM
UNNEST( ARRAY< STRUCT< product STRING, sales INT64, quarter STRING, year INT64>> [
( 'Kale' , 51 , 'Q1' , 2020 ) ,
( 'Kale' , 23 , 'Q2' , 2020 ) ,
( 'Kale' , 45 , 'Q3' , 2020 ) ,
( 'Kale' , 3 , 'Q4' , 2020 ) ,
( 'Kale' , 70 , 'Q1' , 2021 ) ,
( 'Kale' , 85 , 'Q2' , 2021 ) ,
( 'Apple' , 77 , 'Q1' , 2020 ) ,
( 'Apple' , 0 , 'Q2' , 2020 ) ,
( 'Apple' , 1 , 'Q1' , 2021 )
] ) AS produce
)
, pivoted AS (
SELECT
*
FROM
produce
PIVOT (
SUM ( sales)
FOR quarter IN ( 'Q1' , 'Q2' , 'Q3' , 'Q4' )
)
)
SELECT
*
FROM
pivoted
UNPIVOT ( sales FOR quarter IN ( Q1, Q2, Q3, Q4) )
product
year
sales
quarter
Kale
2020
51
Q1
Kale
2020
23
Q2
Kale
2020
45
Q3
Kale
2020
3
Q4
Kale
2021
70
Q1
Kale
2021
85
Q2
Apple
2020
77
Q1
Apple
2020
0
Q2
Apple
2021
1
Q1
Discussion
追記予定