当前位置:首页 > 文章列表 > 数据库 > MySQL > JSON_TABLE 怎样展开嵌套文档

JSON_TABLE 怎样展开嵌套文档

来源:17golang原创 2026-10-25 17:56:59 0浏览 收藏

我第一次用 JSON_TABLE() 展开订单文档时,最容易误判的是“一个订单有几个结果行”。真正决定行数的不是 JSON 看起来有几层,而是根路径匹配了多少父级元素、每个 NESTED PATH 匹配了多少子元素,以及缺失数组时 MySQL 是否保留一行。

官方资料:https://dev.mysql.com/doc/refman/8.4/en/json-table-functions.html

把根数组当作父级行,把子数组放进相对的 NESTED PATH,再用 FOR ORDINALITY 保留层级身份。这样展开后的行数和 NULL 都有明确来源,后面的过滤才不会把数据完整性问题藏起来。

先把根数组映射成父级行

假设一列保存了订单文档,每个订单有 id、customer 和 items 数组。JSON_TABLE() 的第二个参数是行路径:路径匹配到几个 JSON 元素,外层就先建立几行。外层 COLUMNS 再从每个父元素读取标量字段。

-- 根路径 $[*] 把文档数组中的每个订单对象作为一个父级行源。
SELECT jt.order_id, jt.customer
FROM JSON_TABLE(
    '[
      {"id": 101, "customer": "林杉", "items": [{"sku": "A-1", "qty": 2}]},
      {"id": 102, "customer": "周宁", "items": [{"sku": "B-2", "qty": 1}]}
    ]',
    '$[*]' COLUMNS (
        order_id INT PATH '$.id',
        customer VARCHAR(32) PATH '$.customer'
    )
) AS jt;

这里的 $[*] 是父级 row source,$.id 和 $.customer 是相对于当前订单对象的字段路径。路径匹配一个元素就形成一行,匹配两个元素就形成两行;不要把 COLUMNS 中的字段数理解成结果行数。

用相对 NESTED PATH 展开子数组

子数组要放进父级 COLUMNS 内的 NESTED PATH。它的路径相对于当前父级 row source,所以写成 $.items[*],而不是重新从整份文档写一条绝对路径。每个匹配到的商品对象都会生成一行,同时带上父级订单字段。

-- NESTED PATH 只展开当前订单的 items,父级字段会随子级行保留。
SELECT jt.order_id, jt.customer, jt.sku, jt.qty
FROM JSON_TABLE(
    '[
      {"id": 101, "customer": "林杉", "items": [{"sku": "A-1", "qty": 2}, {"sku": "A-2", "qty": 3}]},
      {"id": 102, "customer": "周宁", "items": [{"sku": "B-2", "qty": 1}]}
    ]',
    '$[*]' COLUMNS (
        order_id INT PATH '$.id',
        customer VARCHAR(32) PATH '$.customer',
        NESTED PATH '$.items[*]' COLUMNS (
            sku VARCHAR(32) PATH '$.sku',
            qty INT PATH '$.qty'
        )
    )
) AS jt;

这份数据对应三条商品行:订单 101 的父级字段重复两次,订单 102 的父级字段出现一次。重复不是异常,而是父子关系展开后的自然结果。图1把“订单文档—根数组—父级字段—相对子路径—结果行”放在同一张结构图里,适合用来检查路径到底相对于哪一层。

订单文档根数组父级字段和 NESTED PATH 展开结果行的静态关系图
图1:JSON_TABLE 根数组与相对 NESTED PATH 的结构说明图,展示父级字段如何随子级对象进入结果行;这是静态说明图,不是运行截图。

缺少子数组时,为什么仍可能有一行

我在处理历史订单时遇到过没有 items 字段的文档。MySQL 对没有匹配到的嵌套路径会生成一条 NULL 补全行,父级字段仍然保留,嵌套列为 NULL。这更接近外连接语义,不等于“该订单有一个空商品”。

-- 没有 items 的父级仍保留一行;需要商品时再显式过滤 NULL。
SELECT jt.order_id, jt.sku
FROM JSON_TABLE(
    '[
      {"id": 101, "items": [{"sku": "A-1"}]},
      {"id": 103, "items": []},
      {"id": 104}
    ]',
    '$[*]' COLUMNS (
        order_id INT PATH '$.id',
        NESTED PATH '$.items[*]' COLUMNS (
            sku VARCHAR(32) PATH '$.sku'
        )
    )
) AS jt;

-- 只要真正有商品的订单时,使用 WHERE 把 NULL 补全行排除。
SELECT jt.order_id, jt.sku
FROM JSON_TABLE(
    '[{"id": 101, "items": [{"sku": "A-1"}]}, {"id": 104}]',
    '$[*]' COLUMNS (
        order_id INT PATH '$.id',
        NESTED PATH '$.items[*]' COLUMNS (
            sku VARCHAR(32) PATH '$.sku'
        )
    )
) AS jt
WHERE jt.sku IS NOT NULL;

这里要区分两件事:空数组通常没有子元素可展开,而缺少路径会触发嵌套列的 NULL 补全语义。业务若需要统计“没有商品的订单”,保留这行并检查 sku IS NULL;业务若只想读取商品明细,再明确使用 IS NOT NULL 过滤。不要在外层随手加过滤后,又把结果行数当成原始订单数。

多层嵌套时,路径始终相对当前父级

如果 items 里的商品还有 tags 数组,可以继续嵌套。第二层路径相对于当前商品对象写成 $.tags[*]。每层都应只读取当前对象的字段,这样同名字段不会因为路径写错而从其他层串进来。

-- 第二层 NESTED PATH 相对于当前商品对象,而不是相对于整份文档。
SELECT jt.order_id, jt.sku, jt.tag
FROM JSON_TABLE(
    '[
      {"id": 101, "items": [
        {"sku": "A-1", "tags": ["礼盒", "易碎"]}
      ]}
    ]',
    '$[*]' COLUMNS (
        order_id INT PATH '$.id',
        NESTED PATH '$.items[*]' COLUMNS (
            sku VARCHAR(32) PATH '$.sku',
            NESTED PATH '$.tags[*]' COLUMNS (
                tag VARCHAR(32) PATH '$'
            )
        )
    )
) AS jt;

多层展开时,行数会随着子数组基数增长。图1中的“相对路径”是一个很实用的检查点:看到 $.tags[*] 时,先问它当前绑定的是商品对象还是订单对象。不要用一个看似能匹配的全局路径掩盖层级关系。

用 FOR ORDINALITY 保留重复父对象的层级

当两个父对象的 customer、sku 甚至其他业务字段都一样时,仅靠字段值无法判断子数组属于哪个父对象。FOR ORDINALITY 会为当前 COLUMNS 产生的记录编号,父层和子层各放一个序号,就能保留层级身份。

-- 两个父对象的业务值可以相同,用 top_ord 和 item_ord 保留所属关系。
SELECT jt.top_ord, jt.item_ord, jt.customer, jt.sku
FROM JSON_TABLE(
    '[
      {"customer": "同名客户", "items": [{"sku": "同款"}]},
      {"customer": "同名客户", "items": [{"sku": "同款"}, {"sku": "另一款"}]}
    ]',
    '$[*]' COLUMNS (
        top_ord FOR ORDINALITY,
        customer VARCHAR(32) PATH '$.customer',
        NESTED PATH '$.items[*]' COLUMNS (
            item_ord FOR ORDINALITY,
            sku VARCHAR(32) PATH '$.sku'
        )
    )
) AS jt;

top_ord 能区分根数组里的第几个父对象,item_ord 能区分该父对象内的第几个商品。它们不是业务主键,也不应被写回数据库当永久 ID;它们的价值是帮助排查展开结果、建立临时关联和复核父子归属。

兄弟 NESTED PATH 不是笛卡尔相乘

这是我最容易在第一次设计查询时想错的地方:同一个 COLUMNS 中有两条兄弟 NESTED PATH 时,MySQL 会依次处理它们,记录数是各自产生的记录数相加,另一条路径的列在当前部分通常为 NULL,不是两组数组长度相乘。

-- 两条兄弟路径分别产生记录;不要把结果理解为 tags 与 coupons 的笛卡尔积。
SELECT jt.order_id, jt.tag, jt.coupon
FROM JSON_TABLE(
    '[{"id": 101, "tags": ["礼盒", "易碎"], "coupons": ["满减"]}]',
    '$[*]' COLUMNS (
        order_id INT PATH '$.id',
        NESTED PATH '$.tags[*]' COLUMNS (
            tag VARCHAR(32) PATH '$'
        ),
        NESTED PATH '$.coupons[*]' COLUMNS (
            coupon VARCHAR(32) PATH '$'
        )
    )
) AS jt;

这个父对象有两个标签、一个优惠券时,兄弟路径的结果语义是两条标签记录加一条优惠券记录,而不是两乘一后再复制成一组“标签—优惠券”配对。如果业务真的需要配对关系,应先确认 JSON 数据是否表达了配对关系,再设计明确的关联键或分别展开后连接;不能靠把两条 NESTED PATH 写在一起猜出配对。

FOR ORDINALITY兄弟NESTED PATH和NULL补全行的静态关系图
图2:FOR ORDINALITY 与兄弟 NESTED PATH 的边界结构图,强调记录数相加而不是笛卡尔相乘;这是静态说明图,不是运行截图。

用数据完整性清单收尾

检查项要确认的事实常见误区
根路径每个匹配元素是否应该成为一行把字段数量当成行数
嵌套路径路径是否相对于当前父级 row source从整份文档重新猜路径
缺失子数组是否接受嵌套列为 NULL 的补全行把 NULL 行当成真实子对象
序号字段是否需要区分值相同的父子对象把 FOR ORDINALITY 当永久主键
兄弟路径结果是记录数相加还是业务上需要配对误以为自动产生笛卡尔积

如果只是把嵌套 JSON 读成报表,推荐先写一条只包含根路径和父级字段的查询,再加入一层 NESTED PATH,最后才处理多层数组、空值和兄弟路径。每加一层都单独核对行数来源,这比看到结果不对后一次性修改多条路径更容易定位。

JSON_TABLE 的 NESTED PATH 能直接写绝对路径吗?

嵌套路径的语义是相对于父级路径。要表达当前父对象中的子数组,应使用类似 $.items[*] 的相对路径,不能把它当成从文档根重新开始的独立查询。

没有子数组时怎样保留父级数据?

保留默认的 NULL 补全语义即可;如果只要真实子项,再在外层根据子列是否为 NULL 过滤。两种写法对应不同的数据目标。

两个 NESTED PATH 为什么没有生成配对行?

兄弟嵌套路径按各自记录集合依次产生结果,记录数是相加关系。需要配对时,必须在数据模型或后续查询中提供明确的关联依据。

JSON_TABLE 的核心不是把 JSON“摊平”得越多越好,而是让每一行都有可解释的父级来源、子级来源和空值来源。根路径决定父行,NESTED PATH 决定子行,FOR ORDINALITY 保留重复层级,兄弟路径则提醒我们不要凭直觉猜行数。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
Go os.Root Chtimes 文件时间更新有什么限制Go os.Root Chtimes 文件时间更新有什么限制
上一篇
Go os.Root Chtimes 文件时间更新有什么限制
VS Code 用户代码片段按文件名模式限定展开范围
下一篇
VS Code 用户代码片段按文件名模式限定展开范围
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    689次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    702次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    762次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    724次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    561次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议 和 隐私政策
返回登录
  • 重置密码