1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

SQL Server × JSON 入門

Last updated at Posted at 2025-09-14

〜JOINとCROSS APPLYで広がるデータの世界〜

今回使用するテーブル

CREATE TABLE Orders (
    OrderId INT PRIMARY KEY,
    CustomerInfo JSON NOT NULL,
    OrderDetails JSON NOT NULL,
    CreatedAt DATETIME2 NOT NULL,
    UpdatedAt DATETIME2 NOT NULL,
    IsActive BIT NOT NULL,
    OrderStatus NVARCHAR(50) NOT NULL,
    TotalAmount DECIMAL(18,2) NOT NULL
);

CustomerInfo

Ex.

{
  "customerId": 1,
  "name": "佐藤 千代",
  "email": "mikako17@gmail.com",
  "address": {
    "city": "立川市",
    "prefecture": "山形県",
    "postalCode": "588-1891",
    "street": "280 松本 Street"
  },
  "phone": "080-6685-8712",
  "registeredDate": "2023-03-08"
}


OrderDetails


{
  "orderId": 1,
  "orderDate": "2025-04-26",
  "items": [
    {
      "productId": 6895,
      "productName": "溝",
      "category": "書籍",
      "quantity": 6,
      "unitPrice": 4242.76
    },
    {
      "productId": 8825,
      "productName": "シュガー",
      "category": "衣類",
      "quantity": 3,
      "unitPrice": 14094.12
    },
    {
      "productId": 3919,
      "productName": "チーズ",
      "category": "家具",
      "quantity": 8,
      "unitPrice": 2827.21
    },
    {
      "productId": 6267,
      "productName": "狐",
      "category": "食品",
      "quantity": 2,
      "unitPrice": 6961.97
    },
    {
      "productId": 6463,
      "productName": "ヘア",
      "category": "書籍",
      "quantity": 10,
      "unitPrice": 18969.8
    },
    {
      "productId": 1818,
      "productName": "デッド",
      "category": "電化製品",
      "quantity": 10,
      "unitPrice": 8390.16
    },
    {
      "productId": 7416,
      "productName": "バナー",
      "category": "食品",
      "quantity": 10,
      "unitPrice": 3360.59
    }
  ],
  "payment": {
    "method": "代引き",
    "status": "失敗",
    "amount": 411486.04
  },
  "delivery": {
    "carrier": "佐川",
    "trackingNo": "e6f92c0d-c0fc-4da1-8333-c1122bed6306",
    "expectedDate": "2025-03-28"
  }
}


テーブル生成のSQL

クエリの例(category = 書籍)

SELECT 
    o.OrderId,
    JSON_VALUE(o.CustomerInfo, '$.name') AS CustomerName,
    i.productId,
    i.productName,
    i.category,
    i.quantity,
    i.unitPrice
FROM Orders o
CROSS APPLY OPENJSON(o.OrderDetails, '$.items')
WITH (
    productId INT '$.productId',
    productName NVARCHAR(100) '$.productName',
    category NVARCHAR(50) '$.category',
    quantity INT '$.quantity',
    unitPrice DECIMAL(18,2) '$.unitPrice'
) AS i
WHERE i.category = N'書籍';

解説

Orders テーブルには category という物理列は存在しない
→ JSON の中にあるだけ。

OPENJSON(... WITH (...)) で JSON のキーを SQL の列に展開する必要がある。
ここで category NVARCHAR(50) '$.category' と指定すると、
JSON 内の "category": "書籍" を i.category という列で参照できるようになる。

その上で WHERE i.category = N'書籍' という条件が書ける。

OPENJSON 関数

1. そもそも JSON とは?

  • JSON(JavaScript Object Notation)は、データを キーと値 のペアで表す書き方。
  • 例えば注文データを JSON で書くとこんな感じ:
{
  "orderId": 1,
  "items": [
    {"productId": 101, "name": "本", "price": 1200},
    {"productId": 102, "name": "パン", "price": 300}
  ]
}

ここで問題になるのは、SQL Server の1つのセルに JSON をそのまま入れたら、普通の列みたいに検索できないこと。


2. OPENJSON の役割

OPENJSONJSON をテーブルみたいに変換する関数
JSON の配列やオブジェクトを 行と列に展開できる。


3. シンプルな使い方

DECLARE @json NVARCHAR(MAX) = N'[
  {"productId":101,"name":"本","price":1200},
  {"productId":102,"name":"パン","price":300}
]';

SELECT * FROM OPENJSON(@json);

結果はこんな感じ:

key   value                          type
----  -----------------------------  ----
0     {"productId":101,"name":"本"...}   5
1     {"productId":102,"name":"パン"...} 5

つまり「配列の中身が行になって出てくる」んだね。


4. WITH 句で列にマッピング

さっきの結果はちょっと使いづらい。
そこで WITH 句 を使うと、欲しいカラムに変換できる。

SELECT *
FROM OPENJSON(@json)
WITH (
  productId INT '$.productId',
  name NVARCHAR(50) '$.name',
  price DECIMAL(10,2) '$.price'
);

結果:

productId   name   price
---------   ----   -----
101         本     1200
102         パン   300

5. テーブルと組み合わせる(よく使う形)

Orders テーブルに JSON カラム OrderDetails がある場合:

SELECT o.OrderId, i.productId, i.name, i.price
FROM Orders o
CROSS APPLY OPENJSON(o.OrderDetails, '$.items')
WITH (
  productId INT '$.productId',
  name NVARCHAR(50) '$.name',
  price DECIMAL(10,2) '$.price'
) AS i;

これで「注文1件 → 商品n件」を展開して、明細を取れる。


6. 覚えておきたいポイント

  • OPENJSON は JSON を行に変換してくれる
  • WITH 句を使うと きれいな列形式 にできる
  • よく一緒に使うのは CROSS APPLY(注文ごとに JSON を展開)
  • JSON を まるで別テーブルのように扱える のが強み

CROSS APPLY

JOIN と CROSS APPLY の違い

🟦 普通の JOIN

たとえば「注文テーブル」と「顧客テーブル」があったとする。
Order.CustomerId = Customer.Id でつなぐ場合:

Orders テーブル             Customers テーブル
+---------+----------+      +----+--------+
| OrderId | CustId   |      | Id | Name   |
+---------+----------+      +----+--------+
|   1     |   101    | ---> |101 | 佐藤   |
|   2     |   102    | ---> |102 | 鈴木   |
|   3     |   101    | ---> |101 | 佐藤   |
+---------+----------+      +----+--------+

JOIN は 「2つのテーブルをキーでくっつける」 もの。
結果は「横に情報が増える」イメージ。


CROSS APPLY + OPENJSON

次に「注文テーブル」の中に JSON で商品配列 が入っているケース。

Orders テーブル
+---------+---------------------------------+
| OrderId | OrderDetails (JSON)             |
+---------+---------------------------------+
|   1     | { items: [A, B, C] }            |
|   2     | { items: [D, E] }               |
+---------+---------------------------------+

このままだと「注文1は商品3つ」「注文2は商品2つ」という情報が ひとつのセル にまとまっていて使いにくい。


CROSS APPLY を使うと

SELECT o.OrderId, i.product
FROM Orders o
CROSS APPLY OPENJSON(o.OrderDetails, '$.items') WITH (product NVARCHAR(50) '$')

結果はこうなる:

+---------+---------+
| OrderId | product |
+---------+---------+
|   1     |   A     |
|   1     |   B     |
|   1     |   C     |
|   2     |   D     |
|   2     |   E     |
+---------+---------+

1行の中に入っていた配列を、縦に展開してくれる のが CROSS APPLY。
つまり「JSON を子テーブルみたいに JOIN してる」感覚だね。


まとめ

  • JOIN → テーブルとテーブルを「キー」でくっつける
  • CROSS APPLY → 行ごとに「関数(OPENJSONなど)」を実行して、結果を展開してくっつける

JOIN は横に増やす、CROSS APPLY は縦に展開する。

1
0
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
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?