14
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

BigQueryでグラフ分析ができる!新しい「Graph」機能をSNSデータの例で試してみた

14
Posted at

はじめに

こんにちは!
KDDIアイレットの取り組みとして6月22日〜7月3日の期間で開催中の「Google Cloud Next '26 / Google I/O やってみた系ブログリレー」、本日は3日目の投稿です。

今回は、「BigQueryの「Graph機能」 を対象に、実際に検証してみた様子をお届けします!

前回の記事はこちらです

BigQuery に、データの「つながり」を分析する機能が加わりました。これまでグラフ分析といえば専用のデータベースを別に用意するのが一般的でしたが、今回からはいつもの BigQuery のテーブルのまま、「誰が誰とつながっているか」をたどれるようになっています。

この記事では、小さなSNS(フォロー関係)を題材に、実際に手を動かして試した記録をまとめます。

⚠️ 本機能は現時点(2026年6月24日時点)では Preview(お試し版)です。
まだ正式版ではないため、提供状況や仕様が今後変わる可能性があります。実際に利用する際は、必ず最新の公式ドキュメントで提供状況・制約を確認したうえでお使いください。


そもそも「グラフ分析」とは

ここで言う「グラフ」は、棒グラフや折れ線グラフのことではありません。点と点を線でつないだ「関係性の図」を指します。点(ノード)は人やモノを表し、今回でいえば「ユーザー」です。線(エッジ)は点どうしの関係を表し、今回は「フォローしている」という関係にあたります。

たとえば「自分がフォローしている人が、さらにフォローしている人(=おすすめ候補)」「お互いにフォローし合っている人」「AさんからBさんまで何人を経由してたどり着けるか」といった、つながりをたどっていく分析は、ふつうの SQL だと書くのが難しいです。テーブルの結合を何度もくり返すことになり、たどる距離が伸びるほどクエリは長く読みにくくなっていくからです。

こうした分析を得意とするのがグラフ分析で、これまでは Neo4j のような専用ツールを別途用意するのが定番でした。それが今回、BigQuery だけでできるようになった、というのが本題です。


今回の発表で、何が変わったのか

ひとことで言えば、「つながりをたどる分析」が BigQuery の中だけで完結するようになりました。これまでとの違いを整理すると、次のようになります。

ChatGPT Image 2026年6月11日 13_36_51 (1).png

ChatGPT Image 2026年6月11日 13_36_54 (2).png

これまで 今回から
つながりの分析 SQL でテーブルの結合をくり返す。2つ先・3つ先とたどるほど書くのが大変で、読むのも一苦労 (点)-[線]->(点)矢印そのままの書き方でたどれる
専用ツール 本格的にやるなら Neo4j などのグラフ専用データベースを別に用意。データのコピーや運用の手間が増える いまの BigQuery のテーブルのまま。新しいツールもデータの引っ越しも不要
集計との合わせ技 グラフ専用ツールと BigQuery を行き来する必要がある 「つながり探し」と「ふつうの集計」を1つのクエリで混ぜられる

つまり、これまでの BigQuery は「集計は得意だけれど、つながりをたどるのは苦手」という位置づけでした。今回の発表で、その苦手だった部分を BigQuery 自身がこなせるようになった、というわけです。


どんな場面で使えるのか

「使えるのはわかったけど、結局どこで役立つの?」というのが、いちばん気になるところだと思います。コツは、「○○から○○へ、と何かをたどっていきたい」場面を思い浮かべることです。具体的には、次のような用途で力を発揮します。

お金の流れを追う(不正検知)
「この口座から別の口座へ、さらに次の口座へ…」とお金が次々に移されていないかを追いかける、という使い方です。1件ずつ見ると普通の取引でも、送金を順々につないでいくと「ぐるっと一周して戻ってくる」など、あやしい流れが浮かび上がります。マネーロンダリング(資金洗浄)の検出などで使われます。

モノの流れを追う(サプライチェーン分析)
「原材料 → 部品 → 製品 → 配送」と、モノが作られて届くまでの流れをたどる使い方です。「ある部品が手に入らなくなったら、どの製品まで作れなくなるか」といった、影響範囲の調査に向いています。

ルートを探す(経路探索)
「この地点から、道をいくつか経由して、あの地点まで」という最短ルートや最安ルートを探す使い方です。乗り換え案内や配送ルートの最適化がイメージしやすいと思います。この記事の後半で出てくる「最短ルート探し」がまさにこれです。

人やモノのつながりを見渡す(顧客プロファイル分析)
1人の顧客にまつわる情報(購入履歴・SNS・問い合わせなど)はバラバラに散らばりがちですが、それらを「関係」でつなぐと、その人の全体像が一枚の図として見えてきます。「この商品を買った人が、よく一緒に買う商品」をたどるおすすめ機能も、この仲間です。

どれも共通しているのは、「点と点を線でつなぎ、その線を何度もたどっていく」という点です。逆に、「合計いくら?」「何件?」のような集計だけならグラフは要りません。たどる作業が出てきたらグラフの出番となります。

この記事では、その中でもいちばん身近なSNSのフォロー関係(誰が誰をフォローしているか)を例に進めていきます。


今回のゴール

小さなSNSを作り、ユーザーとフォロー関係のデータを用意したうえで、それを「グラフ(つながりの図)」として登録し、最後に「おすすめユーザー」や「お互いにフォローしている人」などを探してみます。登場人物は6人で、矢印は「フォローしている向き」を表しています。

■ フォロー関係(矢印 = フォローしている向き)

Alice  → Bob, Carol
Bob    → Carol, Dave
Carol  → Alice, Dave
Dave   → Eve
Eve    → Dave, Frank
Frank  → Alice

※ Alice ⇄ Carol、Dave ⇄ Eve は相互フォロー

うまく機能していれば、こうなる

「BigQuery にこう質問したら、こう答えてくれる」がゴールです。最終的に下の表の答えがそのまま返ってくれば成功で、あとの Step 4 で1つずつ実際に確認していきます。

BigQuery にする質問 返ってくる答え
Alice におすすめのユーザーは?(フォローの2つ先) Dave
お互いにフォローし合っているペアは? Alice ⇄ CarolDave ⇄ Eve
Alice から Frank まで最短で何人たどる? 4ステップ(Alice → Bob → Dave → Eve → Frank)
いちばんフォロワーが多いのは? Dave(3人)

Step 1. 入れ物(データセット)を作る

まずはデータを入れる箱を用意します。

bq mk --location=asia-northeast1 --dataset \
  --description "BigQuery Graph 検証用" \
  your-project-id:graph_demo

実行すると、次のように作成されます。

Dataset 'your-project-id:graph_demo' successfully created.

なお、以降の例ではプロジェクトIDを your-project-id と表記しています。ご自身の環境名に置きかえてください。


Step 2. データを2つのテーブルに入れる

グラフは、「点のテーブル」と「線のテーブル」の2つで表します。今回は点としてユーザー一覧の users、線として「誰が誰をフォローしているか」を記録する follows を用意します。

-- 点:ユーザー
CREATE OR REPLACE TABLE graph_demo.users (
  id      STRING NOT NULL,
  name    STRING,
  country STRING
);

INSERT INTO graph_demo.users (id, name, country) VALUES
  ('u1','Alice','JP'),
  ('u2','Bob','JP'),
  ('u3','Carol','US'),
  ('u4','Dave','US'),
  ('u5','Eve','UK'),
  ('u6','Frank','JP');

-- 線:フォロー関係(from が to をフォロー)
CREATE OR REPLACE TABLE graph_demo.follows (
  from_id STRING NOT NULL,
  to_id   STRING NOT NULL,
  since   DATE
);

INSERT INTO graph_demo.follows (from_id, to_id, since) VALUES
  ('u1','u2', DATE '2024-01-10'),
  ('u1','u3', DATE '2024-02-15'),
  ('u3','u1', DATE '2024-02-20'),  -- Alice と Carol はお互いフォロー
  ('u2','u3', DATE '2024-03-01'),
  ('u2','u4', DATE '2024-03-05'),
  ('u3','u4', DATE '2024-03-10'),
  ('u4','u5', DATE '2024-04-01'),
  ('u5','u4', DATE '2024-04-03'),  -- Dave と Eve はお互いフォロー
  ('u5','u6', DATE '2024-05-01'),
  ('u6','u1', DATE '2024-05-20');

ここでのポイントは、特別なデータの入れ方は何もしていないということです。中身はごく普通のテーブルで、次のステップで「これを点と線として見てね」と教えてあげるだけです。


Step 3. 「これはグラフです」と登録する

CREATE PROPERTY GRAPH を使って、さきほどの2つのテーブルをグラフとして登録します。

CREATE OR REPLACE PROPERTY GRAPH graph_demo.SocialGraph
  NODE TABLES (                        -- 点になるテーブル
    graph_demo.users
      KEY (id)
      LABEL User PROPERTIES (id, name, country)
  )
  EDGE TABLES (                        -- 線になるテーブル
    graph_demo.follows
      KEY (from_id, to_id)
      SOURCE KEY (from_id) REFERENCES users (id)   -- 線の出発点は users.id
      DESTINATION KEY (to_id) REFERENCES users (id) -- 線の到着点は users.id
      LABEL Follows PROPERTIES (since)
  );

NODE TABLES で「users が点だよ」と伝え、EDGE TABLES で「follows が線で、from_id から to_id に向かう線だよ」と伝えております。

実際にデータをコピーしているわけではなく、あくまで「こう見なしてね」という設定を加えているだけなので、既存のデータはそのまま使えます。


Step 4. つながりを探してみる

使い方はかんたんで、FROM のところに GRAPH_TABLE(...) と書き、その中に (点)-[線]->(点) という形で探したいつながりを書きます。返ってくるのは普通の表なので、外側では ORDER BY などをいつもどおり使えます。

その1:誰が誰をフォローしているか

SELECT follower, followee
FROM GRAPH_TABLE(
  graph_demo.SocialGraph
  MATCH (a:User)-[:Follows]->(b:User)   -- a が b をフォロー
  RETURN a.name AS follower, b.name AS followee
)
ORDER BY follower, followee;
+----------+----------+
| follower | followee |
+----------+----------+
| Alice    | Bob      |
| Alice    | Carol    |
| Bob      | Carol    |
| Bob      | Dave     |
| Carol    | Alice    |
| Carol    | Dave     |
| Dave     | Eve      |
| Eve      | Dave     |
| Eve      | Frank    |
| Frank    | Alice    |
+----------+----------+

(a)-[:Follows]->(b) が、そのまま「a が b をフォロー」を表します。矢印そのままで読めるのでとてもわかりやすいです。

その2:おすすめユーザーを出す

次に、「Aliceがフォローしている人が、さらにフォローしている人」を、おすすめ候補として出してみます。ただし、すでにフォローしている人と自分自身は候補から除くことにします。

WITH two_hop AS (
  -- フォローの2つ先をたどる
  SELECT DISTINCT candidate_id, candidate
  FROM GRAPH_TABLE(
    graph_demo.SocialGraph
    MATCH (me:User {name:'Alice'})-[:Follows]->(:User)-[:Follows]->(rec:User)
    WHERE rec.name <> 'Alice'
    RETURN rec.id AS candidate_id, rec.name AS candidate
  )
),
already_following AS (
  -- Aliceがすでにフォローしている人
  SELECT f_id
  FROM GRAPH_TABLE(
    graph_demo.SocialGraph
    MATCH (me:User {name:'Alice'})-[:Follows]->(f:User)
    RETURN f.id AS f_id
  )
)
SELECT candidate AS recommended
FROM two_hop
WHERE candidate_id NOT IN (SELECT f_id FROM already_following)
ORDER BY recommended;
+-------------+
| recommended |
+-------------+
| Dave        |
+-------------+

Alice がフォローしているのは Bob と Carol で、その2人がフォローしているのは Carol・Dave・Alice です。

ここから「自分(Alice)」と「すでにフォロー中の Bob・Carol」を除くと、残るのは Dave だけとなります。

きちんと正しい結果になっています。

なお、このクエリでは「つながりをたどる部分」を GRAPH_TABLE に任せ、「除外する計算」を普通の SQL で行うという形で役割分担をしています。なぜこうしたのかは、後半の「つまずいたところ」で触れます。

その3:お互いにフォローし合っているペア

「行って戻ってくる」つながりを書くと、相互フォローを表現できます。

SELECT user_a, user_b
FROM GRAPH_TABLE(
  graph_demo.SocialGraph
  MATCH (a:User)-[:Follows]->(b:User)-[:Follows]->(a)  -- a→b→a と戻ってくる
  WHERE a.id < b.id                                     -- 同じペアの重複を消す
  RETURN a.name AS user_a, b.name AS user_b
)
ORDER BY user_a;
+--------+--------+
| user_a | user_b |
+--------+--------+
| Alice  | Carol  |
| Dave   | Eve    |
+--------+--------+

お互いにフォローし合っているのは、Alice ⇄ Carol と Dave ⇄ Eve の2組だとわかります。

その4:AさんからBさんまで何人で届くか(最短ルート)

->{1,10} は「1〜10人ぶん、つながりをたどる」という指定で、ANY SHORTEST を付けると一番短いルートだけを返してくれます。

SELECT hops, route
FROM GRAPH_TABLE(
  graph_demo.SocialGraph
  MATCH p = ANY SHORTEST (a:User {name:'Alice'})-[:Follows]->{1,10}(b:User {name:'Frank'})
  RETURN path_length(p) AS hops,
         ARRAY(SELECT n.name FROM UNNEST(nodes(p)) AS n) AS route
)
+------+--------------------------------------+
| hops |                route                 |
+------+--------------------------------------+
|    4 | ["Alice","Bob","Dave","Eve","Frank"] |
+------+--------------------------------------+

Alice から Frank へは、最短で4ステップ(Alice→Bob→Dave→Eve→Frank)でたどり着けることがわかります。ふつうの SQL だと大変な「ルート探し」が、1つのクエリで書けてしまいました。

その5:人気者ランキング(フォロワーの多い順)

COUNTGROUP BY も、いつもの SQL と同じように使えます。

SELECT name, follower_count
FROM GRAPH_TABLE(
  graph_demo.SocialGraph
  MATCH (follower:User)-[:Follows]->(u:User)
  RETURN u.name AS name, COUNT(follower.id) AS follower_count
  GROUP BY name
)
ORDER BY follower_count DESC, name;
+-------+----------------+
| name  | follower_count |
+-------+----------------+
| Dave  |              3 |
| Alice |              2 |
| Carol |              2 |
| Bob   |              1 |
| Eve   |              1 |
| Frank |              1 |
+-------+----------------+

つまずいたところ

実際に試して引っかかった点を残しておきます。

【その2:おすすめユーザーを出す】の部分で「フォローの2つ先」を1本のクエリで書こうとしたら、2つのエラーに当たりました。

  • 一度使った名前(me など)を、別の場所でもう一度使うとエラー
  • つながりをたどる条件の中では EXISTS が使えないというエラー

ここで分かったのは、全部をグラフの書き方で済ませようとしないことです。つながりをたどる部分はグラフに任せ、除外や集計はふつうの SQLにやらせると素直に書けます。

GRAPH_TABLE の結果はふつうの表なので、こうした使い分けが簡単にできます。「その2」をあえて2段構えにしたのは、このためです。


感想

実際に触ってみて最も魅力を感じたのは、いつもの BigQuery のまま始められることです。
グラフDBを立てる必要も、データを引っ越しさせる必要もありません。

しかも、SQL だと頭を抱える「友だちの友だち」「最短ルート」が、(点)-[線]->(点) と矢印そのままで書けます。つながり探しとふつうの集計を1つのクエリに混ぜられることに利便性を感じました。

「グラフDBを立てるほどじゃないけど、つながりを見たい」——そんな場面にはドンピシャの機能だと感じました。


参考リンク

14
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?