BigQuery
https://gyazo.com/c32baba970bf9c8175cb295eb9043b5f
凄すぎる
テク
クエリの書き方テク
常識
SELECT * しない
列志向なので、全部なめるのと同じ料金、LIMIT で絞ってもいみはない
SELECT * EXCEPT(hoge)
Cmd+Shift+F を押せ
クエリがフォーマットされる
ゼロ除算
SAFE_DIVIDE(X, Y) 関数がある、ゼロ除算が発生すると NULL になる
IFNULL(SAFE_DIVIDE(X, Y), 0) などすると、NULL の時は 0 にできる
(ほかにも SAFE_ がいろいろある)
標準SQLとレガシーSQL
CLI はまだデフォルトがレガシー、--use_legacy_sql=false することで標準になる
DISTINCT の精度
GROUP EACH BY
SQL の常識
HAVING は ORDER BY の前
というより ORDER BY, LIMIT はとにかく後ろである
GROUP BY
重複除去
(やってない)
code:replace-distinct.sql
CREATE OR REPLACE TABLE project.dataset.table
AS SELECT DISTINCT * FROM project.dataset.table
DISTINCT と EXACT_COUNT_DISTINCT
レガシーでは DISTINCT は概算値, 正確な COUNT(DISTINCT(...)) がほしいなら EXACT_COUNT_DISTINCT を使う
標準 SQL では DISTINCT は正確な値、概算でいい場合は APPROX_COUNT_DISTINCT を使う
レガシー SQL では COUNT(DISTINCT x) は概算の数が返されます。標準 SQL では正確な数が返されます。重複しない値のおおよその数を返す APPROX_COUNT_DISTINCT を使用すると、実行時間が短縮され、必要なリソースも少なくて済みます。
DATETIME と TIMESTAMP
DATETIME オブジェクトは、タイムゾーンに依存せずにカレンダーや時計に表示される日時を表します。これには年、月、日、時、分、秒、サブ秒が含まれます。絶対的な時刻を表すには、タイムスタンプを使用します。
TIMESTAMP オブジェクトは、タイムゾーンや夏時間などの慣習に関係なく、マイクロ秒精度の絶対的な時刻を表します。
JOIN した際にカラム名がかぶる
例えば created などのカラムはよく重複する
Duplicate column names in the result are not supported. Found duplicate(s): ...
同名のカラムを出力すると上のようなエラーが出るので、以下のように EXCEPT して解決する。
code:join-except.sql
SELECT
table_a.*,
table_b.* EXCEPT(created, updated),
FROM table_a JOIN table_b ON table_a.hoge_id = table_b.id
ストリーミングバッファにあるデータに影響ある更新系クエリは流せない
UPDATE or DELETE statement over table ... would affect rows in the streaming buffer, which is not supported
参照系クエリではストリーミングバッファの内容も読まれる
更新系クエリでストリーミングバッファ上のデータが影響を受ける場合はエラーが出る
ストリーミングバッファは "最も早いエントリ時間" を持ってる
ストリーミングバッファはどのぐらいのタイミングで flush されるのか
もろもろのサイズによるらしい
26MB 50000行/h ぐらいのデータは1時間程度では flush されない、sink 作成直後だからかもしれないが...
テーブルの更新時刻
説明の編集等でも更新される、データが見たいならちゃんと SELECT すること
Firestore export の metadata ファイルは protocol buffer
source: asia-northeast1, destination: asia
BigQuery の export で Multi-Regional な bucket に export したらエラーになる
クラスタ化テーブル
WIP
View の作成の権限
クエリ文字列を入れれたらいいじゃん、というわけではなくて参照先のテーブルにアクセスできる必要がある(内部的にView作成時に参照先テーブルのスキーマを読む, 表示されうるため?)
Terraform でクロスプロジェクトの View を作って管理する、みたいなときに面倒
2022/03/16 のような / 区切りで書かれた日付を load したい
できない
CSV データまたは JSON データを読み込む場合、DATE 列の値に区切りとしてダッシュ(-)を使用し、YYYY-MM-DD(年-月-日)の形式にする必要があります。
一度 STRING としてロードして PARSE_DATE('%Y/%m/%d', string_date) する
矛盾した Timezone 指定はエラーになる
SELECT TIMESTAMP("2022-01-01T00:00:00+09:00", "America/Los_Angels") はエラーが出てくれるので安心
ロール
roles/bigquery.jobUser
プロジェクトに課金させてジョブを実行できる
? roles/bigquery.user はジョブ実行できるんだっけ
フォーマッタ
組み込みのやつはツラすぎるのであまり使わない
セッション
対話的に調べたり何個もクエリを実行するときに費用を抑えられる
CREATE TEMP TABLE name AS SELECT ... でデカめのクエリは名前つけておくとよい
コンソールから既存のセッションにアタッチできる?
できなさそう
セッション作りつつ変数にセッションIDを入れたり、クエリでセッションID指定できるとよいのだが
temporary table 作るのが主な気持ちだからだけど、temporary table 一覧がほしい
確実にキャッシュを使う
createDisposition: "CREATE_NEVER" で、キャッシュにないときは失敗する
[* ARRAY_AGG(...)[OFFSET(0)] のほうが効率がいい]
テーブル定義
カラムを捨てる
Struct の中身は EXCEPT できないので、一部を落とすには SELECT * REPLACE する
code:select-replace.sql
SELECT * REPLACE ((SELECT AS STRUCT payload.* EXCEPT (comment)) AS payload)
FROM ...
テーブル定義以外でも、STRUCT 中の特定フィールドを除いて SELECT する際に
(SELECT AS STRUCT payload.* EXCEPT (comment)) AS payload は使える
jsonPayload 以下しか興味ないなら SELECT jsonPayload.* EXCEPT(field) でもいい
カラムの型を変える
SELECT * EXCEPT (foo), CAST(foo AS INT64) as foo などで特定のカラムだけ型を変更する、元テーブルを宛先テーブルにしたらよい
jsonPayload などネストしたテーブルの場合は SELECT * REPLACE でやる
code:select-replace-example.sql
SELECT * REPLACE (
(
SELECT AS STRUCT jsonPayload.* EXCEPT (event_time, logged_time),
# jsonPayload.event_time を FLOAD などから INT64 へ
CAST(jsonPayload.event_time AS INT64) as event_time,
# jsonPayload.logged_time を INT64(epoch) から TIMESTAMP へ
TIMESTAMP_SECONDS(jsonPayload.event_time) as event_time,
) AS jsonPayload
) FROM ...
パーティションを確認
その他 > クエリの設定 > SQL 言語 をレガシーにして以下のクエリ
code:partition.sql
CLI はデフォルトで legacy_sql なので CLI からのほうが楽かも
code:partition.sh
2022/10/18 もう INFORMATION_SCHEMA.PARTITIONS がある スキーマの自動検出
TIMESTAMP パーティション
Day だけでなく Month, Year も選べるようになった 2020/9 頃? CREATE TABLE LIKE
SELECT * FROM dataset.table LIMIT 0 で宛先テーブルを設定すればよい
code:like-create-table-like.sql
CREATE TABLE mydataset.myclusteredtable
PARTITION BY DATE(timestamp)
CLUSTER BY customer_id
AS SELECT * FROM mydataset.myothertable LIMIT 0
後からパーティションつける
直接はつけれないけど DDL でスパッといけないかな
Correlated subqueries that reference other tables are not supported unless they can be de-correlated, such as by transforming them into an efficient JOIN.
ARRAY の中身を JOIN する時とかに見る
SELECT ARRAY(SELECT AS STRUCT * FROM UNNEST(array) JOIN other USING(foo)) とか
LEFT JOIN の LEFT を消せば通ったりする
CREATE SCHEMA DDL
は、データセットを作るクエリ
外部テーブルを DDL で作る
code:external_table.sql
CREATE OR REPLACE EXTERNAL TABLE project.dataset.user (
id STRING OPTIONS(description="..."),
username STRING OPTIONS(description="..."),
is_pro BOOL OPTIONS(description="...")
)
OPTIONS (
format="GOOGLE_SHEETS",
sheet_range="users!A:C",
skip_leading_rows=1
)
エラーハンドリング
@@error.message などエラー出力を参照できる
code:create_schema.sql
BEGIN
CREATE SCHEMA IF NOT EXISTS ... OPTIONS(location="asia-northeast1");
EXCEPTION WHEN ERROR THEN
IF NOT CONTAINS_SUBSTR(@@error.message, "already exists: dataset") AND
NOT CONTAINS_SUBSTR(@@error.message, "too many dataset metadata update operations")
THEN
RAISE USING MESSAGE = @@error.message;
END IF;
END;
Cannot use INFORMATION_SCHEMA with a hidden dataset
_ から始まる名前のデータセットはコンソール上に表示されないなど特別扱いされる
クエリ自体は普通にできる
ただしデータセットの INFORMATION_SCHEMA を参照しようとするとエラーになる
Invalid value: Cannot use INFORMATION_SCHEMA with a hidden dataset: ...
code:hidden_dataset.sql
SELECT ANY_VALUE(table_type)
FROM myproject._hidden_dataset.INFORMATION_SCHEMA.TABLES
WHERE table_name = 'hoge'
変更履歴
FROM APPENDS(TABLE <table_name>, <since>, <until>)
同一テーブルの更新ログなので replace table した場合の履歴は得られない
列・行のアクセス制御
列にポリシータグを付与、ポリシータグへのアクセス権を管理
行アクセスポリシーはクエリで条件を書く、"行" というより WHERE 句で制限する感じ
CREATE ROW ACCESS POLICY ... GRANT TO ("group:hoo@pokutuna.com") FILTER USING (Col="Value")
FILTER USING が真のものに限って結果を得られる
Gemini 叩く
外部接続 & モデル作る
code:create_model.sql
CREATE OR REPLACE MODEL hogehoge.ml.flash001
REMOTE WITH CONNECTION projects/hogehoge/locations/asia-northeast1/connections/vertexai
OPTIONS (ENDPOINT = 'gemini-1.5-flash-001');
code:generate.sql
SELECT
*,
-- 最初の出力取り出す
FROM
ML.GENERATE_TEXT(
MODEL ml.flash001,
(SELECT "こんにちは" AS prompt)
)
bq コマンド
$ bq ls
$ bq mk --location=asia-northeast1 {dataset_name}
$ bq show {dataset_name 他いろいろ}
$ bq show --format=prettyjson {project}:{dataset}.appengine_googleapis_com_stdout スキーマ見る
$ bq load --source_format=NEWLINE_DELIMITED_JSON --autodetect my_dataset.my_table ./path/to/data.json
$ bq --project_id=<PROJECT_ID> load --source_format=NEWLINE_DELIMITED_JSON --autodetect --time_partitioning_field=<FIELD> <DATASET>.<TABLE> gs://...
スキーマの確認
SELECT * FROM {dataset}.INFORMATION_SCHEMA.COLUMN_FIELD_PATHS
WHERE table_name = ... で絞れる
$ bq show --schema --format=prettyjson {dataset}.{table}
データを別のテーブルにコピーする
あるテーブルのデータを別のテーブルに突っ込みたい
カラムが増えたら追加してほしい
コンソールから書き込み先テーブルを指定する場合、同時にスキーマは変更できない
→ CLI でやるのがよい
REQUIRED を NULLABLE にしてもいいならさらに --schema_update_option=ALLOW_FIELD_RELAXATION を追加
確認は --dry_run で
code:copy-to-another-table.sql
bq query \
--project_id=<project> \
--destination_table=<dataset>.<to_table> \
--schema_update_option=ALLOW_FIELD_ADDITION \
--use_legacy_sql=false \
--append_table \
--allow_large_results \
'SELECT * FROM <project>.<dataset>.<from_table>'
FORMAT は DATASTORE_BACKUP です。Cloud Firestore の場合、適切なオプションは Datastore Backup です。Cloud Firestore と Datastore のエクスポート形式は同じです。 $ bq --location=asia-northeast1 mk --dataset pokutuna-playground:import-firebase-backup
$ bq --location=asia-northeast1 load --source_format=DATASTORE_BACKUP pokutuna-playground:import_firebase_backup.table1 gs://campenguin-firestore-export/2020-01-07T10:11:58_4616/all_namespaces/kind_queries/all_namespaces_kind_queries.export_metadata
調べる
Stackdriver Logging の sink は --schema_update_option= なに?
テーブルのクラスタ
リージョンを横断した JOIN とかするとどうなっちゃうの?
できない
GCP のカレントプロジェクトのリージョンの影響がある? なんか実行できないことがある気がする
クエリの設定でリージョン変えれば良い?
書き込み設定
"テーブルに追加する" は bq load --append_table, "テーブルを上書きする" は --replace かな?
https://gyazo.com/2782532f60b08c8093e6ebbfda487a23
取り込み時間分割テーブル
承認済みビュー
一部のデータを社外に共有する際などに、承認済みビューを作ってそれを割り当てる
CREATE TABLE LIKE ... みたいなのやりたい
code:select_schema
SELECT STRING_AGG(CONCAT(column_name, ", ", data_type), "\n")
FROM {dataset}.INFORMATION_SCHEMA.COLUMN_FIELD_PATHS
WHERE table_name = {table}
AND field_path NOT LIKE "%.%"
みたいな感じでスキーマ文字列作れそうなので、これを CREATE TABLE に渡せないか
(bq show & bq mk のほうが簡単とはいえ)
node client で結果は使わずジョブの終了を待つ
code:wait.ts
await job.getQueryResults({maxResults: 0});
Spreadsheet への federated query で Drive credentials エラー
Permission denied while getting Drive credentials.
読み取るサービスアカウントは対象のシートへの閲覧権限はあるがうまくいかない
アクセススコープに https://www.googleapis.com/auth/drive がなかった
これ impersonate しているところで出ていた
普通の ADC で、ユーザーの認証なら https://www.googleapis.com/auth/drive のスコープなくても Spreadsheet にクエリできる気がする、どうだっけ?
--enable-gdrive-access でなくても