Upgrade to Pro — share decks privately, control downloads, hide ads and more …

BigQuery Graph入門

BigQuery Graph入門

2026/04にプレビュー版が出たBigQuery Graph の主にGQLの書き方解説をします。

Avatar for NISHIKAWA, Daisuke

NISHIKAWA, Daisuke

September 30, 2026

More Decks by NISHIKAWA, Daisuke

Other Decks in Technology

Transcript

  1. 自己紹介 ▪ 西川 大亮 ▪ 2019/4 DeNA入社、2020/4 GO転籍(当時Mobility Technologies) ▪

    前職: 中堅SIer ▪ ▪ 14年研究所、3年コンサル 顧客向け技術紹介と火消しが主な仕事 (Projが燃える匂いには敏感) ▪ 趣味 ▪ ▪ ▪ ゴルフ(年数回) 競馬予想(サボり気味) 設計欲を満たせるゲームにハマりやすい ▪ やっていること ▪ タクシーアプリ『GO』の挙動解析 ▪ 課題解決:原因調査→類型化→自動検知 ▪ 性能改善:現状把握→KPI定義→レポート+指針策定 ▪ システムよりも人間系の解析が中心 3
  2. この資料の位置づけ ▪ BigQuery Graphの†入門レベルの使い方紹介 ▪ チュートリアルだと作業手順しかないので、もう少し全体像を知 りたい人向け ▪ GQLの主要な構文の解説が中心 ▪

    create property graph ▪ GQLのmatch文 ▪ グラフ理論の用語と概念は知っている人向け ▪ ノード、エッジ、有向/無向、経路長とか ▪ BigQuery自体と環境周りの説明は省略 ▪ (Web)コンソールからクエリをを書いて実行できる人向け ▪ 予約語は小文字で書きます † BigQuery は、Google LLC の商標または登録商標です 6
  3. 残念なお知らせ: オンデマンドでは使えなくなる模様 2026/08/26 22:14 メールで通知 [2026/08/26 22:14 メール] 2027 年

    4 月 26 日より、BigQuery Graph は BigQuery Enterprise エディションと Enterprise Plus エディションでのみ ご利用いただけるようになります。 ⋯普段使っている環境はオンデマンドでした。 気軽に評価できなくなるのは辛い オンデマンドでもメジャー拡張機能である GRAPH_EXPAND 関数を使えば グラフデータのSQL検索はできるが、使い勝手が素のSQLと同じなので率直 に言って価値がない 8
  4. 使用データ BigQuery Graphのチュートリアルから(fraud_demo)を使います • ノードは顧客、電話番号、メアド、住所 customers emails phones addresses ノード

    ▪ customers ▪ phones ▪ addresses ▪ emails エッジ ▪ customer_phones ▪ customer_addresses ▪ customer_emails • これだけ見ると顧客レコードの属性カラムっぽい が、例えば電話番号が複数ありうる、電話番号が 他の顧客と共有になっている、といったケースが 考えられるので、それぞれ多対多の関係になる。 • ECで同じ住所(配送先)を共有している別の顧客が いる場合、マーケ視点だと家族や事業者判定、不 正対策目線だと取り込み詐欺判定に使えそう • このcustomer -> address -> customer -> (他属 性…) と行った関係はGQLを使うと簡便に探すこ とができる 10
  5. SQL/GQLロゼッタストーン① お題1:住所が一致する別アカウントを探す select -- SQL l.account_id as l_account_id, r.account_id as

    r_account_id, l.address as address, from fraud_demo.customer_addresses as l inner join fraud_demo.customer_addresses as r on l.address = r.address and l.account_id < r.account_id select -- GQL *, from graph_table ( fraud_demo.FraudDemo match (l:Customer)-(a:Address)-(r:Customer) where l.account_id < r.account_id return l.account_id as l_account_id, r.account_id as r_account_id, a.address as address ) • このぐらいだとSQL、GQLどちらで もほとんど差がない • account_id だけ見ているのでSQL はエッジだけで探せて少し有利 • GQLをSQLに組み込むにはfrom graph_table (... の後ろにGQLを書 く 11
  6. SQL/GQLロゼッタストーン② お題2:住所とメアドが一致する別アカウントを探す with -- SQL identical_addr as ( select l.account_id

    as l_account_id, r.account_id as r_account_id, l.address as address, from fraud_demo.customer_addresses as l inner join fraud_demo.customer_addresses as r on l.address = r.address and l.account_id < r.account_id ) , identical_email as ( -- 追加 select l.account_id as l_account_id, r.account_id as r_account_id, l.email as email, from fraud_demo.customer_emails as l inner join fraud_demo.customer_emails` as r on l.email = r.email and l.account_id < r.account_id ) select *, from identical_addr inner join identical_email using(l_account_id, r_account_id) select -- GQL *, from graph_table ( fraud_demo.FraudDemo match (l:Customer)-(a:Address)-(r:Customer), (l:Customer)-(e:Email)-(r:Customer) -- 追加 where l.account_id < r.account_id return l.account_id as l_account_id, r.account_id as r_account_id, a.address as address, e.email as email -- 追加 ) • SQLの記述量が多いが、まだ負担ではなさそう。 GQLはmatch句のカンマでandを意味するのでと ても簡潔に書ける 12
  7. SQL/GQLロゼッタストーン③−2 お題3:住所が一致するアカウントネットワークを探す(5ホップ先まで) with recursive address_network as ( -- SQL select

    account_id as start_account_id, account_id as current_account_id, '' as shared_address, 0 as hop_count, array[account_id] as visited_accounts -- 循環ループ防止用 from fraud_demo.customer_addresses group by account_id -- 重複排除 union all select a.start_account_id, nn.account_id as current_account_id, cn.address as shared_address, a.hop_count + 1 as hop_count, array_concat(a.visited_accounts, [nn.account_id]) as visited_accounts from address_network as a -- ① 現在のアカウントが持っている住所 inner join fraud_demo.customer_addresses` as cn on a.current_account_id = cn.account_id -- ② その住所を共有している「別の」アカウント inner join fraud_demo.customer_addresses as nn on cn.address = nn.address where -- 訪問したアカウントを除外 not (nn.account_id in unnest(a.visited_accounts)) -- パフォーマンスとメモリ保護のため、最大ホップ数を制限 and a.hop_count < 5 ) select distinct start_account_id, current_account_id AS connected_account_id, shared_address, hop_count, visited_accounts from address_network where hop_count > 0 order by hop_count, connected_account_id; • with recursiveを使えばSQLでもグラフサーチはでき る。書き方にかなりクセがあるけど。 • また、大量に重複が返される。レコード数だと 49638だけど、アカウント−住所−アカウントの本 数は5170本 14
  8. SQL/GQLロゼッタストーン③−3 お題3:住所が一致するアカウントネットワークを探す(5ホップ先まで) select -- GQL • * from graph_table (

    • fraud_demo.FraudDemo match p = acyclic((l:Customer)-(a:Address)-(r:Customer)){1,5} return to_ json(p) as pathes ) 流石にこういった処理はGQLが圧勝 とはいえ取った後のデータはjson丸投げなので後 処理はそれなりに面倒 15
  9. SQL/GQLロゼッタストーン④ お題4:住所,メアド,電話のいずれかが一致するアカウントネットワー クを探す(3ホップ先まで) select -- GQL * from graph_table (

    fraud_demo.FraudDemo match p = acyclic((l:Customer)-(a:Address|Email|Phone)-(r:Customer)){1,3} return to_ json(p) as pathes ) • もうSQLで書こうとは思わない。LLMに 投げたら凄いクエリが返ってきそう • 5ホップだとBQのCPU喰い潰して実行さ れなかった 16
  10. テーブルからグラフを作る create or replace property graph fraud_demo.FraudDemo node tables (

    fraud_demo.customers -- BQのテーブル key(account_id) label Customer properties(account_id, name), fraud_demo.addresses -- BQのテーブル key(address) label Address properties(address, address_type) ) edge tables ( fraud_demo.customer_addresses -- BQのテーブル key(account_id, address) source key(account_id) references customers(account_id) destination key(address) references addresses(address) label LinkedToAddress properties(account_id, address, last_updated_ts) ); • 左は住所とユーザだけのグラフ定義 (チュートリアルからの抜粋) • ノードもエッジもBigQueryのテーブルから 作る。先に準備が必要 • keyは主キー。未確認だが事前に重複は 取った方が良さそう。 • GQL側はlabelで参照するので無いと強制的 に作られる。 • エッジは有向のみ。無向にしたければ逆方 向のエッジを定義する ▪ 検索条件にエッジ絞り込みが必要な ケースは両方指定する • 出来上がったfraud_demo.FraudDemoを GQLで探索する • 全体的にとてもシンプル 19
  11. GQL (Graph Query Language)の構造 ▪ 検索対象のグラフを指定して ▪ 検索条件を指定 - 検索結果に対

    するフィルターを指定 ▪ 結果をテーブル形式に変換 ▪ NEXTの後ろにMATCHを続ける と1つ前の結果から処理できる 参照: グラフクエリの概要 色々できるが今回はMATCHの解説のみ (MATCHだけでも十分ややこしい) 20
  12. MATCH 文1(絞り込み条件) BNF(Backus-Naur Form)全体: GQL patterns match (l:Customer)-(a:Address)-(r:Customer) ノード エッジ

    略さず書くと match (l:Customer)-[lta1:LinkedToAddress]-(a:Address)-[lta2:LinkedToAddress]-(r:Customer) エッジ リンクした3ノードを検索 ノード、エッジ無条件 match ()-()-() 中のノードがAddress match ()-(:Address)-() 中のノードがAddressかEmail match ()-(:Address|Email)-() 端のノードのIDが123 match ({id:123})-()-() ノードの重複禁止 match acyclic ()-()-() 絞り込み条件はエッジも同じ記法 21
  13. MATCH 文2(数量記述子) 表記 意味 ()-()-() リンクした3ノード (()-()-()){2} リンクした5ノード(並置のケース) ()-()-()()-()-() ⇒

    ()-()-()-()-() ()(-()-()){2} リンクした5ノード (()-()-()){2,4} リンクした5か7か9ノード ()(-()-)(){2} リンクした5ノード(正規化後並置のケース) ()-()--()-() ⇒ ()()-()-()()-()-()() ⇒ ()-()-()-()-() ▪ パスの一部をカッコ()で括って{N,M}のよう に書くと、N~M回繰り返すという意味にな る。 ▪ *とか+ とか (5,) のような回数の上限がない 指定はBigQuery Graphでは未対応 ▪ この仕様がパスパターンの正規化や並置と必 ず組み合わされるためとてもややこしい 22
  14. MATCH 文3(正則化と並置) ▪ 正規化 ▪ 端がエッジの場合はノードがその外側に付け加えられる ▪ (-){1,5} は (()-()){1,5}

    と正規化される ▪ 並置ノードの統合 ▪ ▪ ▪ ノードがエッジを挟まずに並置(Juxtaposition)されると、それらのノードは同 一ノードとして処理(SAME Oparation)される 1つに集約されたと考えると理解しやすい ()()()-()()-() ⇒ ()-()-() この2つは 数量記述子 が入ったパスパターンを読み解く際に必須の知識 23
  15. RETURNの返し方 -- おすすめ match p = acyclic((:Customer)-(:Address)-(:Customer)){1,5} return to_ json(p)

    as pathes -- エラー match acyclic(l:Customer)-(a:Address)-(r:Customer) return l, a, r -- これはOK match acyclic(l:Customer)-(a:Address)-(r:Customer) return l.account_id -- エラー match acyclic((l:Customer)-(a:Address)-(r:Customer)){1,5} return l.account_id RETURN TO_JSON(p) AS paths で検索できたpath全 体を返して、外側のSQLでjsonを処理する方法がおす すめ 理由: ▪ そもそもグラフエレメントを戻り値にできない ▪ 数量子 {} によって作られたグループ変数 は、「水 平集計関数例: ARRAY_AGG や SUM など)の引数 として渡すことしかできない この辺りの仕様に悩むぐらいならSQLで処理したほう が良い 24
  16. 未成熟な部分 まだかなりバギー ▪ match (a:Customer) (-(e)){2} - (b:Customer) と書くとクエリが終わらなく なることがあったし、2分程度で終わることもあった。

    ▪ ▪ Geminiの修正提案は最初はデタラメで、修正コードのエラーを取り除いても2分程度かかってい るので(これ以外は数秒で終わる)、エンジンの何らかのバグだろう どうも無向エッジ探索でしくじっているようだが、Graph定義からの枝刈りをやっていないだけ ▪ エラーになった時にエラー行を出さない/場所が違うケースが多い ▪ BigQuery Graph は GQL 準拠だそうだが、結構独自の制約があるので、LLM に聞く時は「BigQuery Graph の GQLで」と限定すること ▪ やらないと結構ハルシネーションを喰らう 25