MySQL JSON 处理学习文档
假设订单表
orderr_orderr有comment字段
原始数据
sql
-- 表结构(简化)
CREATE TABLE orderr_orderr (
id BIGINT PRIMARY KEY,
title VARCHAR(100),
monthly_rent DECIMAL(10,2),
comment JSON DEFAULT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
-- 插入数据
INSERT INTO orderr_orderr (id, title, monthly_rent, comment) VALUES
(1011695605988196352, '测试订单A', 120.00,
'{"devNum": 2, "productNum": 1, "addressInfo": {"city": "杭州市", "phone": "13800001111", "address": "西湖区文三路100号天际大厦3幢508室", "village": "", "province": "浙江省", "consignee": "张三"}, "orderrCheckId": "1011695663005564928", "wareDeliveryId": 637, "wareDeliveryStatus": 0}'),
(1011695605988196353, '测试订单B', 200.00,
'{"devNum": 3, "productNum": 2, "addressInfo": {"city": "上海市", "phone": "13900002222", "address": "浦东新区张江高科技园区博云路2号", "village": "", "province": "上海市", "consignee": "李四"}, "orderrCheckId": "1011695663005564929", "wareDeliveryId": 638, "wareDeliveryStatus": 1}'),
(1011695605988196354, '测试订单C', 80.00,
'{"devNum": 1, "productNum": 1, "addressInfo": {"city": "北京市", "phone": "13700003333", "address": "海淀区中关村大街1号海龙大厦12层", "village": "", "province": "北京市", "consignee": "王五"}, "orderrCheckId": "1011695663005564930", "wareDeliveryId": 639, "wareDeliveryStatus": 2}');
comment 字段内容示例(测试订单A):
json
{
"devNum": 2,
"productNum": 1,
"addressInfo": {
"city": "杭州市",
"phone": "13800001111",
"address": "西湖区文三路100号天际大厦3幢508室",
"village": "",
"province": "浙江省",
"consignee": "张三"
},
"orderrCheckId": "1011695663005564928",
"wareDeliveryId": 637,
"wareDeliveryStatus": 0
}
一、查询(SELECT)
1.1 基本查询 --- ->> 操作符(推荐)
sql
-- 查询一级字段
SELECT
id,
title,
comment->>'$.devNum' AS devNum,
comment->>'$.productNum' AS productNum,
comment->>'$.wareDeliveryId' AS wareDeliveryId,
comment->>'$.wareDeliveryStatus' AS wareDeliveryStatus
FROM orderr_orderr;
| id | title | devNum | productNum | wareDeliveryId | wareDeliveryStatus |
|---|---|---|---|---|---|
| 1011695605988196352 | 测试订单A | 2 | 1 | 637 | 0 |
| 1011695605988196353 | 测试订单B | 3 | 2 | 638 | 1 |
| 1011695605988196354 | 测试订单C | 1 | 1 | 639 | 2 |
->>返回的是不带引号的纯文本值,大多数场景用它。
1.2 查询嵌套字段
sql
-- 查询 addressInfo 内部的字段
SELECT
id,
title,
comment->>'$.addressInfo.consignee' AS 收货人,
comment->>'$.addressInfo.phone' AS 电话,
comment->>'$.addressInfo.province' AS 省份,
comment->>'$.addressInfo.city' AS 城市,
comment->>'$.addressInfo.address' AS 详细地址
FROM orderr_orderr;
| id | title | 收货人 | 电话 | 省份 | 城市 | 详细地址 |
|---|---|---|---|---|---|---|
| ...352 | 测试订单A | 张三 | 13800001111 | 浙江省 | 杭州市 | 西湖区文三路100号天际大厦3幢508室 |
| ...353 | 测试订单B | 李四 | 13900002222 | 上海市 | 上海市 | 浦东新区张江高科技园区博云路2号 |
| ...354 | 测试订单C | 王五 | 13700003333 | 北京市 | 北京市 | 海淀区中关村大街1号海龙大厦12层 |
1.3 -> vs ->> 的区别
sql
-- -> 返回带双引号的 JSON 值
SELECT comment->'$.devNum' FROM orderr_orderr WHERE id = 1011695605988196352;
-- 结果: "2" (字符串类型)
-- ->> 返回不带引号的纯文本
SELECT comment->>'$.devNum' FROM orderr_orderr WHERE id = 1011695605988196352;
-- 结果: 2 (文本类型)
对比:
| 操作符 | 返回值 | 适用场景 |
|---|---|---|
-> |
JSON 值(带引号) | 需要保持 JSON 格式时 |
->> |
文本值(不带引号) | 绝大多数场景,推荐使用 |
1.4 使用 JSON_EXTRACT() 函数
sql
-- 等价于 ->
SELECT JSON_EXTRACT(comment, '$.devNum') FROM orderr_orderr WHERE id = 1011695605988196352;
-- 结果: "2"
-- 等价于 ->>(去掉引号)
SELECT JSON_UNQUOTE(JSON_EXTRACT(comment, '$.devNum')) FROM orderr_orderr WHERE id = 1011695605988196352;
-- 结果: 2
二、条件筛选(WHERE)
2.1 简单条件
sql
-- devNum = 2 的订单
SELECT * FROM orderr_orderr
WHERE comment->>'$.devNum' = '2';
2.2 数值比较
sql
-- wareDeliveryId > 637 的订单
SELECT id, title, comment->>'$.wareDeliveryId' AS wareDeliveryId
FROM orderr_orderr
WHERE CAST(comment->>'$.wareDeliveryId' AS SIGNED) > 637;
注意 :
->>返回的是文本,做数值比较时建议用CAST()转换。
2.3 嵌套字段条件
sql
-- 收货人是"张三"的订单
SELECT * FROM orderr_orderr
WHERE comment->>'$.addressInfo.consignee' = '张三';
-- 手机号以 "138" 开头的订单
SELECT * FROM orderr_orderr
WHERE comment->>'$.addressInfo.phone' LIKE '138%';
-- 城市是"杭州市"的订单
SELECT * FROM orderr_orderr
WHERE comment->>'$.addressInfo.city' = '杭州市';
2.4 多条件组合
sql
SELECT id, title,
comment->>'$.devNum' AS devNum,
comment->>'$.addressInfo.consignee' AS consignee,
comment->>'$.addressInfo.city' AS city
FROM orderr_orderr
WHERE comment->>'$.devNum' = '2'
AND comment->>'$.wareDeliveryStatus' = '0'
AND comment->>'$.addressInfo.phone' IS NOT NULL;
2.5 判断字段是否存在
sql
-- 查询 addressInfo 字段存在的记录
SELECT * FROM orderr_orderr
WHERE JSON_CONTAINS_PATH(comment, 'one', '$.addressInfo');
-- 查询同时有 devNum 和 productNum 的记录
SELECT * FROM orderr_orderr
WHERE JSON_CONTAINS_PATH(comment, 'all', '$.devNum', '$.productNum');
三、修改(UPDATE)
3.1 JSON_SET() --- 更新已有字段 / 插入新字段
sql
-- 更新 devNum 为 5,同时新增一个 remark 字段
UPDATE orderr_orderr
SET comment = JSON_SET(comment,
'$.devNum', 5,
'$.remark', '这是备注信息'
)
WHERE id = 1011695605988196352;
执行前:
json
{"devNum": 2, "productNum": 1, "addressInfo": {...}}
执行后:
json
{"devNum": 5, "productNum": 1, "addressInfo": {...}, "remark": "这是备注信息"}
JSON_SET= 存在就更新,不存在就插入,是最常用的修改函数。
3.2 JSON_INSERT() --- 仅插入不存在的字段
sql
-- devNum 已存在,不会被修改;newField 不存在,会被插入
UPDATE orderr_orderr
SET comment = JSON_INSERT(comment,
'$.devNum', 999, -- 已存在,忽略
'$.newField', '新字段值' -- 不存在,插入
)
WHERE id = 1011695605988196352;
结果: devNum 仍然是 2,但多了 newField。
3.3 JSON_REPLACE() --- 仅更新已存在的字段
sql
-- devNum 存在则更新;newField 不存在则忽略
UPDATE orderr_orderr
SET comment = JSON_REPLACE(comment,
'$.devNum', 10,
'$.newField', '不会插入'
)
WHERE id = 1011695605988196352;
结果: devNum 变为 10,newField 不会被插入。
3.4 JSON_REMOVE() --- 删除字段
sql
-- 删除 wareDeliveryStatus 字段
UPDATE orderr_orderr
SET comment = JSON_REMOVE(comment, '$.wareDeliveryStatus')
WHERE id = 1011695605988196352;
-- 删除嵌套字段
UPDATE orderr_orderr
SET comment = JSON_REMOVE(comment, '$.addressInfo.city')
WHERE id = 1011695605988196352;
3.5 修改嵌套对象中的值
sql
-- 修改收货人电话
UPDATE orderr_orderr
SET comment = JSON_SET(comment, '$.addressInfo.phone', '15000001234')
WHERE id = 1011695605988196352;
-- 修改收货地址
UPDATE orderr_orderr
SET comment = JSON_SET(comment, '$.addressInfo.address', '新的详细地址')
WHERE id = 1011695605988196352;
3.6 三个修改函数对比
| 函数 | 已存在的字段 | 不存在的字段 | 类比 |
|---|---|---|---|
JSON_SET |
更新 | 插入 | put |
JSON_INSERT |
忽略 | 插入 | putIfAbsent |
JSON_REPLACE |
更新 | 忽略 | replace |
四、JSON 数组操作
假设新增一个
tags数组字段:
{"devNum": 2, "tags": ["电子产品", "杭州", "已发货"]}
4.1 查询数组元素
sql
-- 查询第一个标签
SELECT comment->>'$.tags[0]' FROM orderr_orderr;
-- 结果: 电子产品
-- 查询数组长度
SELECT JSON_LENGTH(comment->>'$.tags') AS tag_count FROM orderr_orderr;
-- 结果: 3
4.2 追加数组元素
sql
-- 在数组末尾追加
UPDATE orderr_orderr
SET comment = JSON_ARRAY_APPEND(comment, '$.tags', '新标签')
WHERE id = 1011695605988196352;
-- 在数组开头插入
UPDATE orderr_orderr
SET comment = JSON_ARRAY_INSERT(comment, '$.tags[0]', '置顶标签')
WHERE id = 1011695605988196352;
4.3 删除数组元素
sql
-- 删除第一个元素
UPDATE orderr_orderr
SET comment = JSON_REMOVE(comment, '$.tags[0]')
WHERE id = 1011695605988196352;
五、判断与验证
sql
-- 1. 验证是否为合法 JSON
SELECT JSON_VALID(comment) FROM orderr_orderr WHERE id = 1011695605988196352;
-- 结果: 1 (合法)
-- 2. 获取 JSON 类型
SELECT JSON_TYPE(comment) FROM orderr_orderr WHERE id = 1011695605988196352;
-- 结果: OBJECT
-- 3. 获取 JSON 中 key 的数量
SELECT JSON_LENGTH(comment) FROM orderr_orderr WHERE id = 1011695605988196352;
-- 结果: 6
-- 4. 获取所有 key 名称
SELECT JSON_KEYS(comment) FROM orderr_orderr WHERE id = 1011695605988196352;
-- 结果: ["devNum", "productNum", "addressInfo", "orderrCheckId", "wareDeliveryId", "wareDeliveryStatus"]
-- 5. 判断某个值是否包含在 JSON 中
SELECT JSON_CONTAINS(comment, '2', '$.devNum') FROM orderr_orderr WHERE id = 1011695605988196352;
-- 结果: 1 (存在)
-- 6. 格式化输出
SELECT JSON_PRETTY(comment) FROM orderr_orderr WHERE id = 1011695605988196352;
六、聚合统计
sql
-- 统计所有订单的设备总数
SELECT SUM(comment->>'$.devNum') AS total_dev
FROM orderr_orderr;
-- 按 devNum 分组统计
SELECT
comment->>'$.devNum' AS dev_num,
COUNT(*) AS order_count,
SUM(monthly_rent) AS total_rent
FROM orderr_orderr
GROUP BY comment->>'$.devNum';
-- 统计收货省份分布
SELECT
comment->>'$.addressInfo.province' AS province,
COUNT(*) AS cnt
FROM orderr_orderr
WHERE comment->>'$.addressInfo.province' != ''
GROUP BY comment->>'$.addressInfo.province'
ORDER BY cnt DESC;
-- 统计各城市订单数
SELECT
comment->>'$.addressInfo.city' AS city,
COUNT(*) AS cnt
FROM orderr_orderr
GROUP BY comment->>'$.addressInfo.city'
ORDER BY cnt DESC;
七、建表与索引优化
7.1 建表时定义 JSON 字段
sql
CREATE TABLE orderr_orderr (
id BIGINT PRIMARY KEY,
title VARCHAR(100),
monthly_rent DECIMAL(10,2),
comment JSON DEFAULT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
7.2 创建虚拟列 + 索引(重要!)
sql
-- 从 JSON 中提取常用字段为虚拟列
ALTER TABLE orderr_orderr
ADD COLUMN dev_num INT
AS (CAST(comment->>'$.devNum' AS SIGNED)) VIRTUAL,
ADD COLUMN consignee VARCHAR(50)
AS (comment->>'$.addressInfo.consignee') VIRTUAL,
ADD COLUMN phone VARCHAR(20)
AS (comment->>'$.addressInfo.phone') VIRTUAL,
ADD COLUMN city VARCHAR(50)
AS (comment->>'$.addressInfo.city') VIRTUAL;
-- 对虚拟列建索引(大幅提升查询性能)
ALTER TABLE orderr_orderr ADD INDEX idx_devnum (dev_num);
ALTER TABLE orderr_orderr ADD INDEX idx_consignee (consignee);
ALTER TABLE orderr_orderr ADD INDEX idx_phone (phone);
ALTER TABLE orderr_orderr ADD INDEX idx_city (city);
7.3 MySQL 8.0 多值索引(针对数组)
sql
-- 如果 JSON 中有数组字段 tags
ALTER TABLE orderr_orderr
ADD INDEX idx_tags ((CAST(comment->>'$.tags' AS CHAR(50) ARRAY)));
-- 查询时使用 MEMBER OF
SELECT * FROM orderr_orderr
WHERE '电子产品' MEMBER OF (comment->>'$.tags');
八、实战场景速查表
| 场景 | SQL |
|---|---|
| 查收货人 | SELECT comment->>'$.addressInfo.consignee' |
| 查电话 | SELECT comment->>'$.addressInfo.phone' |
| 查城市 | SELECT comment->>'$.addressInfo.city' |
| 查设备数 | SELECT comment->>'$.devNum' |
| 改电话 | JSON_SET(comment, '$.addressInfo.phone', '新号码') |
| 加字段 | JSON_SET(comment, '$.newKey', 'value') |
| 删字段 | JSON_REMOVE(comment, '$.wareDeliveryStatus') |
| 判断存在 | JSON_CONTAINS_PATH(comment, 'one', '$.devNum') |
| 数组追加 | JSON_ARRAY_APPEND(comment, '$.tags', 'val') |
| 数组插入 | JSON_ARRAY_INSERT(comment, '$.tags[0]', 'val') |
| 格式化 | SELECT JSON_PRETTY(comment) |
| 验证JSON | SELECT JSON_VALID(comment) |
| 获取类型 | SELECT JSON_TYPE(comment) |
| 获取keys | SELECT JSON_KEYS(comment) |
九、常见注意事项
| 注意点 | 说明 |
|---|---|
| 类型转换 | ->> 返回文本,数值比较需 CAST() |
| NULL 处理 | JSON 字段为 NULL 时,->> 也返回 NULL |
| 索引 | 直接查 JSON 字段无法走索引,需建虚拟列+索引 |
| 版本要求 | ->> 需 MySQL 8.0+ ;JSON 类型需 5.7+ |
| 空值判断 | WHERE comment IS NULL 而非 WHERE comment = '{}' |
| 性能 | 避免在大表上对 JSON 字段做全表扫描 |
| 嵌套层级 | 建议嵌套不超过 3 层,过深影响可读性和性能 |
十、函数速查表
| 分类 | 函数 | 说明 |
|---|---|---|
| 查询 | ->> |
查询值(纯文本,推荐) |
-> |
查询值(JSON格式) | |
JSON_EXTRACT() |
查询值 | |
| 修改 | JSON_SET() |
更新/插入 |
JSON_INSERT() |
仅插入 | |
JSON_REPLACE() |
仅更新 | |
JSON_REMOVE() |
删除字段 | |
| 数组 | JSON_ARRAY_APPEND() |
追加元素 |
JSON_ARRAY_INSERT() |
插入元素 | |
| 验证 | JSON_VALID() |
验证合法性 |
JSON_TYPE() |
获取类型 | |
JSON_LENGTH() |
获取长度 | |
JSON_KEYS() |
获取所有key | |
JSON_CONTAINS() |
判断值存在 | |
JSON_CONTAINS_PATH() |
判断路径存在 | |
| 格式 | JSON_PRETTY() |
格式化输出 |