Oracle 21c 不支持跨多视图嵌套的 SELECT … FOR JSON,因其仅适配单表或简单 JOIN,而权限体系涉及 DBA_USERS、DBA_ROLE_PRIVS、DBA_SYS_PRIVS 等多层一对多关系,强行使用会丢失层级或触发 ORA-40458 错误;必须通过三层 JSON_OBJECT/JSON_ARRAYAGG 手动封装,并处理角色继承、Unicode 编码及导出截断等兼容性问题。
oracle 21c 原生支持 json,但 dba_sys_privs 等权限视图本身不输出 json 结构——必须手动构造层级关系并用 json_object / json_arrayagg 封装,否则导出的只是扁平化表格,无法体现“用户 → 角色 → 权限”的嵌套逻辑。
为什么不能直接 SELECT … FOR JSON?
Oracle 21c 的 SELECT … FOR JSON 仅适用于单表或简单 JOIN,而用户权限体系天然跨三张视图(DBA_USERS、DBA_ROLE_PRIVS、DBA_SYS_PRIVS),且存在一对多嵌套(一个用户有多个角色,每个角色又有多个系统权限)。强行用 FOR JSON 会丢失层级,或触发 ORA-40458: nested object or array not allowed in this context 错误。
- 必须先用子查询或 CTE 展开权限链,再逐层封装
-
JSON_OBJECT只能接受标量或预聚合的JSON_ARRAYAGG,不能直接嵌套未聚合的多行结果 - 角色继承关系(如
DBA角色隐含哪些权限)需额外查ROLE_SYS_PRIVS,FOR JSON不自动展开
正确构造 JSON 权限结构的三步写法
以用户 'SCOTT' 为例,目标是生成形如:{"username":"SCOTT","roles":[{"role_name":"CONNECT","sys_privs":["CREATE SESSION"]}]} 的结构:
- 最内层:用
JSON_ARRAYAGG(JSON_OBJECT(...))聚合每个角色下的系统权限(DBA_SYS_PRIVSWHEREGRANTEE = role_name) - 中间层:用
JSON_ARRAYAGG(JSON_OBJECT(...))聚合该用户所有角色(DBA_ROLE_PRIVSWHEREGRANTEE = 'SCOTT'),把上一步结果作为sys_privs字段值 - 最外层:用
JSON_OBJECT包裹用户名和角色数组,注意NULL ON NULL避免空角色导致整个 JSON 为 NULL
关键示例(精简版):
SELECT JSON_OBJECT(
'username' VALUE u.username,
'roles' VALUE JSON_ARRAYAGG(
JSON_OBJECT(
'role_name' VALUE r.granted_role,
'sys_privs' VALUE (
SELECT JSON_ARRAYAGG(
JSON_OBJECT('privilege' VALUE s.privilege, 'admin_option' VALUE s.admin_option)
)
FROM dba_sys_privs s
WHERE s.grantee = r.granted_role
) NULL ON NULL
)
)
) AS json_output
FROM dba_users u
JOIN dba_role_privs r ON u.username = r.grantee
WHERE u.username = 'SCOTT'
GROUP BY u.username;
导出时容易忽略的 JSON 兼容性陷阱
即使 SQL 构造出合法 JSON,spool 或 JDBC 导出仍可能破坏格式:
-
SET LINESIZE必须 ≥ 32767,否则长 JSON 字符串被截断换行,变成非法 JSON -
SET TRIMSPOOL ON缺失会导致每行末尾补空格,JSON 解析器报Unexpected token - 字段含 Unicode(如中文角色名)时,
spool默认字符集可能错乱,需在 SQL*Plus 启动前设set NLS_LANG=AMERICAN_AMERICA.AL32UTF8 - 若权限数据量大(如 DBA 用户),
JSON_ARRAYAGG可能超出 128KB 限制,需加ON OVERFLOW TRUNCATE或改用分页查询
真正难的不是语法,而是把角色继承、对象权限(DBA_TAB_PRIVS)、系统权限三层关系全塞进一个 JSON 对象里——稍不注意就会漏掉通过角色间接获得的权限,或者把不同角色的权限混成一团。动手前先确认 SELECT COUNT(*) FROM ROLE_SYS_PRIVS WHERE ROLE IN (...) 是否覆盖了你预期的隐含权限。


















