SQL ServerでJSON_VALUEとOPENJSONのどちらを使うか迷ったら、まず「欲しい結果が1つの値なのか、表形式の結果なのか」で分けるのが最短です。単一のスカラー値を抜きたいならJSON_VALUE、配列や複数項目を行と列に展開したいならOPENJSON、オブジェクトや配列をJSONのまま返したいならJSON_QUERYが基本です。 (Microsoft Learn)
実務で詰まりやすいのは、JSON_VALUEで配列まで無理に読もうとしたり、逆にOPENJSONで単純な条件検索まで複雑にしてしまったりするケースです。さらにJSON_VALUEは通常nvarchar(4000)を返すため長文には向かず、OPENJSONは互換性レベル130以上でないと使えません。最初に境界線を理解しておくと、クエリ設計も性能改善もかなり楽になります。 (Microsoft Learn)
SQL ServerでJSON_VALUEとOPENJSONを使い分ける結論
| 判断軸 | JSON_VALUE | OPENJSON |
|---|---|---|
| 返すもの | 1つのスカラー値 | 行・列の集合 |
| 向く用途 | WHERE、ORDER BY、SELECTで単一項目を使う | 配列展開、明細化、インポート、集計 |
| 長い文字列 | 4,000文字超に弱い | nvarchar(max)で扱える |
| オブジェクト・配列 | 不向き。必要ならJSON_QUERY | WITHやAS JSONで扱える |
| 実装の重さ | 軽く始めやすい | スキーマ設計を意識しやすい |
| 代表的な注意点 | laxだと欠落をNULLで飲み込みやすい | 互換性レベル130以上が必要 |
要するに、JSON_VALUEは「JSONから1つの値を抜く道具」、OPENJSONは「JSONをSQLの表に変える道具」と覚えると判断しやすくなります。 (Microsoft Learn)
SQL ServerでJSON_VALUEが向くケース
1つのプロパティを条件や表示に使うとき
JSON_VALUEが最も向くのは、JSON内の1項目だけを取り出して検索条件や表示列に使う場面です。たとえばstatus、customerId、priority、tenantCodeのように「1レコードにつき1つの値」が欲しいクエリなら、まずJSON_VALUEを検討して問題ありません。Microsoft Learnでも、JSON_VALUEはスカラー値の抽出用として位置づけられ、WHEREやORDER BYに使う例が示されています。 (Microsoft Learn)
SELECT
OrderId,
JSON_VALUE(Payload, '$.status') AS Status,
JSON_VALUE(Payload, '$.customer.id') AS CustomerId
FROM dbo.Orders
WHERE JSON_VALUE(Payload, '$.status') = N'shipped'
ORDER BY JSON_VALUE(Payload, '$.customer.id');
この書き方が強いのは、JSON列を完全に分解しなくても、必要な項目だけをピンポイントで使える点です。JSONを補助データとして持っていて、検索や表示に必要な値が限られているなら、最初からOPENJSONで行展開するより素直に書けます。 (Microsoft Learn)
頻繁に使う値は計算列とインデックスまで考える
同じJSONプロパティで何度も絞り込みや並べ替えをするなら、JSON_VALUEの式を計算列として公開し、その列にインデックスを張る設計が有効です。Microsoftのドキュメントでも、JSON_VALUE式の結果を返す仮想列を作り、その列に通常のインデックスを作る方法が案内されています。しかも、クエリ側を別の形に書き換えなくても、同じ式なら最適化に乗せやすくなります。 (Microsoft Learn)
ALTER TABLE dbo.Orders
ADD Status AS CAST(JSON_VALUE(Payload, '$.status') AS nvarchar(20));
CREATE INDEX IX_Orders_Status
ON dbo.Orders(Status);
ここでの実務ポイントは、文字列をそのまま広い型で公開しないことです。JSON_VALUEは最大でnvarchar(4000)相当を返せるため、文字列インデックスでは1700バイト制限に引っかかることがあります。状態コードや区分値のように長さが読める項目なら、CASTで必要最小限の型に寄せておく方が安全です。 (Microsoft Learn)
JSON_VALUEだけで押し切らない方がいい場面
JSON_VALUEは万能ではありません。配列やオブジェクトを指すパスに対しては、laxではNULL、strictではエラーになります。長文のスカラー値も4,000文字を超えると、laxではNULL、strictではエラーです。必須項目の欠落を早く見つけたいバッチやETLなら、strictパスを使って「壊れたJSON形状を静かに見逃さない」設計にしておくと事故を減らせます。 (Microsoft Learn)
たとえば、$.itemsのような配列を取りたいのにJSON_VALUEを使うのは筋が悪い選択です。その場合は、JSONのまま返したいならJSON_QUERY、明細行にしたいならOPENJSONに切り替える方が自然です。descriptionのような長文を抜きたいときも、最初からOPENJSON ... WITH (Description nvarchar(max) '$.description')で取り出した方が後戻りしません。 (Microsoft Learn)
細かい落とし穴として、JSON_VALUEの戻り値は元の式の照合順序を引き継ぎます。文字列比較やORDER BYの結果が期待とズレるときは、アプリの前提ではなく、JSON列の照合順序まで確認した方が早いです。 (Microsoft Learn)
SQL ServerでOPENJSONが向くケース
配列を明細として展開したいとき
OPENJSONが真価を発揮するのは、JSON配列を行として扱いたいときです。Microsoft Learnでも、OPENJSONはJSONをリレーショナル形式に変換し、インポートや分析、集計に使う関数として説明されています。受注明細、タグ配列、イベント配列、外部APIのレスポンス配列のようなデータは、OPENJSONで展開した方がSQLらしく扱えます。 (Microsoft Learn)
SELECT
o.OrderId,
i.ProductId,
i.Qty,
i.UnitPrice
FROM dbo.Orders AS o
CROSS APPLY OPENJSON(o.Payload, '$.items')
WITH (
ProductId int '$.productId',
Qty int '$.qty',
UnitPrice decimal(10,2) '$.unitPrice'
) AS i;
この形にしておけば、SUM(Qty)やGROUP BY ProductIdのような標準的な集計にそのまま入れます。JSONの中に配列がある時点で、発想を「値の抽出」から「表への展開」に切り替えるのがコツです。 (Microsoft Learn)
複数項目を型付きで一気に取りたいとき
1つのJSONから複数の列を取りたいなら、OPENJSONのWITH句が便利です。WITHを使わない既定のスキーマでは、key、value、typeの3列が返り、しかも返るのは最初のレベルのプロパティだけです。これは構造調査には便利ですが、本番クエリとしては扱いづらいことが多いので、構造が分かったらWITHで列名・型・パスを明示する方が読みやすくなります。 (Microsoft Learn)
-- まず構造確認
SELECT [key], value, [type]
FROM OPENJSON(@payload, '$.items');
-- 本番用は型付きで明示
SELECT *
FROM OPENJSON(@payload, '$.items')
WITH (
ProductId int '$.productId',
ProductName nvarchar(200) '$.productName',
Qty int '$.qty'
);
この使い分けはかなり重要です。開発初期は既定スキーマで中身を見る、本番ではWITHに寄せる。この流れにしておくと、あとからJSON構造が変わったときにも影響箇所を追いやすくなります。 (Microsoft Learn)
ネストしたオブジェクトや配列を残したいとき
OPENJSONは「全部を展開する」だけの関数ではありません。WITH句でAS JSONを付ければ、ネストしたオブジェクトや配列をJSON断片のまま列として受け取れます。逆にAS JSONを付けずにオブジェクトや配列を受けようとすると、laxではNULL、strictではエラーになります。しかもAS JSONを使う列はnvarchar(max)である必要があります。 (Microsoft Learn)
SELECT *
FROM OPENJSON(@payload)
WITH (
OrderNo nvarchar(30) '$.orderNo',
ShippingAddress nvarchar(max) '$.shippingAddress' AS JSON,
Items nvarchar(max) '$.items' AS JSON
);
このパターンは、最上位だけ列にしつつ、入れ子部分はあとで別のOPENJSONやJSON_QUERYに渡したいときに便利です。無理に一度で全部平らにしようとすると、かえって保守しにくくなります。 (Microsoft Learn)
OPENJSONで見落としやすい制約
OPENJSONで最初に確認したいのは互換性レベルです。OPENJSONは互換性レベル130以上でしか使えず、これを満たさないと関数自体が見つかりません。一方、他のJSON関数はすべての互換性レベルで使えます。古いDBを引き継いだときに「SQL Server 2016以降なのにOPENJSONが動かない」という場合は、バージョンではなく互換性レベルが原因のことが少なくありません。 (Microsoft Learn)
もう1つ実務でハマりやすいのが、自動マッピングです。WITH句では列名とJSONキー名を自動で対応づけられますが、この一致は大文字小文字を区別します。JSONのキーがproductIdなのに列名をProductIDにしてしまう、といった微妙なズレでも値が入らないことがあるため、本番では'$.productId'のようにcolumn_pathを明示しておく方が堅実です。 (Microsoft Learn)
JSON_VALUEとOPENJSONで迷ったときの判断手順
判断に迷うときは、次の順番で考えると整理しやすくなります。 (Microsoft Learn)
- 欲しいのは1つの値か、複数行か
1つの値ならJSON_VALUE、配列や明細行ならOPENJSONです。 - 欲しいものはスカラーか、オブジェクト・配列か
オブジェクトや配列ならJSON_VALUEではなく、JSON_QUERYかOPENJSON + AS JSONを選びます。 - その値を何度も検索・並べ替えに使うか
繰り返し使うなら、JSON_VALUE式の計算列とインデックスまで視野に入れます。 - 環境が
OPENJSONを使える状態か
古いデータベースや移行直後の環境では、互換性レベル130以上を確認してから設計します。
よくある失敗と直し方
| 失敗例 | なぜ起きるか | 直し方 |
|---|---|---|
JSON_VALUE(Payload, '$.items')で配列を取ろうとする | JSON_VALUEはスカラー向け | JSONのまま欲しいならJSON_QUERY、行展開したいならOPENJSON |
JSON_VALUEで長文descriptionがNULLになる | 4,000文字超の値は不向き | OPENJSON ... WITH (Description nvarchar(max) '$.description')に切り替える |
OPENJSON ... WITH (...)でオブジェクト列だけNULLになる | AS JSONなしでオブジェクトや配列を受けている | nvarchar(max) AS JSONで受ける |
OPENJSONの列に一部だけ値が入らない | 列名とキー名の大文字小文字不一致、またはパス不一致 | column_pathを明示する |
OPENJSONが「見つからない」 | 互換性レベル130未満 | 互換性レベルを確認する |
| キー名にスペースやドットがあり取得できない | JSONパスの書き方が違う | $."first name"や$."customer.name"のように引用する |
| 同名キーを全部取りたいのに1件しか返らない | JSON_VALUEは最初の一致しか返さない | OPENJSONで展開してWHERE [key] = 'name'の形にする |
この表のどれかに当てはまるなら、関数選びを間違えているというより、「返したい形」と「関数の役割」がズレています。仕様で押し切ろうとせず、返り値の形から選び直す方が早いです。 (Microsoft Learn)
JSON_VALUEとOPENJSONだけで解決しないときの代替策
オブジェクトや配列をそのまま返すならJSON_QUERY
JSONの一部をそのままAPIレスポンスや別処理に渡したいなら、JSON_QUERYの方が自然です。JSON_QUERYはオブジェクトや配列の抽出向けで、スカラー値ではなくJSON断片を返します。addressやtagsをそのまま返したいケースで、JSON_VALUEを使ってNULLに悩む必要はありません。 (Microsoft Learn)
保存時に妥当性を確保するならISJSON
JSON列に不正な文字列が混ざる運用だと、抽出時にトラブルが表面化しやすくなります。保存時に最低限の品質を担保したいなら、ISJSONを使ったCHECK制約を入れておくと安心です。Microsoft Learnでも、ISJSONは有効なJSONかどうかの判定関数として案内されています。 (Microsoft Learn)
ALTER TABLE dbo.Events
ADD CONSTRAINT CK_Events_Payload_IsJson
CHECK (ISJSON(Payload) = 1);
集計や結合が多いなら、展開用テーブルを持つ
OPENJSONはインポートやリレーショナル変換に向き、JSON_VALUEはフィルタや並べ替えの最適化に向きます。逆に言うと、配列の集計・結合を毎日大量に回すレポート系ワークロードでは、都度JSONを解析するより、取り込み時に子テーブルへ展開しておく方が運用しやすいことが多いです。受注明細やイベントログのように「配列の中身が主役」のデータは、この発想に切り替えた方が後々楽になります。 (Microsoft Learn)
バージョン差のあるサンプルはそのままコピペしない
最近の公式ドキュメントでは、SQL Server 2025以降の構文としてJSON_VALUE(... RETURNING data_type)が紹介されていますが、SQL Server 2022以前ではこの構文は使えません。社内に複数バージョンが混在しているなら、ブログやサンプルコードをそのまま流用せず、まず対象環境の構文差を確認した方が安全です。 (Microsoft Learn)
まとめ
JSON_VALUEとOPENJSONの使い分けは、「JSONかどうか」ではなく「どんな形で結果が欲しいか」で決めるのが本質です。1つの値を抜くならJSON_VALUE、配列を行に展開したり複数列を型付きで取りたいならOPENJSON、オブジェクトや配列をそのまま返すならJSON_QUERYを選べば、大きく外しません。検索で多用する値は計算列とインデックスまで考え、配列中心の分析は展開用テーブルも検討すると、後からの改修コストを抑えやすくなります。 (Microsoft Learn)
次にやるべきことはシンプルです。今あるクエリを「単一値の抽出」「配列の展開」「JSON断片の返却」の3つに分けて見直してください。そのうえで、単一値はJSON_VALUE、配列はOPENJSON、オブジェクトや配列の返却はJSON_QUERYに寄せるだけで、クエリはかなり読みやすく、壊れにくくなります。 (Microsoft Learn)

コメント