当前位置:首页 > 文章列表 > 数据库 > MySQL > MySQL JSON_TABLE 怎样保留嵌套数组的父子关联

MySQL JSON_TABLE 怎样保留嵌套数组的父子关联

来源:17golang原创 2026-11-11 18:44:19 0浏览 收藏

我在把订单 JSON 展平成明细行时,最容易踩的坑不是路径写错,而是展开以后不知道某个子项究竟属于哪个父对象。尤其当两个父对象的 parent_id 或名称恰好相同时,只把业务字段带出来是不够的。

用顶层 FOR ORDINALITY 标记父对象,用嵌套层的 FOR ORDINALITY 标记子对象,再把稳定业务键一起投影出来,就能在 JSON_TABLE() 展平后保留父子关联。缺少子数组时,MySQL 默认保留父行并把嵌套列补成 NULL。

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

本文只讨论 MySQL 8.4 中 JSON_TABLE() 的嵌套数组展开,不把问题扩展到其他 JSON 谓词。示例中的 top_ord 和 child_ord 是本次展开产生的定位信息;如果 JSON 本身有稳定的业务 ID,也应该同时保留。

先把父数组和子数组放进同一个 JSON_TABLE

假设一份文档里有两个父对象,每个父对象都有一个 items 子数组。为了故意制造容易混淆的场景,两个父对象的 parent_id 和 parent_name 相同,但它们代表不同的 JSON 对象。

SET @doc = '[
  {"parent_id":"A","parent_name":"同名订单","items":[
    {"sku":"P1","qty":2}, {"sku":"P2","qty":1}
  ]},
  {"parent_id":"A","parent_name":"同名订单","items":[
    {"sku":"P3","qty":4}
  ]}
]';

SELECT jt.top_ord, jt.parent_id, jt.parent_name,
       jt.child_ord, jt.sku, jt.qty
FROM JSON_TABLE(
  @doc,
  '$[*]' COLUMNS (
    top_ord FOR ORDINALITY,
    parent_id VARCHAR(32) PATH '$.parent_id',
    parent_name VARCHAR(64) PATH '$.parent_name',
    NESTED PATH '$.items[*]' COLUMNS (
      child_ord FOR ORDINALITY,
      sku VARCHAR(32) PATH '$.sku',
      qty INT PATH '$.qty'
    )
  )
) AS jt;
MySQL JSON_TABLE 顶层数组、父字段、NESTED PATH 子数组和两层序号的静态结构说明图
图1:父数组到子数组的层级展开说明图,展示父字段如何随子行保留;这是静态结构图,不是截图或运行证据。

这里的外层路径 $[*] 为每个父对象建立一个行来源,parent_id 和 parent_name 直接从当前父对象取值。NESTED PATH '$.items[*]' 再以当前父对象为相对路径,展开它的每个子项。于是同一个父对象的字段会出现在它的每一条子行上。

为什么父子两层都要有 FOR ORDINALITY

只保留 parent_id 看起来很自然,但示例里两个父对象都叫 A。如果再只依赖 parent_name,第一组的 P1、P2 和第二组的 P3 就无法可靠地回指到原始父对象。

top_ord 由顶层 COLUMNS 生成,表示当前父行在顶层数组中的序号;嵌套 COLUMNS 里的 child_ord 表示当前子项在该父对象子数组中的序号。两层序号的组合可以写成 (top_ord, child_ord),它表达的是“第几个父对象下的第几个子对象”。

MySQL JSON_TABLE 用 top_ord 和 child_ord 区分同名父对象与子对象的静态关系图
图2:层级序号联合键说明图,展示同名业务值为何仍需要 top_ord 与 child_ord;这是静态结构图,不是截图或运行证据。

这里有一个取舍:序号只对本次 JSON 展开结果稳定,并不等同于业务主键。写回关系表或跨批次合并时,优先使用 JSON 中真实存在的订单号、明细号等稳定键;把序号作为定位辅助列,排查重复值和回溯原始数组位置。

把父子定位信息显式组合出来

如果下游要把结果交给 ETL 或应用层,可以在查询里直接生成可读的定位字段。不要用字符串拼接替代真正的业务键,它只适合在当前结果集中追踪层级。

SELECT
  jt.top_ord,
  jt.child_ord,
  jt.parent_id,
  jt.sku,
  CONCAT(jt.top_ord, ':', jt.child_ord) AS json_position
FROM JSON_TABLE(
  @doc,
  '$[*]' COLUMNS (
    top_ord FOR ORDINALITY,
    parent_id VARCHAR(32) PATH '$.parent_id',
    NESTED PATH '$.items[*]' COLUMNS (
      child_ord FOR ORDINALITY,
      sku VARCHAR(32) PATH '$.sku'
    )
  )
) AS jt;

json_position 便于日志、导入错误表或调试时快速定位,但它的含义依赖原始数组顺序。如果上游会重新排序数组,不能拿它当跨版本不变的主键。

子数组缺失时,为什么还能看到父行

NESTED PATH 没有匹配项时,MySQL 默认会保留外层父行,并把这个嵌套路径下的列补成 NULL。这相当于外连接效果,适合回答“哪些父对象没有明细”。

SELECT jt.top_ord, jt.parent_id, jt.child_ord, jt.sku
FROM JSON_TABLE(
  '[
    {"parent_id":"A","items":[{"sku":"P1"}]},
    {"parent_id":"B"}
  ]',
  '$[*]' COLUMNS (
    top_ord FOR ORDINALITY,
    parent_id VARCHAR(32) PATH '$.parent_id',
    NESTED PATH '$.items[*]' COLUMNS (
      child_ord FOR ORDINALITY,
      sku VARCHAR(32) PATH '$.sku'
    )
  )
) AS jt;

如果业务只需要确实存在子项的行,再在外层加 WHERE jt.sku IS NOT NULL。这个过滤是查询语义上的内连接效果,不要为了“消除空值”而把父层的定位列一起删掉。

两个 NESTED PATH 不会自动形成笛卡尔积

同一父层里放两个兄弟 NESTED PATH 时,MySQL 会依次处理它们;一个嵌套路径产出记录时,另一个嵌套路径的列会补成 NULL。所以最终行数是各路径产出行数之和,而不是两个数组长度相乘。

如果目标是保留一个明确的父子链,应把真正的子数组写在另一个 NESTED PATH 内部,并为每一层保留自己的序号。不要把兄弟路径的结果误读成同一条明细,也不要用普通的行号去猜两个数组之间的配对关系。

最后用这张表检查关联是否完整

字段作用是否适合当长期主键
业务父键识别真实父对象有稳定语义时优先
业务子键识别真实子对象有稳定语义时优先
top_ord定位顶层数组中的父对象只适合本次展开
child_ord定位父对象内的子对象只适合本次展开
(top_ord, child_ord)组合出当前文档内的父子位置不替代业务键

实践中我会同时保留业务键和两层序号:业务键负责长期关联,序号负责解释“这条结果来自 JSON 的哪一层、哪一个数组元素”。这样即使字段值重复,展开后的结果也仍然能够回到原始父子结构。

常见问题

父字段为什么会在多条子行重复? 因为每个子元素都会生成一行,父层路径列会随这条行一起投影;这不是重复数据错误。

只有一个 FOR ORDINALITY 可以吗? 如果只需要父层定位可以,但嵌套数组里还要区分同一父对象下的多个子对象时,应在子层再定义一个序号。

能不能只用 parent_id 关联? 只有当它在文档内真正唯一且稳定时才可以。示例故意让两个父对象共享同一个值,正是为了说明为什么还要保留层级定位。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
MySQL JSON 字段怎样用生成列建立索引MySQL JSON 字段怎样用生成列建立索引
上一篇
MySQL JSON 字段怎样用生成列建立索引
MySQL JSON_ARRAYAGG 没有行时如何处理空结果
下一篇
MySQL JSON_ARRAYAGG 没有行时如何处理空结果
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之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模型性能。
    984次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    987次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    1108次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    1079次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    921次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议 和 隐私政策
返回登录
  • 重置密码