
你的 orders 表里存了一个 items 字段,格式是 JSON 数组,记录了订单里的商品详情:
[{"skuid": 1001, "price": 99}, {"skuid": 1002, "price": 50}]
需求: 老板让你统计一下,所有订单中 skuid = 1001 的商品一共卖了多少钱。
痛苦的旧写法:
SELECT items FROM orders;(把全量 JSON 拉到内存)- Java 代码里
JSON.parse(items)。 - 双层
for循环遍历订单和商品,累加金额。
代价: 巨大的网络 I/O 开销,OOM(内存溢出)的风险,数据库计算能力的浪费。
优雅的新写法:
利用 JSON_TABLE,直接在 SQL 里把这个数组"打散"成行,然后 SUM()。零网络传输,计算下推到数据库。
1. 核心原理:ETL in SQL
JSON_TABLE 的作用是 Extract(提取)- Transform(转换)- Load(加载) ,但它不是加载到磁盘,而是加载到查询时的临时虚拟表中。
它接收两个核心参数:
- JSON 源数据。
- 映射规则 (COLUMNS):定义如何把 JSON 的 Key 映射为 SQL 的列。
2. 实战演练:从 JSON 数组到 SQL 结果集
准备数据
sql
CREATE TABLE order_docs (
id INT PRIMARY KEY,
user_name VARCHAR(50),
items JSON
);
INSERT INTO order_docs VALUES
(1, 'Alice', '[{"name": "Mouse", "price": 50}, {"name": "Keyboard", "price": 200}]'),
(2, 'Bob', '[{"name": "Mouse", "price": 50}]');
需求:打平数组,统计销量
我们希望看到这样的表格结果:
| user_name | product_name | price |
|---|---|---|
| Alice | Mouse | 50 |
| Alice | Keyboard | 200 |
| Bob | Mouse | 50 |
SQL 实现 (JSON_TABLE):
sql
SELECT
o.user_name,
jt.product_name,
jt.price
FROM
order_docs o,
-- 核心语法开始
JSON_TABLE(
o.items, -- 1. 数据源
'$[*]' -- 2. 路径:遍历数组里的每一个元素
COLUMNS (
-- 3. 定义列映射
product_name VARCHAR(50) PATH '$.name',
price DECIMAL(10,2) PATH '$.price',
-- 甚至可以生成自动递增的序号!
item_index FOR ORDINALITY
)
) AS jt; -- 别忘了给虚拟表起个名字
执行逻辑:
MySQL 会遍历 order_docs 的每一行,针对每一行里的 items JSON,动态生成一张名为 jt 的虚拟表,然后与原表 o 进行 Lateral Join (横向连接)。
3. 三大实战场景
场景一:JSON 数据的聚合统计 (Sum/Count)
这是最直接的收益场景。
需求: 统计所有订单中 "Mouse" 的总销售额。
sql
SELECT
SUM(jt.price) as total_sales
FROM
order_docs o,
JSON_TABLE(
o.items, '$[*]' COLUMNS (
p_name VARCHAR(50) PATH '$.name',
price DECIMAL(10,2) PATH '$.price'
)
) AS jt
WHERE
jt.p_name = 'Mouse';
优势: 相比于把几万条订单拉到应用层处理,这条 SQL 执行速度快几个数量级,且不仅节省网络,还利用了数据库的并行计算能力。
场景二:关联查询 (JSON ID -> 另一张表)
背景: 很多系统为了高性能,会在主表存关联 ID 的 JSON 数组,比如 user_tags 表里存 tag_ids: [101, 102, 105]。
需求: 查询用户具备的所有标签的名称 (需要关联 tags 表)。
sql
SELECT
u.user_name, t.tag_name
FROM
users u,
-- 1. 先把 JSON 里的 [101, 102] 转成行
JSON_TABLE(u.tag_ids, '$[*]' COLUMNS (
tid INT PATH '$'
)) AS jt
-- 2. 再去关联 tags 表
JOIN tags t ON jt.tid = t.id;
这在以前通常需要代码循环查询,现在一条 SQL 搞定。
场景三:导入复杂配置数据 (ETL)
背景: 外部系统发来一个巨大的 JSON 配置文件,包含嵌套的部门和人员信息,需要导入到我们数据库的标准关系表中。
解法: 直接把大 JSON 作为一个参数传给 SQL,利用 JSON_TABLE 解析出多行数据,配合 INSERT INTO ... SELECT ... 批量入库。
4. 注意事项与限制
- 版本要求: 必须是 MySQL 8.0+ 。MySQL 5.7 只有
JSON_EXTRACT,不支持JSON_TABLE。 - 数据类型匹配: 在
COLUMNS定义时,类型(如INT,VARCHAR)要与 JSON 里的实际值匹配,否则可能会报错或产生默认值。 - 性能考量:
JSON_TABLE是在查询时实时计算的(On-the-fly)。如果 JSON 非常大(几 MB),或者表数据量极大,CPU 消耗会很高。
- 优化策: 如果查询极其频繁,建议在写入时就拆分到子表,或者使用 MySQL 8.0 的"函数索引"对提取出的关键字段加索引。
5. 总结
JSON_TABLE 是连接 NoSQL 灵活性和 SQL 规范性的桥梁。
- 以前: JSON 是个黑盒,只能整体存取,逻辑全靠代码写。
- 现在: JSON 就是一张待展开的表,SQL 的所有能力(Join, Group By, Window Function)都能直接作用于 JSON 内部数据。
拒绝在应用层写丑陋的 for 循环,把数据处理还给数据库!