Oracle 19c JSON_OBJECT 完全实战指南|嵌套、多行多列数组合并、空值踩坑、医保报文落地

摘要

本文基于 Oracle 19c 低版本真实环境实测,聚焦 HIS 系统、医保对接、药品追溯业务场景,系统讲解JSON_OBJECT 基础用法、多层嵌套报文拼装方案。重点解决 19c 经典坑点:空字符串自动转 null、官方参数不兼容、特殊字符报错等问题。同时详细拆解 JSON_ARRAYAGG 单列、多列多行聚合技巧,提供可直接上线的医保标准报文模板、空值兜底、去重排序、空数组兼容方案,是数据库原生构建 JSON 报文的实战避坑手册。

一、前言

在 HIS、医保前置机对接开发中,经常需要 Oracle 端直接生成标准 JSON 报文 ,无需程序层二次处理。Oracle 12c 及以上提供的JSON_OBJECT 是原生构建 JSON 的核心函数,支持自动识别数字、字符串、浮点类型,支持多层嵌套结构。本文基于生产常用的 Oracle 19c 完整实测,整理从基础语法、嵌套写法、经典踩坑到生产落地的全套方案,非常适合医保、药品追溯类项目开发。

二、基础语法:单层 JSON_OBJECT 构造

1. 最简格式

|---------------------------------------------------------------------------------------------------------------------------------------------|
| sql SELECT JSON_OBJECT( KEY 'fylx' VALUE 1, KEY 'patient_name' VALUE '张三', KEY 'visit_no' VALUE 'MZ20260810001' ) AS json_result FROM DUAL; |

输出结果:

|----------------------------------------------------------------|
| json {"fylx":1,"patient_name":"张三","visit_no":"MZ20260810001"} |

规则:

  1. KEY 为 JSON 键名,直接写文本,无需额外引号;
  1. VALUE 自动识别类型:数字为 JSON 数值,文本自动包裹双引号;
  1. 最后一个字段禁止逗号结尾,否则 Oracle 报语法错误。

三、核心重点:JSON_OBJECT 多层嵌套写法

医保、药品追溯报文大多为多层嵌套结构,外层为业务主信息,内层包含药品明细、追溯子信息。Oracle 原生支持 JSON_OBJECT 嵌套 ,直接内层再写一层 JSON_OBJECT 即可生成标准子对象,是官方唯一标准写法,全 19c 补丁版本兼容。

嵌套完整示例(药品追溯报文结构)

|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| sql SELECT JSON_OBJECT( KEY 'fylx' VALUE 1, KEY 'jlxh' VALUE 123, KEY 'drugtracinfo' VALUE JSON_OBJECT( KEY 'fyxh' VALUE 1, KEY 'ypcd' VALUE 11, KEY 'ypdj' VALUE 23.23 ), KEY 'memo' VALUE ' ' ) AS result_json FROM DUAL; |

生成嵌套 JSON:

|--------------------------------------------------------------------------------------------------------|
| json { "fylx": 1, "jlxh": 123, "drugtracinfo": { "fyxh": 1, "ypcd": 11, "ypdj": 23.23 }, "memo": " " } |

  • 外层主对象承载业务主字段;
  • 子节点通过嵌套 JSON_OBJECT 生成内嵌对象;
  • 数值、小数自动识别 JSON 类型,无需手动处理格式。

四、高频踩坑点:空字符串 '' 自动转为 null

1. 问题复现

Oracle 19c 存在一个经典兼容坑:''null 直接赋值空字符串 ,最终 JSON 会变成,而非空字符串,极易导致医保、政务接口校验失败。

|-------------------------------------------------------------------------------------|
| sql SELECT JSON_OBJECT( KEY 'memo' VALUE '' ) AS res FROM DUAL; -- 输出:{"memo":null} |

很多医保、政务接口严格校验字段,要求空备注必须为"",不允许null,直接导致报文校验失败。

2. 几种解决方案实测对比(Oracle 19c)

方案 1:NULL ON EMPTY 全局修饰(版本兼容性差)

NULL ON EMPTY、ABSENT ON NULL 等高阶参数仅高版本19c支持,多数生产低版本补丁会直接报 ORA-02000: 缺失关键字,兼容性极差,不推荐生产使用。

|----------------------------------------------------------------------|
| sql SELECT JSON_OBJECT(KEY 'memo' VALUE '') NULL ON EMPTY FROM DUAL; |

方案 2:CHR (0) 零字符(产生转义字符,接口拒收)

|-------------------------------------------------------------------------------------|
| sql SELECT JSON_OBJECT(KEY 'memo' VALUE CHR(0)) FROM DUAL; -- 输出 {"memo":"\u0000"} |

CHR(0) 会生成 \u0000 不可见Unicode字符,绝大多数医保前置机、JSON解析器判定为非法字符,直接拒收,完全废弃。

方案 3:空格占位(最终落地首选,兼容性 100%)

生产最终通用方案:半角空格占位 。使用 ' ' 替代空字符串,最终输出 " ",所有接口均可正常识别为空值,无报错、无特殊字符、100%兼容所有19c版本。

|---------------------------------------------------------|
| sql SELECT JSON_OBJECT(KEY 'memo' VALUE ' ') FROM DUAL; |

方案 4:JSON_TRANSFORM 后置修改(高版本规范写法)

新版 Oracle 可使用 JSON_TRANSFORM 后置修正 JSON 字段,强制将 null 替换为空字符串,写法最规范,但低版本19c不兼容,仅作拓展了解。

|---------------------------------------------------------------------------------------------|
| sql SELECT JSON_TRANSFORM( JSON_OBJECT(KEY 'memo' VALUE ''), SET '$.memo' = '' ) FROM DUAL; |

五、关联业务表实战(HIS 门诊明细场景)

将固定值替换为业务表字段,结合 NVL 统一空值兜底,即可快速拼装符合医保规范的完整嵌套报文。

|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| sql SELECT JSON_OBJECT( KEY 'fylx' VALUE 1, KEY 'visit_id' VALUE t.vis_id, KEY 'patient_id' VALUE t.pat_id, KEY 'drug_detail' VALUE JSON_OBJECT( KEY 'drug_code' VALUE t.drug_code, KEY 'drug_name' VALUE t.drug_name, KEY 'dosage' VALUE t.dos_num, KEY 'price' VALUE t.unit_price, KEY 'memo' VALUE NVL(t.remark, ' ') ) ) AS insurance_json FROM outp_drug_detail t WHERE t.vis_id = 'MZ20260810001'; |

核心兜底规则:NVL(字段,' '),字段为 NULL 时自动填充空格,彻底规避 JSON 出现 null 的校验问题。

六、多行数据合并:YK_TRACE_CODE 追溯码数组拼接(医保核心场景)

药品追溯业务中,一条药品明细往往对应多条追溯码记录 ,存储在追溯明细表 YK_TRACE_CODE。需要将多条行数据合并为 JSON 数组嵌套进主报文,Oracle 通过 JSON_ARRAYAGG 实现多行聚合数组,是医保追溯开发必备技能。

1. 业务场景说明

主业务表(门诊费用明细)一对一关联追溯码表,YK_TRACE_CODE 一条药品对应多条追溯码,最终需要输出如下格式JSON:

|----------------------------------------------------------------------------------------------------------------------|
| json { "fylx": 1, "jlxh": 123, "trace_code_list": "TC20260810001","TC20260810002","TC20260810003", "memo": " " } |

2. 核心语法:JSON_ARRAYAGG 多行合并

JSON_ARRAYAGG 是 Oracle 原生多行转数组函数,支持排序、去重、空值兜底,与 JSON_OBJECT 嵌套完全兼容 19c 全补丁版本。

3. 完整实战SQL(关联追溯码表)

|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| sql SELECT JSON_OBJECT( KEY 'fylx' VALUE 1, KEY 'jlxh' VALUE x.jlxh, -- 多条追溯码合并为JSON数组 KEY 'trace_code_list' VALUE JSON_ARRAYAGG( NVL(t.trace_code,'') ORDER BY t.trace_code -- 数组内追溯码排序 ), KEY 'memo' VALUE ' ' ) AS drug_trace_json FROM yf_mzfymx x -- 左连追溯码表,一对多关联 LEFT JOIN YK_TRACE_CODE t ON t.ypcd = x.ypcd AND t.ypxh = x.ypxh WHERE x.fyrq > DATE'2026-01-01' GROUP BY x.jlxh; -- 按药品明细分组,合并对应多条追溯码 |

4. 关键适配优化(解决业务痛点)

(1)无追溯码时返回空数组,不返回null

JSON_ARRAYAGG 无数据时默认返回 null,医保接口严格禁止数组 null,必须通过外层 NVL(...,JSON_ARRAY()) 兜底,强制返回空数组 \[\]。

|----------------------------------------------------------------------------------------------------------------|
| sql KEY 'trace_code_list' VALUE NVL( JSON_ARRAYAGG(NVL(t.trace_code,'') ORDER BY t.trace_code), JSON_ARRAY() ) |

(2)去除重复追溯码

追溯明细表经常存在重复流水数据,增加 DISTINCT 可自动剔除完全重复的追溯码数据,保证报文干净规范。

|------------------------------------------------------------------------|
| sql JSON_ARRAYAGG(DISTINCT NVL(t.trace_code,'') ORDER BY t.trace_code) |

5. 最终生产可用完整版(兼容所有场景)

|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| sql SELECT JSON_OBJECT( KEY 'fylx' VALUE 1, KEY 'jlxh' VALUE x.jlxh, KEY 'yyxmbm' VALUE NVL(m.fixmedins_hilist_id, ''), KEY 'yyxmmc' VALUE NVL(m.fixmedins_hilist_name, '未匹配项目'), KEY 'fixmedins_bchno' VALUE '1-' || x.jlxh, KEY 'jshid' VALUE s.jshid, -- 兜底:无数据返回空数组,去重+排序 KEY 'trace_code_list' VALUE NVL( JSON_ARRAYAGG(DISTINCT NVL(t.trace_code,'') ORDER BY t.trace_code), JSON_ARRAY() ), KEY 'memo' VALUE ' ' ) AS insurance_upload_json FROM yf_mzfymx x LEFT JOIN yk_ypcd c ON c.ypcd = x.ypcd AND c.ypxh = x.ypxh LEFT JOIN yb_si_match m ON x.ypxh = m.ypxh AND x.ypcd = m.ypcd LEFT JOIN YK_TRACE_CODE t ON t.ypcd = x.ypcd AND t.ypxh = x.ypxh INNER JOIN yb_gs_fymx f ON f.blh = '202601091633432107' AND f.yzzh = 'CF' || x.cfsb AND f.yyxmbm = LPAD(x.YPXH,8,'0') || LPAD(x.YPCD,8,'0') INNER JOIN yb_gs_jsxx s ON f.blh = s.blh WHERE x.fyrq > DATE'2026-01-01' GROUP BY x.jlxh,m.fixmedins_hilist_id,m.fixmedins_hilist_name,s.jshid; |

6. 高阶进阶:JSON_ARRAYAGG 多列组装子对象(重点!追溯多字段数组)

实际生产追溯报文不是单字段 ,每条追溯记录包含追溯码、流转标识、最小包装类型、明细序号等多个字段,需要生成对象数组,这是大多数开发卡住的难点。

目标输出格式(完全贴合业务需求)

|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| json {"drug_trac_codg":"84105970269404504645","trdn_flag":"0","min_prcunt_type":"2","feedetl_sn":" "}, {"drug_trac_codg":"84105970269405220502","trdn_flag":"0","min_prcunt_type":"2","feedetl_sn":" "} |

核心原理

JSON_ARRAYAGG不支持直接多列聚合 ,正确写法:内层先用 JSON_OBJECT 组装单条多字段子对象,外层再用 JSON_ARRAYAGG 聚合为数组,19c 全版本稳定兼容。

可直接运行最简Demo

|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| sql SELECT JSON_ARRAYAGG( -- 内层:单条追溯记录 = 多字段JSON对象 JSON_OBJECT( KEY 'drug_trac_codg' VALUE NVL(t.trace_code,' '), KEY 'trdn_flag' VALUE NVL(t.trdn_flag,' '), KEY 'min_prcunt_type' VALUE NVL(t.min_prcunt_type,' '), KEY 'feedetl_sn' VALUE NVL(t.feedetl_sn,' ') ) ORDER BY t.trace_code ) AS trace_list FROM YK_TRACE_CODE t WHERE ROWNUM <=5; |

整合进医保完整报文(生产最终版)

嵌套进主报文结构,搭配之前的空值兜底、去重、空数组兼容逻辑,适配正式上线场景:

|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| sql SELECT JSON_OBJECT( KEY 'fylx' VALUE 1, KEY 'jlxh' VALUE x.jlxh, KEY 'yyxmbm' VALUE NVL(m.fixmedins_hilist_id, ''), KEY 'yyxmmc' VALUE NVL(m.fixmedins_hilist_name, '未匹配项目'), KEY 'fixmedins_bchno' VALUE '1-' || x.jlxh, KEY 'jshid' VALUE s.jshid, -- 多列组装追溯子对象数组,全套生产兜底逻辑 KEY 'trace_code_list' VALUE NVL( JSON_ARRAYAGG( DISTINCT JSON_OBJECT( KEY 'drug_trac_codg' VALUE NVL(t.trace_code,' '), KEY 'trdn_flag' VALUE NVL(t.trdn_flag,' '), KEY 'min_prcunt_type' VALUE NVL(t.min_prcunt_type,' '), KEY 'feedetl_sn' VALUE NVL(t.feedetl_sn,' ') ) ORDER BY t.trace_code ), JSON_ARRAY() -- 无数据返回空数组 \[\],不返回null ), KEY 'memo' VALUE ' ' ) AS insurance_upload_json FROM yf_mzfymx x LEFT JOIN yk_ypcd c ON c.ypcd = x.ypcd AND c.ypxh = x.ypxh LEFT JOIN yb_si_match m ON x.ypxh = m.ypxh AND x.ypcd = m.ypcd LEFT JOIN YK_TRACE_CODE t ON t.ypcd = x.ypcd AND t.ypxh = x.ypxh INNER JOIN yb_gs_fymx f ON f.blh = '202601091633432107' AND f.yzzh = 'CF' || x.cfsb AND f.yyxmbm = LPAD(x.YPXH,8,'0') || LPAD(x.YPCD,8,'0') INNER JOIN yb_gs_jsxx s ON f.blh = s.blh WHERE x.fyrq > DATE'2026-01-01' GROUP BY x.jlxh,m.fixmedins_hilist_id,m.fixmedins_hilist_name,s.jshid; |

关键踩坑说明

  1. 多列必须嵌套JSON_OBJECT:数组聚合函数只能包单值,多字段必须先组子对象;
  1. 统一空格兜底方案 :放弃高风险函数,全部字段 NVL(col,' ') 兼容所有19c版本;
  1. DISTINCT 自动去重:剔除重复追溯明细,保证报文唯一性;
  1. 空数组兜底强制开启 :无追溯数据返回 \[\],彻底杜绝 null 数组报错。

七、全文总结(生产极简准则)

结合 Oracle 19c 低版本兼容特性与医保、药品追溯生产规范,整理出一套可直接上线的 JSON 拼装标准准则,覆盖 99% 数据库JSON报文开发场景。

  1. 单层JSON构造 :使用标准 KEY ... VALUE ... 语法,数字自动识别为JSON数值类型,字符串自动包裹双引号,禁止尾部多余逗号。
  1. 多层嵌套构造 :子对象直接嵌套 JSON_OBJECT,是Oracle官方标准写法,全19c补丁版本兼容。
  1. 空值兼容终极方案 :19c低版本放弃不兼容的 NULL ON EMPTY、CHR(0),统一使用 NVL(字段,' ') 空格占位,彻底解决空字符串强制转null、特殊转义字符、接口拒收问题。
  1. 单列多行聚合数组 :通过 JSON_ARRAYAGG 聚合多行字段,搭配 DISTINCT 去重、ORDER BY 排序、外层 NVL(...,JSON_ARRAY()) 兜底,无数据返回空数组 \[\],杜绝null报错。
  1. 多列多行对象数组(核心重点) :JSON_ARRAYAGG 不支持直接多列聚合,必须内层嵌套 JSON_OBJECT,先组装单条多字段子对象、再聚合为数组,适配药品追溯复杂报文。
  1. 医保生产通用规范:所有业务字段统一NVL兜底,空值用空格占位、无明细返回空数组,完全满足政务/医保接口严格JSON格式校验。
  1. 高版本拓展方案 :新版Oracle可使用 JSON_TRANSFORM 后置修正JSON字段,代码更优雅规范。
相关推荐
晴天161 小时前
Agent 全栈学习笔记 1-Day14
数据库·笔记·学习
瀚高PG实验室1 小时前
几种因网络波动导致应用与数据库操作异常的现象
运维·网络·数据库·postgresql·瀚高数据库
一个天蝎座 白勺 程序猿1 小时前
SQL Server数据迁移金仓性能优化实战:从慢SQL到系统瓶颈,一套组合拳打下来
sql·oracle·性能优化·kingbasees
崖边看雾2 小时前
同步上下文管理器规则
开发语言·数据库
.柒宇.2 小时前
运维常见面试题_02_数据库
运维·数据库·sql·面试
名字还没想好☜10 小时前
Python f-string 进阶:数字格式化、对齐填充、调试 = 号与嵌套表达式
开发语言·数据库·python·字符串格式化·f-string
ltl10 小时前
Serverless 数据库弹性理论:Neon 与 Aurora Serverless v2
数据库
marvelyu15 小时前
每天10分钟学会OceanBase系列(Day 20):跨机房容灾实战——构建多数据中心高可用架构
java·大数据·数据库
ZCBUS实时计算15 小时前
金融证券实时数仓建设实践:轻量化实时计算平台落地,实现交易数据端到端秒级处理
大数据·数据库·数据仓库·金融·flink·dba·etl