NL2SQL の限界を突破する!「データ・ナレッジベース」アプローチによるデータ分析エージェント
はじめに(NL2SQL の課題点)
最近、自然言語による問い合わせから動的に SQL を生成して、データを取得・分析する「NL2SQL」のアプローチが注目を集めています。AI エージェントが自発的に SQL を考えて実行することで、SQL の知識がないビジネスユーザーでも複数のテーブルにまたがったデータを自在に分析できるというものです。
一見すると、業務システムのバックエンドにあるデータベースを AI エージェントに公開すれば、その後のことはエージェントが考えてくれるので、従来の BI システムのように時間をかけてデータマートを構築する必要がなくなりそうです。しかしながら、NL2SQL の手法だけで、実用レベルの分析システムを構築するのはそれほど簡単ではありません。
人間のデータアナリストが SQL でデータ分析する際は、それぞれのテーブルにどのようなデータが保存されていて、どのカラムをキーにして結合すればビジネス的に意味のあるデータが得られるのかという事前の知識が必要です。NL2SQL を実施する際は、これに相当する情報を AI エージェントに与える必要があります。テーブルのカラム名やスキーマ情報からある程度は推測できますが、推測結果が正しいという保証がなければ、AI エージェントの分析結果を信用することはできません。

SQL による分析にはテーブルの構造に関する知識が必要
このような課題への対処方法として、次のようなアプローチが提案されることがあります。
-
(1) テーブルのスキーマを可能な限り詳しく記述して、AI エージェントが正しい SQL を構成できるようにする。
-
(2) AI エージェントが実行可能な SQL のテンプレートを事前に用意しておき、AI エージェントはテンプレートに与えるパラメーターのみを指定する。
(1) の場合、スキーマ情報をどこまで与えれば十分なのかが不明確で、正しい SQL を確実に実行させるという意味では限界があります。(2) の場合、SQL のテンプレートを事前に考える必要がある点、また、AI エージェントが実行できる SQL の範囲が大きく制限されるという点が課題になります。
(1) と (2) の中間にあたる、うまい方法はないものでしょうか?
——— 実は、うまい方法があります。
データ・ナレッジベースによるアプローチ
一般に、業務システムのバックエンドデータベースは、データの整合性を保持するのに適した正規化テーブルで構成されます。BI ツールでは、通常、これら複数のテーブルを結合して、「売り上げ関連の情報をまとめたテーブル」「顧客関連の情報をまとめたテーブル」など、ビジネス視点で意味のあるデータを1つのテーブルにまとめたものを用意します。このようなデータ分析用のデータベースを「データマート」と呼びます。データマートが事前に用意されていると、複数のテーブルを結合することなく、比較的シンプルな SQL でビジネス視点でのデータ分析ができるようになります。
「もしかして、データマートに NL2SQL を適用する?」と思ったあなた ——— おしいです。
事前にデータマートが用意されている環境であれば、そのアプローチも考えられますが、AI エージェントに与えるための「データマートに含まれるテーブルを説明した情報」を用意する必要がある点では、状況はそれほど変わりません。
ここでは、データマートを実際に作るのではなく、データマートの個々のテーブルに対応した、次のような情報をマークダウンテキストの「データ・ナレッジベース」として用意して、これを利用します。
- テーブルに含まれる情報の説明(どのようなビジネス視点の情報が含まれており、どのような分析に利用できるのか)
- テーブルデータを取得する「ベース SQL」(正確には、テーブルに相当するデータを全件取得する SQL 文)
- テーブルに含まれるデータの特徴
AI エージェントは、この「ベース SQL」によって、大元のデータベースにある複数のテーブルを結合して、データマートのテーブルに相当するデータを取得する SQL が理解できます。このベースとなる SQL 文に WHERE 句や GROUP BY といったシンプルな処理を加えることで、分析に必要な情報が取得できます。複数のテーブルを結合するといった複雑な処理はベース SQL に含まれているので、意図しない情報を誤って取得する SQL を実行するリスクを大きく減らせます。
たとえば、BigQuery のオープンデータ「theLook eCommerce」には、架空の EC サイトの取引データが保存されており、「顧客マスタ」「受注明細」「受注履歴」「商品マスタ」などのテーブルがあります。このデータベースに対して、次のような「データ・ナレッジベース」を作成します。
すこし長くなりますが、マークダウンテキスト全体を示すと次になります。
# TheLook eCommerce データナレッジベース
## セクション1:売上・商品分析のミクロデータ基盤
### データの説明
このベースデータは、個々の注文商品(`order_items`)を基点とし、それに関連する商品マスタ(`products`)と注文情報(`orders`)を結合した、詳細なトランザクションデータです。各行が「どの注文で」「どの商品が」「いくらで」売れたかを表しており、利益計算(`sale_price` - `product_cost`)の基礎情報も含まれています。
後続のエージェントは、このデータを活用して以下のような多角的な分析を実行できます。
- **商品レベルの分析**:
- 商品別・SKU別の売上、利益、利益率の算出
- ABC分析による売れ筋・死に筋商品の特定
- **カテゴリ・ブランド分析**:
- 商品カテゴリ別・ブランド別の売上・利益ランキング
- 特定カテゴリ・ブランドの売上トレンド(月次、四半期など)
- **時間軸分析**:
- 全期間を通した売上・利益の推移
- キャンセル・返品の発生トレンド分析
- **価格分析**:
- `product_retail_price`(定価)と`sale_price`(売価)の差分分析
- 利益率((sale_price - product_cost) / sale_price)の分布調査
### CSV データを取得するベース SQL
```sql
SELECT
t1.id as order_item_id,
t1.order_id,
t1.user_id,
t1.product_id,
t1.inventory_item_id,
t1.status as order_item_status,
t1.created_at as order_item_created_at,
t1.shipped_at as order_item_shipped_at,
t1.delivered_at as order_item_delivered_at,
t1.returned_at as order_item_returned_at,
t1.sale_price,
t2.created_at as order_created_at,
t2.num_of_item as num_items_in_order,
t3.cost as product_cost,
t3.category as product_category,
t3.name as product_name,
t3.brand as product_brand,
t3.retail_price as product_retail_price,
t3.department as product_department
FROM
`bigquery-public-data.thelook_ecommerce.order_items` AS t1 TABLESAMPLE SYSTEM (0.55 PERCENT)
LEFT JOIN
`bigquery-public-data.thelook_ecommerce.orders` AS t2
ON t1.order_id = t2.order_id
LEFT JOIN
`bigquery-public-data.thelook_ecommerce.products` AS t3
ON t1.product_id = t3.id
```
### CSV データの全レコード件数(フィルタリング前)
180,858
### CSV データのサンプル
| order_item_id | order_id | user_id | product_id | inventory_item_id | order_item_status | order_item_created_at | order_item_shipped_at | order_item_delivered_at | order_item_returned_at | sale_price | order_created_at | num_items_in_order | product_cost | product_category | product_name | product_brand | product_retail_price | product_department |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 55 | 38 | 33 | 14235 | 145 | Cancelled | 2023-06-29 10:37:03+00:00 | | | | 0.02 | 2023-06-25 13:26:42+00:00 | 2 | 0.0083 | Accessories | Indestructable Aluminum Aluma Wallet - RED | marshal | 0.02 | Women |
| 35745 | 24690 | 19706 | 14235 | 96397 | Complete | 2023-12-08 22:36:11+00:00 | 2023-12-10 09:49:54+00:00 | 2023-12-11 14:01:54+00:00 | | 0.02 | 2023-12-09 00:44:54+00:00 | 1 | 0.0083 | Accessories | Indestructable Aluminum Aluma Wallet - RED | marshal | 0.02 | Women |
| 140238 | 96736 | 77392 | 14235 | 378825 | Cancelled | 2025-02-27 07:36:27+00:00 | | | | 0.02 | 2025-02-26 08:05:49+00:00 | 2 | 0.0083 | Accessories | Indestructable Aluminum Aluma Wallet - RED | marshal | 0.02 | Women |
| 153699 | 106101 | 84854 | 14235 | 415292 | Processing | 2025-08-07 10:48:55+00:00 | | | | 0.02 | 2025-08-07 12:56:13+00:00 | 1 | 0.0083 | Accessories | Indestructable Aluminum Aluma Wallet - RED | marshal | 0.02 | Women |
| 163404 | 112802 | 90339 | 14235 | 441436 | Shipped | 2026-02-01 18:55:29+00:00 | 2026-02-04 04:27:05+00:00 | | | 0.02 | 2026-02-01 21:30:05+00:00 | 1 | 0.0083 | Accessories | Indestructable Aluminum Aluma Wallet - RED | marshal | 0.02 | Women |
| 13006 | 8987 | 7226 | 14159 | 35078 | Cancelled | 2023-09-19 09:19:41+00:00 | | | | 0.49 | 2023-09-18 10:20:16+00:00 | 2 | 0.17738 | Accessories | Set of 2 - Replacement Insert For Checkbook Wallets Card Or Picture Insert | Made in USA | 0.49 | Women |
| 23987 | 16585 | 13268 | 14159 | 64702 | Cancelled | 2022-02-20 00:13:27+00:00 | | | | 0.49 | 2022-02-20 00:43:03+00:00 | 1 | 0.17738 | Accessories | Set of 2 - Replacement Insert For Checkbook Wallets Card Or Picture Insert | Made in USA | 0.49 | Women |
| 3520 | 2437 | 1918 | 14159 | 9461 | Processing | 2025-09-04 00:08:23+00:00 | | | | 0.49 | 2025-08-31 03:47:08+00:00 | 2 | 0.17738 | Accessories | Set of 2 - Replacement Insert For Checkbook Wallets Card Or Picture Insert | Made in USA | 0.49 | Women |
| 94436 | 65175 | 52281 | 14159 | 254844 | Returned | 2025-11-26 13:54:54+00:00 | 2025-11-24 09:28:39+00:00 | 2025-11-28 06:15:39+00:00 | 2025-11-29 19:12:39+00:00 | 0.49 | 2025-11-22 15:48:39+00:00 | 4 | 0.17738 | Accessories | Set of 2 - Replacement Insert For Checkbook Wallets Card Or Picture Insert | Made in USA | 0.49 | Women |
| 128697 | 88741 | 70833 | 14159 | 347595 | Processing | 2023-01-19 13:18:42+00:00 | | | | 0.49 | 2023-01-19 16:54:28+00:00 | 1 | 0.17738 | Accessories | Set of 2 - Replacement Insert For Checkbook Wallets Card Or Picture Insert | Made in USA | 0.49 | Women |
| 48997 | 33888 | 27084 | 14159 | 132073 | Shipped | 2024-08-08 17:00:48+00:00 | 2024-08-11 07:34:56+00:00 | | | 0.49 | 2024-08-08 19:33:56+00:00 | 4 | 0.17738 | Accessories | Set of 2 - Replacement Insert For Checkbook Wallets Card Or Picture Insert | Made in USA | 0.49 | Women |
| 109657 | 75645 | 60526 | 14159 | 296042 | Shipped | 2020-03-13 18:39:43+00:00 | 2020-03-16 06:11:54+00:00 | | | 0.49 | 2020-03-13 18:55:54+00:00 | 1 | 0.17738 | Accessories | Set of 2 - Replacement Insert For Checkbook Wallets Card Or Picture Insert | Made in USA | 0.49 | Women |
| 161342 | 111424 | 89216 | 14159 | 435862 | Shipped | 2021-10-26 10:13:06+00:00 | 2021-10-27 13:58:57+00:00 | | | 0.49 | 2021-10-26 11:17:57+00:00 | 1 | 0.17738 | Accessories | Set of 2 - Replacement Insert For Checkbook Wallets Card Or Picture Insert | Made in USA | 0.49 | Women |
| 55714 | 38540 | 30872 | 28700 | 150208 | Cancelled | 2026-01-18 20:10:17+00:00 | | | | 1.5 | 2026-01-18 21:24:41+00:00 | 1 | 0.606 | Accessories | Wayfarer Style Sunglasses Dark Lens Black Frame | Wayfayrer | 1.5 | Men |
| 13557 | 9371 | 7537 | 14202 | 36591 | Complete | 2023-05-12 13:44:28+00:00 | 2023-05-14 21:51:01+00:00 | 2023-05-18 21:31:01+00:00 | | 1.5 | 2023-05-12 14:40:01+00:00 | 1 | 0.618 | Accessories | GENUINE LEATHER SNAP ON STUDDED WHITE PIANO BELT FITS ANY BUCKLE | H2W | 1.5 | Women |
| 61666 | 42590 | 34076 | 14202 | 166332 | Complete | 2020-08-29 02:23:22+00:00 | 2020-08-28 13:10:03+00:00 | 2020-08-29 15:31:03+00:00 | | 1.5 | 2020-08-28 05:52:03+00:00 | 2 | 0.618 | Accessories | GENUINE LEATHER SNAP ON STUDDED WHITE PIANO BELT FITS ANY BUCKLE | H2W | 1.5 | Women |
| 121460 | 83732 | 66908 | 14202 | 327981 | Complete | 2025-11-05 08:56:45+00:00 | 2025-11-07 08:23:40+00:00 | 2025-11-09 13:25:40+00:00 | | 1.5 | 2025-11-05 11:21:40+00:00 | 2 | 0.618 | Accessories | GENUINE LEATHER SNAP ON STUDDED WHITE PIANO BELT FITS ANY BUCKLE | H2W | 1.5 | Women |
| 10792 | 7452 | 5932 | 14202 | 29118 | Processing | 2021-10-08 15:16:00+00:00 | | | | 1.5 | 2021-10-08 17:57:29+00:00 | 1 | 0.618 | Accessories | GENUINE LEATHER SNAP ON STUDDED WHITE PIANO BELT FITS ANY BUCKLE | H2W | 1.5 | Women |
| 23210 | 16042 | 12842 | 14202 | 62579 | Processing | 2024-07-27 21:27:16+00:00 | | | | 1.5 | 2024-07-26 22:36:45+00:00 | 2 | 0.618 | Accessories | GENUINE LEATHER SNAP ON STUDDED WHITE PIANO BELT FITS ANY BUCKLE | H2W | 1.5 | Women |
| 74796 | 51632 | 41311 | 14202 | 201804 | Returned | 2025-09-12 17:24:29+00:00 | 2025-09-13 01:50:37+00:00 | 2025-09-17 17:42:37+00:00 | 2025-09-19 22:16:37+00:00 | 1.5 | 2025-09-12 20:27:37+00:00 | 1 | 0.618 | Accessories | GENUINE LEATHER SNAP ON STUDDED WHITE PIANO BELT FITS ANY BUCKLE | H2W | 1.5 | Women |
### 統計的特徴、異常値、相関関係
- **データ期間**: `order_item_created_at` は2019年から2026年にかけて分布しており、eコマースの全ライフサイクルをカバーしています。
- **ステータス**: `order_item_status` には `Complete`, `Shipped`, `Processing`, `Cancelled`, `Returned` の5つのステータスが存在します。これにより、正常な販売だけでなく、キャンセルや返品といったサプライチェーン上の重要なイベントも分析可能です。
- **価格とコスト**: `sale_price` と `product_cost` の両方が含まれているため、アイテムごとの粗利益を直接計算できます。一部、コストが0または非常に低い商品が見られ、これらはサンプル品や特殊な商品の可能性があります。
- **相関**: `num_items_in_order`(注文内のアイテム数)と `sale_price` の間には、まとめ買いによる割引などの関係性が見られる可能性があります。
### その他の特筆項目
- このデータはサンプリングされているため、正確な合計値を求めるには適していませんが、分布や傾向、相関関係を把握するには十分な代表性を持っています。
- `product_department`(メンズ/ウィメンズ)と`product_category`を組み合わせることで、性別ごとのカテゴリ嗜好などを分析する切り口も提供します。
---
## セクション2:顧客プロファイリングのミクロデータ基盤
### データの説明
このベースデータは、全顧客(`users`)を基点とし、各顧客の注文履歴(`orders`)を時系列で紐付けたものです。顧客の属性情報(年齢、性別、居住地、流入元)と、その顧客が行った個々の注文情報(注文日時、ステータス、アイテム数)が1レコードになっています。
後続のエージェントは、このデータを活用して以下のような顧客分析を実行できます。
- **顧客セグメンテーション**:
- デモグラフィック(年齢、性別、地域)や流入元(`traffic_source`)別の顧客グループを作成
- 各セグメントの購買行動(購入頻度、購入アイテム数)を分析
- **LTV(顧客生涯価値)分析**:
- 顧客ごとの注文履歴を集計し、総購入額や平均購入単価を算出するための基礎データとして利用
- `user_created_at`(登録日)と`order_created_at`(注文日)から、初回購入までの期間や継続購入のパターンを分析
- **地理的分析**:
- `country`, `state`, `city` を用いた地域別の顧客分布や売上分析
- `latitude`, `longitude` を用いた、より詳細な地図上での可視化
- **非購買ユーザーの特定**:
- `LEFT JOIN` を使用しているため、`order_id` が `NULL` のレコードを抽出することで、一度も購入に至っていない登録ユーザーを特定し、その属性を分析可能
### CSV データを取得するベース SQL
```sql
SELECT
t1.id as user_id,
t1.first_name,
t1.last_name,
t1.email,
t1.age,
t1.gender,
t1.state,
t1.street_address,
t1.postal_code,
t1.city,
t1.country,
t1.latitude,
t1.longitude,
t1.traffic_source,
t1.created_at as user_created_at,
t2.order_id,
t2.status as order_status,
t2.created_at as order_created_at,
t2.returned_at as order_returned_at,
t2.shipped_at as order_shipped_at,
t2.delivered_at as order_delivered_at,
t2.num_of_item as num_items_in_order
FROM
`bigquery-public-data.thelook_ecommerce.users` AS t1 TABLESAMPLE SYSTEM (1 PERCENT)
LEFT JOIN
`bigquery-public-data.thelook_ecommerce.orders` AS t2
ON t1.id = t2.user_id
```
### CSV データの全レコード件数(フィルタリング前)
100,000
### CSV データのサンプル
| user_id | first_name | last_name | email | age | gender | state | street_address | postal_code | city | country | latitude | longitude | traffic_source | user_created_at | order_id | order_status | order_created_at | order_returned_at | order_shipped_at | order_delivered_at | num_items_in_order |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 75822 | Janet | Boyd | janetboyd@example.com | 43 | F | Acre | 91843 Mcdaniel Ways | 69980-000 | null | Brasil | -8.065346 | -72.870949 | Facebook | 2019-06-08 11:39:00+00:00 | 94834 | Shipped | 2022-05-15 10:25:29+00:00 | | 2022-05-17 02:12:29+00:00 | | 1 |
| 36479 | Christopher | Hardy | christopherhardy@example.org | 33 | M | Acre | 69363 Myers Junctions | 69980-000 | null | Brasil | -8.065346 | -72.870949 | Search | 2019-07-18 01:03:00+00:00 | 45565 | Shipped | 2024-11-08 11:06:22+00:00 | | 2024-11-09 20:38:22+00:00 | | 1 |
| 35495 | Eric | Shelton | ericshelton@example.com | 41 | M | Acre | 178 Paul Row | 69980-000 | null | Brasil | -8.065346 | -72.870949 | Search | 2019-10-01 02:06:00+00:00 | 44325 | Processing | 2022-06-16 04:20:17+00:00 | | | | 1 |
| 97574 | Audrey | Patterson | audreypatterson@example.com | 57 | F | Acre | 608 Rachel Orchard | 69980-000 | null | Brasil | -8.065346 | -72.870949 | Search | 2024-02-12 15:00:00+00:00 | 121801 | Processing | 2025-09-19 13:24:22+00:00 | | | | 1 |
| 97574 | Audrey | Patterson | audreypatterson@example.com | 57 | F | Acre | 608 Rachel Orchard | 69980-000 | null | Brasil | -8.065346 | -72.870949 | Search | 2024-02-12 15:00:00+00:00 | 121802 | Processing | 2025-10-29 08:31:44+00:00 | | | | 2 |
| 82326 | Lisa | Alexander | lisaalexander@example.org | 59 | F | Acre | 189 Rebecca Ferry Suite 092 | 69980-000 | null | Brasil | -8.065346 | -72.870949 | Search | 2025-05-14 09:21:00+00:00 | 102963 | Shipped | 2025-11-17 20:47:23+00:00 | | 2025-11-17 21:32:23+00:00 | | 1 |
| 54527 | Michael | Kelley | michaelkelley@example.com | 54 | M | Acre | 860 Deborah Lane | 69980-000 | null | Brasil | -8.065346 | -72.870949 | Search | 2019-02-17 14:15:00+00:00 | | | | | | | |
| 57829 | Kimberly | Lewis | kimberlylewis@example.org | 29 | F | Acre | 7229 Riley Station Suite 121 | 69980-000 | null | Brasil | -8.065346 | -72.870949 | Search | 2022-04-05 04:36:00+00:00 | 72304 | Shipped | 2023-12-05 23:51:09+00:00 | | 2023-12-06 06:45:09+00:00 | | 1 |
| 79698 | Theresa | Gibson | theresagibson@example.com | 30 | F | Acre | 7529 Gray Shores Suite 453 | 69980-000 | null | Brasil | -8.065346 | -72.870949 | Organic | 2024-12-10 04:25:00+00:00 | 99634 | Shipped | 2026-01-19 20:10:11+00:00 | | 2026-01-20 00:00:11+00:00 | | 4 |
| 79698 | Theresa | Gibson | theresagibson@example.com | 30 | F | Acre | 7529 Gray Shores Suite 453 | 69980-000 | null | Brasil | -8.065346 | -72.870949 | Organic | 2024-12-10 04:25:00+00:00 | 99635 | Shipped | 2026-02-12 23:03:57+00:00 | | 2026-02-13 09:20:57+00:00 | | 2 |
| 91483 | Alicia | Dominguez | aliciadominguez@example.com | 40 | F | Acre | 32800 Berry Locks Suite 340 | 69980-000 | null | Brasil | -8.065346 | -72.870949 | Search | 2020-02-12 14:40:00+00:00 | 114198 | Returned | 2020-02-24 10:29:34+00:00 | 2020-03-01 21:32:34+00:00 | 2020-02-24 16:35:34+00:00 | 2020-02-28 02:37:34+00:00 | 1 |
| 41423 | Maria | George | mariageorge@example.com | 38 | F | Acre | 362 Ochoa Court Apt. 467 | 69980-000 | null | Brasil | -8.065346 | -72.870949 | Search | 2022-11-10 03:27:00+00:00 | 51775 | Shipped | 2025-07-24 21:41:25+00:00 | | 2025-07-25 08:19:25+00:00 | | 1 |
| 77762 | Valerie | Waller | valeriewaller@example.com | 25 | F | Acre | 82509 Monica Avenue Apt. 609 | 69980-000 | null | Brasil | -8.065346 | -72.870949 | Search | 2025-03-19 07:10:00+00:00 | 97188 | Cancelled | 2026-02-07 03:18:07+00:00 | | | | 1 |
| 68931 | Joshua | Young | joshuayoung@example.org | 23 | M | Acre | 2205 Margaret Corner | 69970-000 | Tarauacá | Brasil | -8.325245 | -71.553030 | Organic | 2020-09-27 06:54:00+00:00 | | | | | | | |
| 72619 | Tanner | Allen | tannerallen@example.net | 39 | M | Acre | 7930 Walter Trail | 69970-000 | Tarauacá | Brasil | -8.325245 | -71.553030 | Search | 2025-02-13 02:27:00+00:00 | | | | | | | |
| 74540 | Austin | Jackson | austinjackson@example.com | 48 | M | Acre | 94670 Woods Square | 69970-000 | Tarauacá | Brasil | -8.325245 | -71.553030 | Search | 2022-01-01 17:40:00+00:00 | 93266 | Complete | 2025-05-06 00:02:26+00:00 | | 2025-05-08 22:17:26+00:00 | 2025-05-10 08:18:26+00:00 | 1 |
| 76020 | Jason | Jones | jasonjones@example.org | 41 | M | Acre | 73218 Lewis Motorway Suite 602 | 69970-000 | Tarauacá | Brasil | -8.325245 | -71.553030 | Search | 2020-05-28 15:09:00+00:00 | | | | | | | |
| 44561 | Derek | Dickerson | derekdickerson@example.com | 27 | M | Acre | 953 Justin Path Suite 154 | 69970-000 | Tarauacá | Brasil | -8.325245 | -71.553030 | Search | 2021-04-29 02:00:00+00:00 | 55725 | Processing | 2024-10-26 14:09:06+00:00 | | | | 2 |
| 90194 | Juan | Cummings | juancummings@example.com | 41 | M | Acre | 8890 Fuller Valley Apt. 001 | 69970-000 | Tarauacá | Brasil | -8.325245 | -71.553030 | Search | 2019-12-23 16:45:00+00:00 | 112615 | Cancelled | 2024-12-30 08:26:25+00:00 | | | | 1 |
| 90194 | Juan | Cummings | juancummings@example.com | 41 | M | Acre | 8890 Fuller Valley Apt. 001 | 69970-000 | Tarauacá | Brasil | -8.325245 | -71.553030 | Search | 2019-12-23 16:45:00+00:00 | 112616 | Cancelled | 2025-10-01 00:29:53+00:00 | | | | 1 |
### 統計的特徴、異常値、相関関係
- **顧客と注文の関係**: 1人の顧客(`user_id`)が複数の注文(`order_id`)を持つことがあり、データは縦に長くなる形式です。これにより、顧客ごとの時間経過に伴う購買行動の変化を追跡できます。
- **非購買ユーザー**: サンプルデータ内にも `order_id` が `NULL` のユーザーが含まれており、これは登録はしたもののまだ購入に至っていない顧客層の存在を示唆しています。これらの顧客の `traffic_source` やデモグラフィックを分析することで、コンバージョン改善のヒントが得られる可能性があります。
- **地理的分布**: 顧客は `Brasil`, `United States`, `China` など、世界中に分布しています。`city` が `null` のレコードも存在し、データクレンジングの必要性を示唆しています。
- **流入経路**: `traffic_source` には `Search`, `Organic`, `Facebook`, `Email` など複数のチャネルがあり、チャネル別の顧客獲得効率やLTVを分析する上で重要な次元となります。
### その他の特筆項目
- このデータは、顧客を軸にしたあらゆる分析の出発点となります。後続のエージェントは、`user_id` でグループ化することで、顧客単位の指標(例:総購入回数、総購入額、平均注文額、最新購買日など)を自由に計算できます。
- `user_created_at` は顧客登録日を示しており、顧客の「サービス利用期間」を算出するための起点となります。
---
## セクション3:物流・オペレーションのミクロデータ基盤
### データの説明
このベースデータは、注文商品(`order_items`)のライフサイクル(作成、出荷、配送、返品)に関連するタイムスタンプと、商品の発送元である物流センター(`distribution_centers`)、および配送先である顧客(`users`)の地理情報を組み合わせたものです。各行が「どの商品が」「いつ」「どこから」「どこへ」移動したか(またはする予定か)を示します。
後続のエージェントは、このデータを活用して以下のような物流・オペレーション分析を実行できます。
- **リードタイム分析**:
- `order_item_created_at` から `order_item_shipped_at` までの「出荷リードタイム」
- `order_item_shipped_at` から `order_item_delivered_at` までの「配送リードタイム」
- 物流センター別、配送先地域(国、州)別にリードタイムを比較分析
- **オペレーション効率分析**:
- 物流センター(`distribution_center_name`)ごとの処理能力や遅延発生率を分析
- `dc_latitude`/`dc_longitude` と `user_latitude`/`user_longitude` を使用して、配送距離を算出し、距離とリードタイムの関係性を調査
- **問題検出**:
- `order_item_status` が `Cancelled` や `Returned` となった注文について、関連する物流センターや配送地域を特定し、問題の原因を探る
- `shipped_at` や `delivered_at` が `NULL` のまま長期間経過している `Processing` ステータスの注文を検出し、潜在的な配送問題を警告
### CSV データを取得するベース SQL
```sql
SELECT
t1.id as order_item_id,
t1.order_id,
t1.user_id,
t1.inventory_item_id,
t1.status as order_item_status,
t1.created_at as order_item_created_at,
t1.shipped_at as order_item_shipped_at,
t1.delivered_at as order_item_delivered_at,
t1.returned_at as order_item_returned_at,
t2.product_distribution_center_id,
t3.name as distribution_center_name,
t3.latitude as dc_latitude,
t3.longitude as dc_longitude,
t4.state as user_state,
t4.city as user_city,
t4.country as user_country,
t4.latitude as user_latitude,
t4.longitude as user_longitude
FROM
`bigquery-public-data.thelook_ecommerce.order_items` AS t1 TABLESAMPLE SYSTEM (0.55 PERCENT)
LEFT JOIN
`bigquery-public-data.thelook_ecommerce.inventory_items` AS t2 ON t1.inventory_item_id = t2.id
LEFT JOIN
`bigquery-public-data.thelook_ecommerce.distribution_centers` AS t3 ON t2.product_distribution_center_id = t3.id
LEFT JOIN
`bigquery-public-data.thelook_ecommerce.users` AS t4 ON t1.user_id = t4.id
```
### CSV データの全レコード件数(フィルタリング前)
180,858
### CSV データのサンプル
| order_item_id | order_id | user_id | inventory_item_id | order_item_status | order_item_created_at | order_item_shipped_at | order_item_delivered_at | order_item_returned_at | product_distribution_center_id | distribution_center_name | dc_latitude | dc_longitude | user_state | user_city | user_country | user_latitude | user_longitude |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 169671 | 117170 | 93894 | 458360 | Cancelled | 2024-01-15 07:38:21+00:00 | | | | 9 | Charleston SC | 32.7833 | -79.9333 | Arizona | Mesa | United States | 33.476462 | -111.629361 |
| 172479 | 119110 | 95413 | 465990 | Cancelled | 2025-11-06 04:11:05+00:00 | | | | 9 | Charleston SC | 32.7833 | -79.9333 | Bourgogne-Franche-Comté | Dijon | France | 47.322872 | 5.037560 |
| 3667 | 2531 | 1993 | 9866 | Complete | 2026-02-08 09:48:37+00:00 | 2026-02-08 07:25:56+00:00 | 2026-02-09 18:24:56+00:00 | | 9 | Charleston SC | 32.7833 | -79.9333 | Gyeonggi-do | Yongin City | South Korea | 37.250541 | 127.091296 |
| 35471 | 24498 | 19535 | 95659 | Complete | 2025-08-17 15:16:12+00:00 | 2025-08-18 05:53:38+00:00 | 2025-08-18 06:51:38+00:00 | | 9 | Charleston SC | 32.7833 | -79.9333 | Sichuan | Datong | China | 30.821632 | 104.247353 |
| 110800 | 76407 | 61150 | 299118 | Complete | 2021-05-22 21:35:57+00:00 | 2021-05-22 15:00:23+00:00 | 2021-05-26 18:00:23+00:00 | | 9 | Charleston SC | 32.7833 | -79.9333 | New York | Rome | United States | 43.211728 | -75.460447 |
| 130875 | 90241 | 72042 | 353493 | Complete | 2025-05-27 17:42:52+00:00 | 2025-05-29 06:51:04+00:00 | 2025-05-29 12:01:04+00:00 | | 9 | Charleston SC | 32.7833 | -79.9333 | Sachsen | Döbeln | Germany | 51.141603 | 13.124396 |
| 164195 | 113364 | 90806 | 443589 | Complete | 2024-02-19 17:38:25+00:00 | 2024-02-20 14:10:50+00:00 | 2024-02-22 14:43:50+00:00 | | 9 | Charleston SC | 32.7833 | -79.9333 | Inner Mongolia Autonomous Region | Shenzhen | China | 40.748466 | 111.636653 |
| 180811 | 124838 | 99977 | 488523 | Complete | 2021-10-16 02:27:03+00:00 | 2021-10-17 07:53:50+00:00 | 2021-10-20 13:00:50+00:00 | | 9 | Charleston SC | 32.7833 | -79.9333 | Guangdong | Foshan | China | 22.652173 | 114.074965 |
| 8153 | 5615 | 4482 | 21987 | Processing | 2021-05-08 20:44:51+00:00 | | | | 9 | Charleston SC | 32.7833 | -79.9333 | Guangxi Zhuang Autonomous Region | Xiamen | China | 22.832696 | 108.320799 |
| 81918 | 56556 | 45260 | 221061 | Processing | 2024-02-25 15:28:05+00:00 | | | | 9 | Charleston SC | 32.7833 | -79.9333 | Michigan | Grand Rapids | United States | 42.962233 | -85.659872 |
| 129319 | 89171 | 71175 | 349269 | Processing | 2025-10-02 08:41:51+00:00 | | | | 9 | Charleston SC | 32.7833 | -79.9333 | Guangdong | Beijing | China | 23.105136 | 113.743445 |
| 22959 | 15868 | 12717 | 61905 | Returned | 2025-11-14 10:06:27+00:00 | 2025-11-15 10:06:59+00:00 | 2025-11-16 00:04:59+00:00 | 2025-11-18 05:22:59+00:00 | 9 | Charleston SC | 32.7833 | -79.9333 | Nordrhein-Westfalen | Bergkamen | Germany | 51.622955 | 7.630646 |
| 37921 | 26228 | 20894 | 102310 | Shipped | 2025-06-23 21:05:38+00:00 | 2025-06-22 22:45:05+00:00 | | | 9 | Charleston SC | 32.7833 | -79.9333 | Zhejiang | Xinxiang | China | 30.216092 | 121.257722 |
| 38902 | 26893 | 21433 | 104907 | Shipped | 2021-11-12 10:22:14+00:00 | 2021-11-12 14:22:33+00:00 | | | 9 | Charleston SC | 32.7833 | -79.9333 | Rio de Janeiro | Itaboraí | Brasil | -22.730295 | -42.862038 |
| 167389 | 115576 | 92588 | 452178 | Shipped | 2025-08-17 01:49:54+00:00 | 2025-08-17 19:55:03+00:00 | | | 9 | Charleston SC | 32.7833 | -79.9333 | Hebei | Heze | China | 37.988926 | 115.559113 |
| 132343 | 91253 | 72862 | 357500 | Cancelled | 2022-10-23 20:49:14+00:00 | | | | 9 | Charleston SC | 32.7833 | -79.9333 | Beijing | Huludao | China | 39.993665 | 116.577071 |
| 93493 | 64535 | 51776 | 252290 | Complete | 2024-08-07 02:30:14+00:00 | 2024-08-09 21:56:46+00:00 | 2024-08-10 08:25:46+00:00 | | 9 | Charleston SC | 32.7833 | -79.9333 | Michigan | Mount Morris Township | United States | 43.057409 | -83.749512 |
| 54131 | 37460 | 30005 | 145932 | Processing | 2022-12-08 11:18:48+00:00 | | | | 9 | Charleston SC | 32.7833 | -79.9333 | Goiás | Itaberaí | Brasil | -16.080027 | -49.822453 |
| 32028 | 22135 | 17662 | 86389 | Shipped | 2023-03-12 11:12:35+00:00 | 2023-03-12 01:51:54+00:00 | | | 9 | Charleston SC | 32.7833 | -79.9333 | Connecticut | Hartford | United States | 41.739233 | -72.668289 |
| 161427 | 111478 | 89257 | 436087 | Shipped | 2021-12-19 03:43:37+00:00 | 2021-12-21 02:53:41+00:00 | | | 9 | Charleston SC | 32.7833 | -79.9333 | Ohio | London | United States | 39.879559 | -83.432171 |
### 統計的特徴、異常値、相関関係
- **タイムスタンプの粒度**: `created_at`, `shipped_at`, `delivered_at`, `returned_at` の4つのタイムスタンプにより、注文ライフサイクルの各フェーズにかかる時間を詳細に分析できます。
- **地理情報**: 物流センター(`dc_`)と顧客(`user_`)双方の緯度経度(`latitude`/`longitude`)が含まれているため、物理的な配送距離とリードタイムの相関分析が可能です。
- **データの完全性**: `shipped_at` や `delivered_at` が `NULL` のレコードは、まだ処理中、あるいはキャンセルされた注文を示します。これらのレコードの割合をモニタリングすることで、オペレーションの健全性を評価できます。
- **異常値**: `shipped_at` が `created_at` よりも前になっている、あるいは `delivered_at` が `shipped_at` よりも前になっているといったデータは、システム上の入力ミスや異常を示唆しており、データ品質の評価に役立ちます。
### その他の特筆項目
- このデータセットは、サプライチェーンのボトルネックを特定し、配送効率を改善するためのインサイトを提供します。
- 例えば、「特定の物流センターから特定の地域への配送だけが遅延している」といった仮説を検証し、具体的な改善アクションに繋げることが期待できます。
---
## セクション4:ユーザー行動(Webイベント)のミクロデータ基盤
### データの説明
このベースデータは、Webサイトまたはアプリ上で発生した個々のユーザー行動イベント(`events`)の全記録です。各行が「いつ」「誰が(またはどのセッションが)」「どのページで」「何をしたか」という詳細な情報を含んでいます。
後続のエージェントは、このデータを活用して以下のようなユーザー行動分析を実行できます。
- **ファネル分析**:
- `event_type`(例: `department`, `category`, `product`, `cart`, `purchase`)を `sequence_number`(セッション内でのイベント順序)で並べ、ユーザーが購買に至るまでの各ステップでの離脱率を算出
- 特定の `traffic_source` や `browser` からの流入ユーザーのファネルを比較分析
- **セッション分析**:
- `session_id` でグループ化し、1セッションあたりのページビュー数、滞在時間(`created_at` の最小と最大)、コンバージョン率を分析
- **行動パターン分析**:
- `uri`(ページURL)を分析し、ユーザーがどのページをどのような順序で閲覧しているかの行動フローを可視化
- `event_type` が `search` のレコードを抽出し、ユーザーがどのようなキーワードで検索しているかを分析
- **ユーザー識別**:
- `user_id` が `NULL` でないイベントは、ログイン済みユーザーの行動を示します。これにより、特定の顧客セグメント(セクション2参照)のWeb行動を詳細に追跡できます。
- `user_id` が `NULL` のイベントは、非ログインユーザーまたは新規訪問者の行動を示し、新規獲得ファネルの分析に利用できます。
### CSV データを取得するベース SQL
```sql
SELECT
*
FROM
`bigquery-public-data.thelook_ecommerce.events` TABLESAMPLE SYSTEM (0.041 PERCENT)
```
### CSV データの全レコード件数(フィルタリング前)
2,420,973
### CSV データのサンプル
| id | user_id | sequence_number | session_id | created_at | ip_address | city | state | postal_code | browser | traffic_source | uri | event_type |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1715695 | | 3 | 3b1a0980-610c-4ef0-9a9b-c44b3be4a8dc | 2021-02-13 15:54:00+00:00 | 101.4.222.233 | Loda City | Akita | 018-5201 | Firefox | Email | /cancel | cancel |
| 2207749 | | 3 | eb92241a-88c5-445e-bbb9-631bbc7b4ad7 | 2023-01-31 14:17:00+00:00 | 162.61.46.228 | São Paulo | São Paulo | 02675-031 | Chrome | Adwords | /cancel | cancel |
| 2267293 | | 3 | 34ce76d3-f281-4473-9198-7c3452676e7a | 2023-03-30 05:22:00+00:00 | 30.25.219.247 | São Paulo | São Paulo | 02675-031 | Chrome | Adwords | /cancel | cancel |
| 2288060 | | 3 | 3549145a-b2d6-4632-aa56-baedbdcd7d67 | 2021-10-03 16:52:00+00:00 | 74.116.137.135 | São Paulo | São Paulo | 02675-031 | Chrome | Facebook | /cancel | cancel |
| 1639811 | | 3 | 6ade5677-73b8-4f91-b76f-8e3508bccfe4 | 2023-04-15 08:11:00+00:00 | 184.52.36.70 | São Paulo | São Paulo | 02675-031 | Safari | Adwords | /cancel | cancel |
| 1680714 | | 3 | 14349c11-ab07-44bf-812d-6a2321701cee | 2020-03-03 04:11:00+00:00 | 157.52.14.206 | São Paulo | São Paulo | 02675-031 | Chrome | Adwords | /cancel | cancel |
| 1826154 | | 3 | a51f15dd-949c-464d-bc80-5464172b3c06 | 2025-04-13 05:59:00+00:00 | 5.13.93.231 | São Paulo | São Paulo | 02675-031 | Chrome | Email | /cancel | cancel |
| 2163475 | | 3 | 3f36c4d3-e215-438c-9829-7e605dd162f4 | 2021-02-19 13:52:00+00:00 | 24.96.121.120 | São Paulo | São Paulo | 02675-031 | Chrome | Adwords | /cancel | cancel |
| 2129626 | | 3 | e65be876-7ed6-40a6-897f-4b708665c097 | 2022-02-02 05:06:00+00:00 | 136.14.248.209 | São Paulo | São Paulo | 02675-031 | Chrome | Email | /cancel | cancel |
| 1598371 | | 3 | 7792b303-af2b-4510-9238-023a07b5a0d1 | 2022-11-14 15:04:00+00:00 | 76.234.58.59 | São Paulo | São Paulo | 02675-031 | Safari | Email | /cancel | cancel |
| 2015442 | | 3 | 8f5408d1-c787-4b15-bbda-c35fe0af0980 | 2020-03-07 06:23:00+00:00 | 93.22.15.174 | São Paulo | São Paulo | 02675-031 | Chrome | YouTube | /cancel | cancel |
| 2150413 | | 3 | f434a679-9b12-49b3-854d-6d84d036c348 | 2025-08-08 04:05:00+00:00 | 202.190.185.249 | São Paulo | São Paulo | 02675-031 | IE | Email | /cancel | cancel |
| 1771096 | | 3 | de1223e5-cc4a-4578-9f34-3650759b4228 | 2023-01-29 19:18:00+00:00 | 17.124.165.207 | São Paulo | São Paulo | 02675-031 | Chrome | Email | /cancel | cancel |
| 1495455 | | 3 | 9defa05b-85de-4bce-80f7-4c37ef60e55c | 2021-02-26 10:02:00+00:00 | 182.43.126.25 | São Paulo | São Paulo | 02675-031 | Firefox | Adwords | /cancel | cancel |
| 1700448 | | 3 | cda7fadf-fb37-44a0-96ad-16a2b3e9ad72 | 2021-10-02 03:00:00+00:00 | 63.251.133.1 | São Paulo | São Paulo | 02675-031 | Chrome | Email | /cancel | cancel |
| 2337129 | | 3 | 55a1c4da-4791-466a-8f09-cb63c8cd94f4 | 2022-09-05 06:48:00+00:00 | 210.222.191.151 | São Paulo | São Paulo | 02675-031 | Chrome | YouTube | /cancel | cancel |
| 2054797 | | 3 | c53fdc10-6931-4ed9-b01f-841c444a37b8 | 2023-03-01 03:49:00+00:00 | 129.102.187.14 | São Paulo | São Paulo | 02675-031 | Other | Email | /cancel | cancel |
| 1778017 | | 3 | 17d163ab-3047-4fc8-a6ed-bb731443fc7f | 2023-11-26 17:37:00+00:00 | 113.111.201.141 | São Paulo | São Paulo | 02675-031 | Firefox | Email | /cancel | cancel |
| 2328750 | | 3 | 9dfe8b35-dda2-4150-8437-90bfe4405778 | 2025-06-18 03:27:00+00:00 | 74.142.86.31 | São Paulo | São Paulo | 02675-031 | IE | Email | /cancel | cancel |
| 1370892 | | 3 | 6f701aa4-aae7-4423-a60d-11d771f1830d | 2019-06-20 16:21:00+00:00 | 88.111.77.50 | São Paulo | São Paulo | 02675-031 | Safari | Email | /cancel | cancel |
### 統計的特徴、異常値、相関関係
- **イベントの多様性**: `event_type` には `cart`, `purchase`, `cancel`, `department`, `product` など、ユーザーの購買ファネルにおける各段階の行動が記録されています。
- **セッションの追跡**: `session_id` により、一連のユーザー行動をまとめて追跡することが可能です。`sequence_number` はセッション内での行動順序を示しており、ユーザーの行動パスを再構築するのに役立ちます。
- **技術的属性**: `browser` (`Chrome`, `Safari`, `Firefox`, `IE`, `Other`) や `traffic_source` (`Email`, `Adwords`, `YouTube`, `Facebook`, `Organic`) といった技術的な属性が含まれており、これらの違いによるユーザー行動の差異を分析できます。
- **匿名ユーザー**: サンプルデータの多くで `user_id` が `NULL` となっており、これは非ログインユーザーによるセッションが非常に多いことを示唆しています。これらのセッションを分析することは、新規顧客獲得の観点から重要です。
### その他の特筆項目
- このデータは非常に粒度が細かいため、WebサイトのUI/UX改善や、マーケティングキャンペーンの効果測定に直結するインサイトを得るための強力な基盤となります。
- 例えば、「特定のブラウザを使っているユーザーだけが、カートページで離脱しやすい」といった具体的な問題点を発見し、技術的な対応を検討するきっかけとなり得ます。
これは、次の4つのセクションから構成されています。
- セクション1:売上・商品分析のミクロデータ基盤
- セクション2:顧客プロファイリングのミクロデータ基盤
- セクション3:物流・オペレーションのミクロデータ基盤
- セクション4:ユーザー行動(Webイベント)のミクロデータ基盤
最初のセクションだけ見やすく整形して表示すると、次のようになります。

データ・ナレッジベースの例(セクション1)
データ・ナレッジベースには、どのようにテーブルを組み合わせればビジネス的に意味のあるデータが得られるのか、そして、そのデータからどのような分析ができるのかというヒントが記載されています。人間のデータアナリストが持っていた、テーブル構造やビジネスドメインに関する暗黙知を明文化したドキュメントと言えるでしょう。

データアナリストの暗黙知をデータ・ナレッジベースとして明文化
この情報を持った AI エージェントを利用すれば、単純な NL2SQL よりも安定した、信頼のおけるデータ分析エージェントが作成できるというわけです。
データ・ナレッジベースの作成・利用方法
「とはいえ、データ・ナレッジベースの作成が大変じゃない?」と思ったあなた ——— おしいです。
データ・ナレッジベースそのものの作成にも AI エージェントを活用すれば大丈夫です。実際、先ほどのデータ・ナレッジベースは、次のインストラクションを持つ AI エージェントで自動作成したものです。

データ・ナレッジベース生成に使用するインストラクション
このインストラクションに従って、AI エージェントが作成したデータ・ナレッジベースに含まれる「ベース SQL」に誤りがないことは、データアナリストが事前に確認します。また、データ分析に必要となるビジネスに関する前提知識など、自動生成されたデータ・ナレッジベースに含まれないドメイン知識を書き加えていきます。データが頻繁に更新される環境であれば、定期的にデータ・ナレッジベースを再作成する運用が必要ですが、この際は、データアナリストが書き加えた情報が失われないように、既存のデータ・ナレッジベースを更新するように AI エージェントに指示を与えるとよいでしょう。

データ・ナレッジベースの作成プロセス
さらに、このデータ・ナレッジベースを利用する AI エージェントには、次のようなインストラクションを与えます。

データ分析に使用するインストラクション
「3. SQL 実行の権限と制約」の部分では、基本的には「ベース SQL」を用いながら、どうしても「ベース SQL + 集計処理」の基本パターンからはずれた SQL の実行が必要な際は、実行する SQL をユーザーに提示して確認するように指示しています。ただし、ビジネス視点でのデータ分析では、分析したい内容はある程度の「型」が決まっています。データ分析を行うビジネスユーザーから、希望する分析パターンをヒアリングして、対応するデータ・ナレッジベースをしっかりと用意しておけば、基本パターンの SQL だけでほとんどの場合は対応可能です。
次は、Google Cloud の環境で Gemini 2.5 Pro を用いた AI エージェントで作成した分析レポートです。ここに含まれる分析は、すべて、ベース SQL を元にした基本パターンの SQL で行われています。

AI エージェント(Gemini 2.5 Pro)による分析例
補足:Agent Skills との関係
AI エージェントに特定ドメインの知識を追加する方法として、Agent Skills があります。これは、標準化されたフォーマットのマークダウンテキストを用いて、AI エージェントの中核となる LLM が十分に学習していない情報を AI エージェントに対する参考情報として記述したものです。AI エージェントは、必要に応じて、Skills に記載された情報を参照することで、ユーザーが期待する処理を実現します。AI エージェントに適切なコンテキスト情報を注入する「コンテキストエンジニアリング」の手法の1つと言えるでしょう。
この記事で作成する「データ・ナレッジベース」は、Skills の形式にはなっていませんが、AI エージェントにマークダウンテキストの形で必要な情報(ドメイン知識)を提供するという意味では、本質的には同じことを行なっています。データ・ナレッジベースに含まれる情報をより体系化した複数の Skills として整理することも可能ですが、まずはじめは、この記事のように、シンプルな単一のテキストにまとめるのがおすすめです。
特に、Google Cloud が提供する基盤モデルの Gemini は、非常に大きなコンテキストウィンドウを持っているので、細切れの Skills に分解した情報を与えるよりは、すべての情報を1つのテキストにまとめて与えることで、より安定的な動作が期待できます。参照するデータベースの数が増えるなどの理由で、データ・ナレッジベースの内容が多岐にわたり、分析内容に応じて与える情報を変えるなどの工夫が必要になった際は、Skills の導入を検討してもよいでしょう。
ハンズオンで試してみる
ここまでに説明した内容を Google Cloud の環境で実際に試してみましょう。
環境準備
Vertex AI Workbench のノートブック上で実装しながら説明するために、まずは、ノートブックの実行環境を用意します。新しいプロジェクトを作成したら、Cloud Shell のコマンド端末を開いて、必要な API を有効化します。
gcloud services enable \
aiplatform.googleapis.com \
notebooks.googleapis.com \
cloudresourcemanager.googleapis.com
続いて、Workbench のインスタンスを作成します。
PROJECT_ID=$(gcloud config list --format 'value(core.project)' 2>/dev/null)
gcloud workbench instances create development-instance \
--project=$PROJECT_ID \
--location=us-central1-a \
--machine-type=e2-standard-2
クラウドコンソールのナビゲーションメニューから「Vertex AI」→「Workbench」を選択すると、作成したインスタンス development-instance があります。インスタンスの起動が完了するのを待って、「JUPYTERLAB を開く」をクリックしたら、「Python 3(ipykernel)」の新規ノートブックを作成します。
この後は、ノートブックのセルでコードを実行していきます。
事前準備
ADK (Agent Development Kit) でエージェントを作成するので、そのために必要なパッケージをインストールします。グラフ描画機能で必要になる Pillow のパッケージもあわせてインストールします。
%pip install --upgrade --user \
Pillow \
google-adk==1.25.0 \
google-genai==1.56.0 \
google-cloud-aiplatform==1.132.0
インストールしたパッケージを利用可能にするために、次のコマンドでカーネルを再起動します。
import IPython
app = IPython.Application.instance()
_ = app.kernel.do_shutdown(True)
再起動を確認するポップアップが表示されるので [Ok] をクリックします。
続いて、必要なモジュールをインポートして、Vertex AI の環境を初期化します。
# Standard library
import asyncio
import base64
import io
import re
from google import auth
# Third-party libraries
from IPython.display import Markdown, display
from PIL import Image
# Google Cloud & Vertex AI
import vertexai
from google.genai.types import GenerateContentConfig
from vertexai.agent_engines import AdkApp
# Google ADK
from google.adk.agents.llm_agent import LlmAgent
from google.adk.artifacts import GcsArtifactService
from google.adk.code_executors import BuiltInCodeExecutor
from google.adk.tools.agent_tool import AgentTool
from google.adk.tools.bigquery import BigQueryCredentialsConfig, BigQueryToolset
from google.adk.tools.bigquery.config import BigQueryToolConfig, WriteMode
# Initialize Vertex AI
[PROJECT_ID] = !gcloud config list --format 'value(core.project)' 2>/dev/null
LOCATION = 'us-central1'
vertexai.init(project=PROJECT_ID, location=LOCATION)
グラフ描画機能では、GCS Artifact Service を利用して画像データを GCS バケットに保存するので、そのための GCS バケットを作成しておきます。
BUCKET_NAME = f'{PROJECT_ID}_artifacts'
!gsutil ls -b gs://{BUCKET_NAME} 2>/dev/null || \
gsutil mb -b on -l {LOCATION} gs://{BUCKET_NAME}
次は、ADK エージェントが BigQuery のテーブルにアクセスするためのツール関数を用意します。
tool_config = BigQueryToolConfig(write_mode=WriteMode.BLOCKED)
application_default_credentials, _ = auth.default()
credentials_config = BigQueryCredentialsConfig(
credentials=application_default_credentials
)
bigquery_toolset = BigQueryToolset(
credentials_config=credentials_config, bigquery_tool_config=tool_config
)
さらに、ノートブック上でエージェントと会話するための簡易アプリのクラスを定義します。
class ChatClient:
def __init__(self, adk_app, user_id='default_user'):
self.adk_app = adk_app
self.user_id = user_id
self.session_id = None
async def async_stream_query(self, message):
if not self.session_id:
session = await self.adk_app.async_create_session(
user_id=self.user_id,
)
self.session_id = getattr(session, 'id', None) or session['id']
result = []
async for event in self.adk_app.async_stream_query(
user_id=self.user_id,
session_id=self.session_id,
message=message,
):
if ('content' in event and 'parts' in event['content']):
response = '\n'.join(
[p['text'] for p in event['content']['parts'] if 'text' in p]
)
if response:
result.append(response)
return '\n'.join(result)
グラフ描画ツールの作成
ビジネス分析のレポートにはグラフがあるとわかりやくすなります。ここでは、エージェントがグラフを描くためのツール関数を用意します。いくつか関数の定義が続きますが、この記事としては本質ではないので、詳しい説明は割愛します。
グラフ表示のコールバック関数
# Async version of re.sub() that accepts async replacer.
async def async_sub(pattern, repl, string, count=0, flags=0):
matches = list(re.finditer(pattern, string, flags=flags))
if not matches:
return string
if count > 0:
matches = matches[:count]
tasks = [repl(m) for m in matches]
replacements = await asyncio.gather(*tasks)
result_parts = []
last_pos = 0
for match, replacement in zip(matches, replacements):
result_parts.append(string[last_pos:match.start()])
result_parts.append(replacement)
last_pos = match.end()
result_parts.append(string[last_pos:])
return ''.join(result_parts)
async def replace_filename_with_base64(
markdown_text, quality=80, max_width=None,
callback_context=None,
):
pattern = r'!\[(.*?)\]\((.*?)\)'
async def replacer(match):
alt_text = match.group(1)
filename = match.group(2)
# print(f'# Replacing {filename} with MIME binary', flush=True)
try:
image_artifact = await callback_context.load_artifact(filename=filename)
image_bytes = image_artifact.inline_data.data
with Image.open(io.BytesIO(image_bytes)) as img:
if img.mode in ('RGBA', 'P'):
img = img.convert('RGB')
if max_width and img.width > max_width:
ratio = max_width / img.width
new_height = int(img.height * ratio)
img = img.resize((max_width, new_height), Image.Resampling.LANCZOS)
output_buffer = io.BytesIO()
img.save(output_buffer, format='JPEG', quality=quality, optimize=True)
jpeg_data = output_buffer.getvalue()
b64_encoded = base64.b64encode(jpeg_data).decode('utf-8')
return f''
except Exception as e:
print(f'# Warning: Failed to process {filename}. Error: {e}', flush=True)
return match.group(0)
return await async_sub(pattern, replacer, markdown_text, flags=re.MULTILINE)
async def callback_load_images(
callback_context, llm_response,
):
if not (llm_response.content and llm_response.content.parts):
return None
for part in llm_response.content.parts:
if not part.text:
continue
part.text = await replace_filename_with_base64(
part.text, quality=80, max_width=None,
callback_context=callback_context,
)
return None
def replace_base64_with_filename(markdown_text):
pattern = r'!\[(.*?)\]\(data:image/(?:jpeg|png|gif|bmp|webp);base64,[A-Za-z0-9+/=]+\)'
def replacer(match):
original_alt_text = match.group(1)
if '#' in original_alt_text:
parts = original_alt_text.split('#')
full_gs_url = parts[-1]
clean_alt_text = '#'.join(parts[:-1])
else:
return match.group(0)
# print(f'# Replacing MIME binary with {full_gs_url}', flush=True)
return f''
return re.sub(pattern, replacer, markdown_text, flags=re.MULTILINE)
async def callback_remove_base64(
callback_context, llm_request,
):
if not llm_request.contents:
return None
for content in llm_request.contents:
if not content.parts:
continue
for part in content.parts:
if not part.text:
continue
part.text = replace_base64_with_filename(part.text)
return None
グラフを描画するエージェント
IMAGE_DPI = 70
chart_agent = LlmAgent(
model='gemini-2.5-flash',
name='chart_agent',
instruction=f'''
You are a specialist in code execution. Use matplotlib for plotting.
## Important conditions
- Do not use seaborn style option such as plt.style.use('seaborn-...')
- Max amount of data points in a single plot is around 20.
If you receive more data, write a code to reduce them in a statistically reasonable way.
- Return the title of the chart that you placed in the chart.
## Display conditions
- Use the constrained layout without setting `figsize`.
```python
fig, ax = plt.subplots(layout='constrained')
```
- Set default color with:
```python
import seaborn as sns
sns.set_palette('pastel')
```
- Use `fontsize=18` for the title.
- Use `fontsize=16` for labels.
- Use `fontsize=14` for ticks and legend.
- If necessary, place legend on top-right.
- Think about the readability of the chart when placing labels, ticks, legend etc.
## Final output image
Use the following pattern to save the image instead of showing it with `plt.show()`
```python
plt.savefig('output.png', format='png', dpi={IMAGE_DPI})
```
## Language requirements for plots
- All titles, labels, and legends MUST be in Japanese.
- Use of Japanese characters is supported by default.
You don't have to import any extra modules, or you don't have to set special fonts.
Bad pattern: `plt.rcParams['font.family'] = 'sans-serif'`
''',
code_executor=BuiltInCodeExecutor(),
)
データ・ナレッジベースを生成するエージェントの定義
先に説明した、データ・ナレッジベースを生成するエージェントを定義します。
root_agent = LlmAgent(
name='knowledge_base_generator',
model='gemini-2.5-pro',
instruction=f'''
あなたは AI エージェントのための高度な「コンテキスト・ジェネレーター」です。
人間のためのレポートではなく、後続の AI エージェントが大規模言語モデルの推論能力を最大限に発揮できるよう、**後続エージェントが自由に集計・絞り込みを行うための基盤となる、統計的密度が高く全期間を網羅したミクロデータ(明細レベルのナレッジベース)**を作成してください。
## データの網羅性とサンプリングに関する絶対原則
1. **ミクロデータ(明細レベル)の徹底**: 後続エージェントが `WHERE` や `GROUP BY` を用いて自由に多角的な分析を行えるよう、事前集計(マクロ・メゾ)は行わず、イベントやトランザクションの明細レベル(ミクロデータ)を提供してください。
2. **時間軸の完全網羅**: SQLを実行する前に必ずテーブルの最小日と最大日を確認し、特定の期間(直近30日など)に偏らせず、データセットに存在する全期間を対象に含めてください。
3. **サンプリングのランダム化**: レコード件数が制限(1,000件)を超える場合、単なる `LIMIT` ではなく、`TABLESAMPLE SYSTEM (x PERCENT)` を用いた分散抽出を行い、全体の分布を維持した「代表データ」を取得してください。
4. **Top N の禁止**: 「売上上位」などの偏ったフィルタリングは、ノイズ(低頻度だが重要な事象)を消し去る可能性があるため避けてください。
5. **リッチな結合(JOIN)の維持**: 後続エージェントが多様な切り口で分析できるよう、中心となるトランザクションデータに対し、関連するマスタデータ(ユーザー属性、商品情報など)を適切にJOINし、カラムが豊富なベースクエリを作成してください。
## ナレッジベースのコンテンツ構成
複数のセクションから構成されており、1つのセクションは、特定のビジネス視点に対応する CSV データを取得する SQL 文になります。
以下の構造でマークダウンを出力してください。
```markdown
# TheLook eCommerce データナレッジベース
## セクション番号:[ビジネス視点]のミクロデータ基盤
### データの説明
※ このベースデータを使って、後続エージェントがどのような分析(例:「カテゴリ別の売上推移」「地域別の返品率」など)を実行可能か、そのポテンシャルを記載すること。
### CSV データを取得するベース SQL
※ 実行可能な単一の SELECT 文。1,000件を超える場合はサンプリング済みの状態にすること。
※ 事前集計(GROUP BYによる要約)は原則行わず、明細行を維持すること。
※ 後続エージェントがこのクエリをサブクエリやCTE(WITH句)として扱い、外側からWHERE句やGROUP BY句を自由に追加して分析できる構成にすること。
### CSV データの全レコード件数(フィルタリング前)
### CSV データのサンプル
※ 最大20件。時系列の「最初・中間・最後」から抽出して提示すること。
### 統計的特徴、異常値、相関関係
※ サンプリングされたデータから読み取れる、後続エージェントの分析のヒントとなる特徴。
### その他の特筆項目
```
## セクションの選定方針(ビジネス視点ごとのミクロデータ)
1つの巨大なテーブルを作るのではなく、後続エージェントが分析の目的に応じて使い分けられるよう、以下の**ビジネス視点ごとに最適化されたミクロデータのセクション**を生成してください。
* **売上・商品分析用ミクロデータ**:
`order_items` を中心に、`products`(カテゴリ、ブランド、コストなど)と `orders`(ステータス、日時)を結合。後続が「カテゴリ別の利益率」「ブランド別の売上トレンド」などを算出するための基盤。
* **顧客プロファイリング用ミクロデータ**:
`users` を中心に、顧客の属性(年齢、性別、地域、トラフィックソース)と、初回購入日やLTV計算の基になるようなオーダー履歴情報を結合。後続が「年代別の購買行動」「ロイヤルカスタマーの特定」を行うための基盤。
* **物流・オペレーション用ミクロデータ**:
`orders` や `order_items` の各種タイムスタンプ(作成、出荷、配送、返品)を中心に、`distribution_centers` や `users` の位置情報を結合。後続が「配送センター別のリードタイム」「地域別の遅延率・キャンセル率」を分析するための基盤。
* **ユーザー行動(Webイベント)ミクロデータ**:
`events` を中心に、セッション情報、ブラウザ/OS情報、トラフィックソースを結合。後続が「コンバージョンファネルの分析」「離脱ポイントの特定」を行うための基盤。
## データ取得方法
* `bigquery_toolset()` を用いて以下の情報を参照。
* project id: `bigquery-public-data` / dataset: `thelook_ecommerce`
* BigQuery Job は project id `{PROJECT_ID}` で実行。
## 出力要件
* 分析のヒント: データに特筆すべき特徴(特定の月の返品率のスパイクなど)がある場合は詳細に記載。
* ナレッジベースのマークダウンテキストのみを出力すること。
''',
generate_content_config=GenerateContentConfig(
temperature=0.2,
),
tools=[bigquery_toolset],
)
adk_app = AdkApp(
agent=root_agent,
app_name='knowledge_base_generator_app',
)
このエージェントを利用して、データ・ナレッジベースを作成します。次のコードを実行すると、先ほど紹介したマークダウンテキストのデータ・ナレッジベースが得られます。
chat_client = ChatClient(adk_app)
query = '''
利用可能なデータを確認した上でナレッジベースを作成。
'''
response = await chat_client.async_stream_query(query)
display(Markdown(response))
得れれたデータ・ナレッジベースをファイルに保存しておきます。実際の運用では、このファイルの内容をデータアナリストが確認して、必要に応じて加筆・修正します。
filename = 'knowledge_base_theLook-eCommerce.md'
with open(filename, 'w') as f:
f.write(response)
データ分析エージェントの定義
そして次は、生成したデータ・ナレッジベースを用いてビジネス分析を行うデータ分析エージェントの定義です。
root_agent = LlmAgent(
name='thelook_ecommerce_analyst',
model='gemini-2.5-pro',
instruction=f'''
あなたは The Look eCommerce 専任の**シニア・ビジネスコンサルタント**です。
提供されたナレッジベース(KB)には、事前集計されていない「リッチな次元が結合された明細データ(ミクロデータ)」を取得するためのベース SQL がビジネス視点ごとに定義されています。
あなたはこの KB を「信頼できるデータ基盤(セマンティックレイヤー)」として活用し、ユーザーの課題に対してデータ駆動型の戦略的な分析を提供します。
ユーザーははじめにナレッジベースを提供するので、受け取ったナレッジベースの簡単なサマリーを応答してください。
## 1. 分析の基本サイクル(トップダウン・アプローチ)
KB のデータは明細レベルであるため、そのまま全件取得するのではなく、**必ずマクロな集計から入り、徐々にミクロな要因へとドリルダウンする**アプローチを取ってください。
1. **KBの読み込み**: 課題解決に直結するミクロデータのセクション(ベース SQL)を特定します。
2. **実行計画の明示(必須)**: ツールを実行する**前**に、必ず後述の【実行計画フォーマット】を用いて、これから行う分析のアプローチをユーザーに宣言してください。(宣言後、ユーザーの返答を待たずに自律的にSQLを実行して構いません)
3. **動的な集計とドリルダウンの実行**:
* **マクロ把握**: 最初は KB のベース SQL をサブクエリや CTE(WITH句)として扱い、外側で `GROUP BY`(月別、カテゴリ別など)を行って全体トレンドを把握します。
* **メゾ・ミクロへの深掘り**: 全体像から特異点(急な売上低下、特定の異常値など)を見つけたら、ベース SQL に `WHERE` 句を追加して対象を絞り込み、さらに詳細な粒度で再集計、あるいは少量の明細データをサンプリング取得して要因を特定します。
4. **仮説と検証**: 分析結果から異常値や傾向を見つけたら、「なぜそうなったか」の仮説を立て、別の切り口での集計や別のベース SQL を用いてその仮説を裏付けます。
## 2. 実行計画フォーマット(ツール実行前の絶対ルール)
データ取得ツールを呼び出す際は、その直前に**必ず**以下のマークダウン形式で計画を出力してください。この出力を省略することは厳禁です。
```markdown
### 分析アプローチ
* **検証する仮説**: [データから何を明らかにするか]
* **使用するKBセクション**: [ベースとするKBのセクション名]
* **データ抽出・集計方針**: [どのような期間・条件で絞り込み、どのような粒度で集計するか(SQLそのものの提示は不要)]
```
*(※このブロックを出力した直後に、自律的にツールを実行してください)*
## 3. SQL 実行の権限と制約
* **高度なクエリ操作の許可**: KB に記載されたベース SQL はあくまで「素材」です。あなたの裁量でベース SQL を CTE で囲み、集計関数(SUM, AVG, COUNT等)、ウィンドウ関数、時間粒度の変更(DATE_TRUNC)、フィルタリング(WHERE, HAVING)を自由かつ積極的に適用してください。
* **新規クエリの制限**: KB のベース SQL に含まれない「テーブルの結合方法」や、複雑な新ロジックが必要な場合に限り、SQL を提示してユーザーの許可を得てください。
* **生データの大量取得禁止**: 意味のない「明細データの全件取得(1,000件など)」は厳禁です。明細を取得する場合は、必ず事前の集計分析に基づき、特定の期間やユーザー群に `WHERE` で強力に絞り込んだ上で、原因究明に必要な範囲(数十件程度)に留めてください。
## 4. 分析・レポートの品質基準
* **事実と解釈の厳密な分離**:
* **【事実】**: 実行したSQLの集計結果やグラフから読み取れる客観的な数値・傾向。
* **【解釈】**: コンサルタントとしての洞察、背景の推測、および推奨アクション。
* **「想像」の排除**: 取得したデータのみを根拠とします。不足しているデータがある場合は「不明」と明記し、必要であればそのデータを取得するための提案を行ってください。
* **可視化の優先**: 時系列推移、構成比、相関関係など、比較が必要な箇所では必ず `chart_agent()` を使用して視覚化してください。
## 5. ツール利用・出力形式
* **データ取得**: `bigquery_toolset()` を使用。
* Project: `bigquery-public-data` / Dataset: `thelook_ecommerce` / Job Project: `{PROJECT_ID}`
* データ取得後に「データをありがとうございます」等の挨拶は不要です。即座に分析結果の報告に移行してください。
## グラフの表示方法
* `chart_agent()` はグラフのタイトルとファイル名を返却します。ファイル名の形式は `yyyymmdd-HHMMSS.png` です。
* グラフを表示する際は、以下の形式の行を挿入します。フロントエンドによって、この部分に実際のグラフが描画されます。前後の空行は必ず必要です。
```markdown

```
''',
tools=[
bigquery_toolset,
AgentTool(agent=chart_agent)
],
before_model_callback=callback_remove_base64,
after_model_callback=callback_load_images,
generate_content_config=GenerateContentConfig(
temperature=0.2,
),
)
def artifact_builder():
return GcsArtifactService(bucket_name=BUCKET_NAME)
adk_app = AdkApp(
agent=root_agent,
app_name='thelook_ecommerce_analyst_app',
artifact_service_builder=artifact_builder, # Save artifacts in the GCS Bucket.
)
このエージェントを使用する際は、はじめに、作成済みのデータ・ナレッジベースを受け渡します。
filename = 'knowledge_base_theLook-eCommerce.md'
with open(filename, 'r') as f:
content = f.read()
chat_client = ChatClient(adk_app)
query = f'''
ナレッジベースを提供します。まずはこの内容を受け取ってください。分析は不要です。
{content}
'''
response = await chat_client.async_stream_query(query)
display(Markdown(response))
出力結果

この後は、エージェントと対話的に分析を進めます。
query = '''
直近1年間のビジネス状況を確認。
'''
response = await chat_client.async_stream_query(query)
display(Markdown(response))
出力結果

出力結果の冒頭にある「分析アプローチ」をみると、先に「データ・ナレッジベースの例(セクション1)」として示した部分に記載のベース SQL を利用して、売上と利益の月次データを取得していることがわかります。データ抽出や集計方法も記載されているので、ベース SQL で得れらるデータをどのように集計しているかも確認できます。
この後は、エージェントの提案に従って分析を進めていきますが、ベース SQL だけでは分析できず、より複雑な SQL の実行が必要な場合は、次のような応答が得られます。
query = '''
2026年の冬物カテゴリへの流入をユーザーの行動ログから分析
'''
response = await chat_client.async_stream_query(query)
display(Markdown(response))
出力結果

ベース SQL とは異なる方法なので、提案された SQL の妥当性には注意が必要です。データアナリストに確認した上で処理を進めるべきですが、具体的な SQL の内容と処理ステップが記述されているので、SQL が理解できるアナリストであれば容易に確認できるでしょう。また、提案された SQL による分析の有用性が確認できた場合は、これをデータ・ナレッジベースの新たなセクションに加えるという運用も考えられます。
ここから先の実行例は省略しますが、エージェントからの提案に従って分析を進めていき、最後に提言レポートの作成を依頼すると、先に示した「AI エージェント(Gemini 2.5 Pro)による分析例」のようなレポートが得られます。
query = '''
以上の分析をまとめて提言レポートを作成。重要ポイントはグラフで可視化。
'''
response = await chat_client.async_stream_query(query)
display(Markdown(response))
まとめ
大規模言語モデル(LLM)の NL2SQL 機能は飛躍的なスピードで向上しており、AI を活用したデータ分析の可能性は広がり続けています。その進化の恩恵を受けつつ、誤った SQL によるハルシネーションのリスクを確実に防ぎ、信頼できる分析を実施するには、この記事で説明した「データ・ナレッジベース」を間に挟むアプローチが、現実的な解決策になるでしょう。
また、セキュリティや社内ポリシーの制約で、「AI エージェントにデータベースへの直接のアクセス権限を付与できない」という環境もあるかもしれません。そのような場合は、ベース SQL を実行して得られる各テーブルの主要なサマリーデータ(CSV データなど)を、そのままの形でデータ・ナレッジベースのテキストに含めてしまう方法もあります。
この場合、AI エージェントはデータベースの実行環境にアクセスすることなく、プロンプトとして渡されたデータ・ナレッジベース内のテキストデータだけを読み込んで、自律的に推論や分析を進めることができます。ビジネス視点での分析シナリオや定点観測の項目がある程度「定型化」しているプロジェクトであれば、動的な SQL 実行環境を準備しなくても、十分に実用的な分析結果が得られます。
NL2SQL の限界をプロンプトの調整だけで乗り越えようとするのではなく、「データ・ナレッジベース」のように、セマンティックレイヤーとしての工夫を取り入れることで、LLM の推論能力を確実に引き出すことができます。さらにまた、将来、データ・ナレッジベースに記載された「ベース SQL」だけに頼らない、より動的なデータ分析エージェントを構築する際には、このアプローチで分析した結果を「信頼できるベースライン」、すなわち、より高度なデータ分析エージェントの行動を評価する際のテストデータとして利用することも可能になります。
世の中では、「AI エージェントの活用に適したデータベース環境の構築が必要」という話を耳にすることもありますが、AI エージェントの中核である LLM にとっては、今回説明したデータ・ナレッジベースのようなテキストデータが、最適な入力データ形式であることは間違いありません。AI エージェントによるデータ活用の第一歩としてぜひ活用してください。
Discussion