SQL ServerでJSON_VALUEとOPENJSONを使い分ける基準|仕様差・実装例・注意点

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_VALUEOPENJSON
返すもの1つのスカラー値行・列の集合
向く用途WHERE、ORDER BY、SELECTで単一項目を使う配列展開、明細化、インポート、集計
長い文字列4,000文字超に弱いnvarchar(max)で扱える
オブジェクト・配列不向き。必要ならJSON_QUERYWITHや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)

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次