Web APIからのデータ取得やアプリケーションのログなど、現代のシステム開発においてJSON(JavaScript Object Notation)形式のデータは避けて通れません。柔軟性が高く便利な一方、リレーショナルデータベースに慣れ親しんだエンジニアにとって、この「スキーマレスな非構造化データ」をどのようにSQLで集計・分析するかは大きな課題です。特に、データベーススペシャリスト試験や応用情報技術者試験の午後問題では、複雑なJSONデータを正確にテーブル形式へ変換する能力が問われます。
「JSON_TABLEとOPENJSON、名前は似ているけれど、どちらをいつ使えばいいのか分からない」
「配列の中にオブジェクトが入れ子になった、深い階層のデータをどうやって行と列に展開すれば…?」
もし、あなたがこのような疑問を抱えているなら、この記事がその解決策を示します。この記事では、Oracle Database、MySQL、SQL Serverといった主要なデータベースでサポートされているJSONクエリ関数を徹底解説。特に、JSONをリレーショナルなテーブル形式に変換する強力な武器となるJSON_TABLE(Oracle, MySQL)とOPENJSON(SQL Server)について、その基本的な構文から実践的な活用シナリオまでを、豊富な図解とコード例を交えて体系的に学びます。
本記事を読み終える頃には、あなたは非構造化データであるJSONを自在にSQLで操り、必要な情報を的確に抽出・分析するスキルを身につけているでしょう。
目次
SQLでJSONを扱う時代へ│データベースにおける非構造化データの重要性
かつてリレーショナルデータベース(RDB)が扱うデータは、行と列によって厳密に構造が定義された「構造化データ」が中心でした。しかし、Web APIやマイクロサービスアーキテクチャが普及した現代では、より柔軟なデータ構造を持つJSONが、システム間でデータを交換するための標準形式(デファクトスタンダード)となっています。
例えば、Webアプリケーションのバックエンドが、ユーザー情報を以下のようなJSONで受け取るとします。
{
"userId": "U001",
"name": "Taro Yamada",
"registeredDate": "2023-10-27",
"interests": ["SQL", "Python", "Cloud"]
}
このデータをRDBに格納する場合、従来はアプリケーション側でJSONを解析(パース)し、「userId」「name」といった各要素をテーブルの各列に分解してINSERTする必要がありました。
この流れを受け、Oracle Database、MySQL、SQL Server、PostgreSQLといった主要なRDBは、JSONデータ型やJSONを直接操作するための専用関数を標準でサポートするようになりました。これにより、JSONデータを分解せずにそのままテーブルの一つのカラムに格納し、SQLを用いてデータベースエンジン上で直接フィルタリング、抽出、集計といった処理を高速に実行できるようになったのです。
この技術は、アプリケーション側の処理負荷を軽減し、データの一貫性を保ちやすくするだけでなく、データベーススペシャリスト試験のような高度な資格試験においても、現代的なデータ処理能力を測るための重要なテーマとして扱われています。SQLでJSONを制することは、もはや全てのデータベースエンジニアにとって必須のスキルと言えるでしょう。
JSONデータへの第一歩│JSONパス式を使った値の抽出(JSON_VALUE/JSON_QUERY)
JSONデータをSQLで本格的に扱う前に、まず「JSON内のどのデータが欲しいか」をデータベースに伝えるための共通言語、「JSONパス式」を理解する必要があります。これは、JSONの階層構造を辿って目的のデータに到達するための住所(パス)のようなものです。
$:ルート(最上位)のオブジェクトまたは配列を示します。.(ドット):オブジェクトのキー(プロパティ名)を指定します。[n]:配列のn番目の要素を指定します(0から始まります)。
例えば、{"user": {"name": "Taro"}} というJSONデータから "Taro" という値を取得するパス式は $.user.name となります。
このパス式を利用して値を抽出する最も基本的な関数が JSON_VALUE と JSON_QUERY です。
JSON_VALUE:単一の値(スカラー値)を取り出す
JSON_VALUE は、パス式で指定した場所から単一の値(スカラー値:文字列、数値、true/falseなど)を一つだけ抽出する関数です。
構文例 (MySQL/SQL Server/Oracle):
JSON_VALUE(json_column, '$.path')
使用例:
以下のデータが users テーブルの json_data カラムに格納されているとします。
{
"userId": "U001",
"name": "Taro Yamada",
"age": 30,
"isPremium": true,
"interests": ["SQL", "Python", "Cloud"]
}
ここからユーザー名を取得するには、次のようにSQLを書きます。
SELECT
JSON_VALUE(json_data, '$.name') AS user_name
FROM
users;
実行結果:
| user_name |
|---|
| Taro Yamada |
重要: JSON_VALUE はオブジェクトや配列そのものを抽出することはできません。$.interests のような配列を指定した場合、エラーまたはNULLが返ります。
JSON_QUERY:オブジェクトや配列を取り出す
一方、JSON_QUERY は、パス式で指定した場所からオブジェクトまたは配列を、JSON形式のテキストとして抽出する関数です。
構文例 (MySQL/SQL Server/Oracle):
JSON_QUERY(json_column, '$.path')
使用例:
同じデータから、興味(interests)の配列全体を取得してみましょう。
SELECT
JSON_QUERY(json_data, '$.interests') AS user_interests
FROM
users;
実行結果:
| user_interests |
|---|
| ["SQL", "Python", "Cloud"] |
このように、JSON_VALUE と JSON_QUERY を使い分けることで、JSONデータからピンポイントで情報を抜き出すことが可能になります。
| 関数 | 目的 | 抽出対象 | 戻り値の例 |
|---|---|---|---|
JSON_VALUE |
単一の値 を抽出 | 文字列, 数値, 真偽値 | 'Taro Yamada', 30 |
JSON_QUERY |
構造 を抽出 | オブジェクト, 配列 | '{"key": "value"}', '["a", "b"]' |
この基本をしっかり押さえることが、次から解説する JSON_TABLE や OPENJSON といった、より高度なテーブル変換処理を理解する鍵となります。
本命登場:JSON_TABLE関数で行と列に一括変換する (Oracle/MySQL)
JSON_VALUE を使えば特定の値を取り出せることが分かりました。しかし、もしJSON配列の中にオブジェクトが100個あった場合、100個のオブジェクトからそれぞれ name を取り出す、といった処理は非常に面倒です。このような「JSON配列をまとめて行と列に展開したい」というニーズに応えるのが JSON_TABLE 関数です。
JSON_TABLE は、JSON配列の各要素を行に、オブジェクト内の各キーと値のペアを列に変換し、仮想的なテーブルを生成する強力な機能です。主にOracle DatabaseとMySQL 8.0以降で利用できます。
JSON_TABLEの基本構文
少し複雑に見えますが、構造を分解すれば理解は難しくありません。
JSON_TABLE(
json_data, -- 対象のJSONデータが含まれる列
'$.array_path[*]' -- ①行に展開したい配列のパス
COLUMNS ( -- ②列の定義
column_name1 datatype PATH '$.key1',
column_name2 datatype PATH '$.key2'
-- 必要な分だけ列を定義
)
)
- ① 行に展開する配列の指定:
'$'から始まるパスで、テーブルの行にしたいJSON配列を指定します。[*]は「配列のすべての要素」を意味するワイルドカードです。 - ② 列の定義 (COLUMNS句):
COLUMNS句の中で、生成したいテーブルの列名、データ型、そしてJSONオブジェクト内のどのキーから値を取得するか(PATH '$.key')を定義します。
使用例:商品リストJSONをテーブル化する
ECサイトの注文明細を表す、以下のようなJSONデータを持つ orders テーブルを考えます。
orders テーブル
| order_id | items_json |
|---|---|
| 101 | [{"productId": "A-01", "price": 1500, "qty": 1}, {"productId": "B-02", "price": 300, "qty": 5}] |
| 102 | [{"productId": "C-03", "price": 2000, "qty": 2}] |
この items_json 列から、注文IDごとに行を分割した商品明細テーブルを作成してみましょう。
SELECT
o.order_id,
jt.product_id,
jt.price,
jt.quantity
FROM
orders o, -- 元のテーブル
JSON_TABLE(
o.items_json,
'$[*]' -- ルートが配列なので単純に '$[*]'
COLUMNS (
product_id VARCHAR(10) PATH '$.productId',
price INT PATH '$.price',
quantity INT PATH '$.qty'
)
) AS jt; -- JSON_TABLEの結果に別名(jt)を付ける
実行結果:
| order_id | product_id | price | quantity |
|---|---|---|---|
| 101 | A-01 | 1500 | 1 |
| 101 | B-02 | 300 | 5 |
| 102 | C-03 | 2000 | 2 |
このように、JSON_TABLE を FROM 句で元のテーブルと結合(, や CROSS JOIN を使用)することで、まるで最初から正規化されたテーブルが存在したかのように、JSONデータをSQLで扱うことができるのです。これにより、商品ごとの売上集計や在庫分析などが格段に容易になります。
SQL Serverの強力な武器:OPENJSON関数によるテーブル展開
OracleやMySQLにJSON_TABLEがあるように、SQL ServerにはOPENJSONという強力なテーブル値関数が用意されています。OPENJSONは、JSONテキストを解析し、その要素を行と列のセットとして返す機能です。
JSON_TABLEがFROM句でカンマやJOINを使って結合するのに対し、OPENJSONはCROSS APPLYまたはOUTER APPLY演算子と共に使用するのが特徴です。これは、OPENJSONがテーブルの各行に対して個別に実行されるテーブル値関数であるためです。
OPENJSONの基本構文(WITH句を使用)
JSON_TABLEと同様の目的で使う場合、WITH句で出力するテーブルのスキーマ(列構成)を明示的に定義します。構文はJSON_TABLEのCOLUMNS句と非常によく似ています。
SELECT ...
FROM
source_table
CROSS APPLY OPENJSON(json_column, '$.array_path')
WITH (
column_name1 datatype '$.key1',
column_name2 datatype '$.key2'
-- 必要な分だけ列を定義
) AS alias_name
CROSS APPLY OPENJSON(...): 元のテーブルの各行のjson_columnに対してOPENJSONを適用します。WITH (...): 生成されるテーブルの列名、データ型、そして値の取得元となるJSONパス式を定義します。
使用例:JSON_TABLEと同じ例をOPENJSONで書く
JSON_TABLEのセクションで使用したECサイトの注文明細データを、今度はSQL ServerのOPENJSONでテーブル化してみましょう。
orders テーブル
| order_id | items_json |
|---|---|
| 101 | [{"productId": "A-01", "price": 1500, "qty": 1}, {"productId": "B-02", "price": 300, "qty": 5}] |
| 102 | [{"productId": "C-03", "price": 2000, "qty": 2}] |
SELECT
o.order_id,
jt.product_id,
jt.price,
jt.quantity
FROM
orders o
CROSS APPLY OPENJSON(o.items_json, '$') -- 配列を直接指定
WITH (
product_id VARCHAR(10) '$.productId',
price INT '$.price',
quantity INT '$.qty'
) AS jt;
実行結果:
| order_id | product_id | price | quantity |
|---|---|---|---|
| 101 | A-01 | 1500 | 1 |
| 101 | B-02 | 300 | 5 |
| 102 | C-03 | 2000 | 2 |
ご覧の通り、JSON_TABLEと全く同じ結果が得られました。OPENJSONは、第二引数のパスで配列を指定し、WITH句でオブジェクトのどのキーを列にするかを定義する、という点でJSON_TABLEと概念的に同じ働きをします。SQL Server環境でJSONをリレーショナルに扱う際には、このOPENJSONが中核的な役割を担います。
| 項目 | JSON_TABLE (Oracle/MySQL) |
OPENJSON (SQL Server) |
|---|---|---|
| 関数タイプ | テーブル関数 | テーブル値関数 |
| 結合方法 | FROM句でカンマ、CROSS JOIN |
CROSS APPLY, OUTER APPLY |
| 列定義 | COLUMNS句 |
WITH句 |
| 役割 | JSON配列を行と列に変換 | JSON配列を行と列に変換 |
応用編:ネストしたJSON配列の展開│多対多リレーションをSQLで攻略
これまでの例は、階層が浅い比較的シンプルなJSONでした。しかし実務や試験問題では、配列の中にさらに配列が含まれる「ネストしたJSON」が登場します。これは、リレーショナルモデルにおける「多対多」の関係性を表現していることが多く、これをいかにしてSQLでフラットなテーブルに展開するかが腕の見せ所です。
課題:入れ子になった商品タグのJSON
ある注文データ(order_logテーブル)に、以下のようなJSONが格納されているとします。一つの注文に複数の商品があり、さらに一つの商品に複数のタグが付与されている状況です。
{
"orderId": "ORD001",
"customer": {"name": "Sato", "level": "Gold"},
"products": [
{
"productName": "高機能ノートPC",
"tags": ["家電", "仕事用", "高スペック"]
},
{
"productName": "ワイヤレスマウス",
"tags": ["PC周辺機器", "家電"]
}
]
}
このデータから、「注文ID、商品名、タグ」という組み合わせのリストを作成するにはどうすればよいでしょうか。
解決策:JSON_TABLE / OPENJSON の連鎖
答えは、関数を連鎖させて段階的に展開することです。
- 第一段階:
JSON_TABLE(またはOPENJSON)でproducts配列を展開し、「注文 対 商品」の関係をテーブル化します。この時点では、tags列はまだJSON配列のままです。 - 第二段階: 第一段階で生成された
tags列(JSON配列)に対して、さらにもう一度JSON_TABLE(またはOPENJSON)を適用し、「商品 対 タグ」の関係をテーブル化します。
SQLコード例 (Oracle/MySQL の JSON_TABLE):
SELECT
l.order_id,
p.product_name,
t.tag
FROM
order_log l,
-- 第一段階:products配列の展開
JSON_TABLE(l.json_data, '$.products[*]'
COLUMNS (
product_name VARCHAR(50) PATH '$.productName',
tags_json JSON PATH '$.tags' -- tagsをJSON型のまま取り出す
)
) AS p,
-- 第二段階:tags配列の展開
JSON_TABLE(p.tags_json, '$[*]'
COLUMNS (
tag VARCHAR(20) PATH '$' -- 配列の要素そのものを値とする
)
) AS t;
実行結果:
| order_id | product_name | tag |
|---|---|---|
| ORD001 | 高機能ノートPC | 家電 |
| ORD001 | 高機能ノートPC | 仕事用 |
| ORD001 | 高機能ノートPC | 高スペック |
| ORD001 | ワイヤレスマウス | PC周辺機器 |
| ORD001 | ワイヤレスマウス | 家電 |
このように関数をネスト(FROM句やAPPLYで連鎖)させることで、どれだけ深い階層のJSONデータであっても、最終的には正規化されたリレーショナルなテーブル形式に変換できます。このテクニックは、データベーススペシャリスト試験の午後問題で頻出するパターンであり、非構造化データを扱う上で極めて重要なスキルです。
実践で役立つ知識│パフォーマンス・NULL/エラーハンドリングの注意点
JSON_TABLEやOPENJSONは非常に強力ですが、実務で使いこなすにはいくつかの注意点、特にパフォーマンスとエラーハンドリングについて理解しておく必要があります。これらの知識は、安定したアプリケーションを構築するため、また高度な資格試験で減点されないためにも不可欠です。
パフォーマンスに関する考慮事項
JSON関数をWHERE句などで使用すると、テーブルのフルスキャンが発生し、パフォーマンスが著しく低下する可能性があります。
- インデックスの活用: 大量のJSONデータを扱う場合、JSONデータ内の特定キーに対してインデックスを作成することを検討しましょう。例えば、Oracleのファンクション索引やMySQLの生成列に対するインデックスなど、データベースによってはJSON内のスカラー値にインデックスを付与する機能が提供されています。これにより、特定のキーの値に基づいた検索を高速化できます。
- データ処理の分離: APIから取得した生データを格納する「ステージングテーブル」と、
JSON_TABLEなどで正規化・変換した後のデータを格納する「分析用テーブル」を分けるアプローチも有効です。夜間バッチなどで一度に変換処理を行い、日中の分析クエリは最適化された分析用テーブルに対して実行することで、パフォーマンスを安定させることができます。 - DBとアプリの役割分担: ネットワーク帯域がボトルネックになる場合、巨大なJSONをそのままアプリケーションに送るのではなく、
JSON_VALUEやJSON_QUERYを使ってDB側で必要な情報だけを抽出し、データ量を削減してから転送する方が効率的です。
NULL値とエラーのハンドリング
APIから取得するJSONデータは、必ずしも常に期待通りの形式であるとは限りません。キーが存在しなかったり、予期せぬデータ型が入っていたりする場合の挙動を制御することは非常に重要です。
- パスが存在しない場合:
JSON_TABLEのPATHで指定したキーがJSON内に存在しない場合、デフォルトではその列にはNULLが設定されます。これは多くの場合で便利な挙動ですが、SQL ServerでOPENJSONを使う際は、CROSS APPLYではなくOUTER APPLYを使うことで、JSON配列自体が空、または存在しないレコードも元の行が結果セットに残るように制御できます。 - データ型が不一致の場合:
COLUMNS句でINT型を指定した列に対応するJSONの値が"abc"のような文字列だった場合、エラーが発生します。OracleなどではON ERROR句を使い、NULL ON ERROR(エラー時にNULLを返す)やDEFAULT '値' ON ERROR(エラー時に指定したデフォルト値を返す)といった挙動を細かく制御できます。
| 状況 | デフォルトの挙動 | 対策・制御方法の例 |
|---|---|---|
| 指定したキーが存在しない | NULLが返る |
OUTER APPLYの利用 (SQL Server) / LEFT JOINの発想 |
| 配列やオブジェクトが空 | 行が生成されない | ON EMPTY句の利用 (Oracle) |
| データ型が不一致 | エラーが発生 | ON ERROR句の利用 (Oracle) / TRY_CASTの利用 (SQL Server) |
データベーススペシャリスト試験の午後問題などでは、こうした「例外ケース」をいかに考慮できているかが問われることがあります。「正常系」のデータだけでなく、JSONがnullや空配列[]だった場合にSQLがどう動くかを常に意識する癖をつけましょう。JSON関数は強力なツールですが、リレーショナルデータベースの堅牢な設計原則を代替するものではなく、両者を適切に橋渡しするための技術であることを忘れてはなりません。
まとめ:SQLによるJSON処理をマスターし、データ活用の幅を広げよう
本記事では、現代のデータベースエンジニアに必須のスキルである「SQLによるJSONデータのテーブル化」について、体系的に解説してきました。
- JSONパス式が、JSON内の特定データにアクセスするための基本言語です。
JSON_VALUEとJSON_QUERYは、それぞれ単一の値とオブジェクト/配列をピンポイントで抽出します。JSON_TABLE(Oracle/MySQL) とOPENJSON(SQL Server) は、JSON配列を行と列からなるテーブル形式に一括変換する、本テーマの主役です。- ネストした複雑なJSONは、これらの関数を連鎖させることで、どんなに深い階層でもフラットなテーブルに展開できます。
- 実務ではパフォーマンスとエラーハンドリングを考慮することで、より堅牢なシステムを構築できます。
API連携や非構造化データの活用が当たり前になった今、SQLを使ってJSONを直接扱える能力は、あなたの市場価値を大きく高める武器となります。データベーススペシャリスト試験などの資格対策としてはもちろん、日々の開発業務を効率化するためにも、ぜひ本記事で紹介した関数とテクニックを実際に手を動かして試してみてください。
最初は複雑に感じるかもしれませんが、一つ一つの構文の役割を理解すれば、必ず使いこなせるようになります。非構造化データと構造化データの垣根をSQLで自在に乗り越え、データ活用の可能性をさらに広げていきましょう。