英文:
Creating nested json data from Oracle SQL
问题
{
"data": [
{
"ID": 1,
"NAME": "India",
"child": [
{
"ID": 3,
"NAME": "FINANCE",
"child": [
{
"ID": 5,
"NAME": "HR"
}
]
}
]
},
{
"ID": 2,
"NAME": "Canada",
"child": [
{
"ID": 4,
"NAME": "IT"
}
]
}
]
}
英文:
I am trying to create a nested json object using Oracle SQL. I am able to create JSON objects where the hierarchy level is predefined. In this case, it is dynamic and not able to find SQL or PLSQL solution for this nested data.
I have a table with below data,
I want the output as below using Oracle SQL or PLSQL
{
"data": [
{
"ID": 1,
"NAME": "India",
"child": [
{
"ID": 3,
"NAME": "FINANCE",
"child": [
{
"ID": 5,
"NAME": "HR"
}
]
}
]
},
{
"ID": 2,
"NAME": "Canada",
"child": [
{
"ID": 4,
"NAME": "IT"
}
]
}
]
}
Can anyone help me on this?
答案1
得分: 2
你可以创建一个递归函数:
CREATE FUNCTION generate_json(
i_parent_id IN NUMBER
) RETURN CLOB
IS
v_json CLOB;
BEGIN
SELECT JSON_ARRAYAGG(
JSON_OBJECT(
KEY 'ID' VALUE id,
KEY 'name' VALUE name,
KEY 'child' VALUE generate_json(id) FORMAT JSON ABSENT ON NULL
)
)
INTO v_json
FROM table_name
WHERE parent_id = i_parent_id;
RETURN v_json;
END;
/
对于示例数据:
CREATE TABLE table_name(id, name, parent_id) AS
SELECT 1, 'INDIA', 0 FROM DUAL UNION ALL
SELECT 2, 'CANADA', 0 FROM DUAL UNION ALL
SELECT 3, 'FINANCE', 1 FROM DUAL UNION ALL
SELECT 4, 'IT', 2 FROM DUAL UNION ALL
SELECT 5, 'HR', 3 FROM DUAL;
然后:
SELECT JSON_OBJECT(KEY 'data' VALUE generate_json(0)) AS json FROM DUAL;
输出:
JSON |
---|
{"data":"[{"ID":1,"name":"INDIA","child":[{"ID":3,"name":"FINANCE","child":[{"ID":5,"name":"HR"}]}]},{"ID":2,"name":"CANADA","child":[{"ID":4,"name":"IT"}]}]"} |
英文:
You can create a recursive function:
CREATE FUNCTION generate_json(
i_parent_id IN NUMBER
) RETURN CLOB
IS
v_json CLOB;
BEGIN
SELECT JSON_ARRAYAGG(
JSON_OBJECT(
KEY 'ID' VALUE id,
KEY 'name' VALUE name,
KEY 'child' VALUE generate_json(id) FORMAT JSON ABSENT ON NULL
)
)
INTO v_json
FROM table_name
WHERE parent_id = i_parent_id;
RETURN v_json;
END;
/
Which, for the sample data:
CREATE TABLE table_name(id, name, parent_id) AS
SELECT 1, 'INDIA', 0 FROM DUAL UNION ALL
SELECT 2, 'CANADA', 0 FROM DUAL UNION ALL
SELECT 3, 'FINANCE', 1 FROM DUAL UNION ALL
SELECT 4, 'IT', 2 FROM DUAL UNION ALL
SELECT 5, 'HR', 3 FROM DUAL;
Then:
SELECT JSON_OBJECT(KEY 'data' VALUE generate_json(0)) AS json FROM DUAL;
Outputs:
JSON |
---|
{"data":"[{"ID":1,"name":"INDIA","child":[{"ID":3,"name":"FINANCE","child":[{"ID":5,"name":"HR"}]}]},{"ID":2,"name":"CANADA","child":[{"ID":4,"name":"IT"}]}]"} |
答案2
得分: 0
没有函数:您可以创建一个视图(最外层的JSON_QUERY只是为了漂亮地打印)
WITH data (id, name, parent_id) as (
select 1, '印度', 0 from dual union all
select 2, '加拿大', 0 from dual union all
select 3, '金融', 1 from dual union all
select 4, 'IT', 2 from dual union all
select 5, '人力资源', 3 from dual
),
rel_hier( id, name, parent_id, lvl ) AS (
SELECT id, name, parent_id, 1
FROM data
WHERE parent_id = 0
UNION ALL
SELECT n.id, n.name, n.parent_id,
lvl + 1
FROM rel_hier h
JOIN data n on n.parent_id = h.id
)
SEARCH DEPTH FIRST BY id SET rn
, rel_hier_with_leadlag AS (
SELECT r.*
, LAG(lvl) OVER(ORDER BY rn) AS lag_lvl
, LEAD(lvl,1) OVER(ORDER BY rn) AS llead_lvl -- 我们需要知道最新的节点
, LEAD(lvl,1,1) OVER(ORDER BY rn) AS lead_lvl -- 对于最新的节点,我们需要使用1而不是NULL
, JSON_OBJECT(
'id' value id
, 'name' value name
ABSENT ON NULL
RETURNING CLOB
) js
FROM rel_hier r
)
SELECT
JSON_QUERY(
XMLCAST(
XMLAGG(
XMLELEMENT(e,
CASE WHEN rn = 1 THEN '[' END ||
CASE
WHEN lvl - lag_lvl = 1 THEN ',"children":[' -- 级别增加了一个,所以是子级,开始数组
WHEN lvl > 1 then ',' -- 不是第一级时添加
WHEN rn>1 AND parent_id = 0 THEN ',' -- 当根节点但不是第一个根节点时添加
END ||
SUBSTR(js, 1, LENGTH(js) - 1) || -- 删除最后一个大括号,因为我们正在控制子级
CASE
WHEN lvl >= lead_lvl then '}' || -- 级别与下一级相同或更大,因此关闭json_object
RPAD(' ', (lvl - lead_lvl) * 2 + 1, ']}') -- 添加所需数量的关闭数组/对象块
END ||
CASE WHEN llead_lvl IS NULL THEN ']' END -- 当最新节点
)
ORDER BY rn
)
AS CLOB
)
, '$' RETURNING CLOB PRETTY
) as js
FROM rel_hier_with_leadlag r;
英文:
Without a function: you can make a view of it (the outermost JSON_QUERY is just there for the pretty printing)
WITH data (id, name, parent_id) as (
select 1, 'INDIA', 0 from dual union all
select 2, 'CANADA', 0 from dual union all
select 3, 'FINANCE', 1 from dual union all
select 4, 'IT', 2 from dual union all
select 5, 'HR', 3 from dual
),
rel_hier( id, name, parent_id, lvl ) AS (
SELECT id, name, parent_id, 1
FROM data
WHERE parent_id = 0
UNION ALL
SELECT n.id, n.name, n.parent_id,
lvl + 1
FROM rel_hier h
JOIN data n on n.parent_id = h.id
)
SEARCH DEPTH FIRST BY id SET rn
, rel_hier_with_leadlag AS (
SELECT r.*
, LAG(lvl) OVER(ORDER BY rn) AS lag_lvl
, LEAD(lvl,1) OVER(ORDER BY rn) AS llead_lvl -- we need to know which the latest node
, LEAD(lvl,1,1) OVER(ORDER BY rn) AS lead_lvl -- for the latest node we need to use 1 instead of NULL
, JSON_OBJECT(
'id' value id
, 'name' value name
ABSENT ON NULL
RETURNING CLOB
) js
FROM rel_hier r
)
SELECT
JSON_QUERY(
XMLCAST(
XMLAGG(
XMLELEMENT(e,
CASE WHEN rn = 1 THEN '[' END ||
CASE
WHEN lvl - lag_lvl = 1 THEN ',"children":[' -- Level incremented by one, so child level, start array
WHEN lvl > 1 then ',' -- appending when not first level
WHEN rn>1 AND parent_id = 0 THEN ',' -- appending when a root node but not the first one
END ||
SUBSTR(js, 1, LENGTH(js) - 1) || -- remove last brace, as we are controlling children
CASE
WHEN lvl >= lead_lvl then '}' || -- Level same or greater than next level, so close json_object
RPAD(' ', (lvl - lead_lvl) * 2 + 1, ']}') -- and add as many closing array / object blocks as required
END ||
CASE WHEN llead_lvl IS NULL THEN ']' END -- when the latest node
)
ORDER BY rn
)
AS CLOB
)
, '$' RETURNING CLOB PRETTY
) as js
FROM rel_hier_with_leadlag r
;
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论