PL/SQL遍历动态JSON执行DML:多行编辑仅更新一行问题
解决PL/SQL函数无法处理多行JSON数据DML操作的问题
问题背景
为支持可编辑表格的应用编写PL/SQL函数,处理包含行数据增删改操作的JSON请求负载时,发现仅第一行数据被处理,后续行无任何操作,无报错但未达预期。请求JSON中,修改行含"Changed":1标识,删除行含"Deleted":1标识。
示例JSON请求体
{"Changes": [ {"id": "AR46","Changed": 1,"SESSION_ID": "963","NAME": "IMAGE_LOGO","VALUE": "","DESCRIPTION": "v1","TYPE": "IMAGE","PARAM_GROUP": "J","BLOB_VALUE": "oracle.sql.BLOB@2ba32","EDIT": "Edit","DOWNLOAD": "Download","CLOB_VALUE": "oracle.sql.CLOB@7fd86843","XML_VALUE": "","CREATE_DATE": "11.03.2022 13:04:26","_DefaultSort": ""}, {"id": "AR47","Changed": 1,"SESSION_ID": "963","NAME": "IMAGE_HPB_MEMO_FOOTER","VALUE": "","DESCRIPTION": "v2","TYPE": "IMAGE","PARAM_GROUP": "JASPER","BLOB_VALUE": "oracle.sql.BLOB@7621f9df","EDIT": "Edit","DOWNLOAD": "Download","CLOB_VALUE": "oracle.sql.CLOB@43e24152","XML_VALUE": "","CREATE_DATE": "11.03.2022 13:04:35","_DefaultSort": ""}, {"id": "AR48","Changed": 1,"SESSION_ID": "963","NAME": "IMAGE_HPB_MEMO_INVCRED","VALUE": "","DESCRIPTION": "v3","TYPE": "IMAGE","PARAM_GROUP": "JASPER","BLOB_VALUE": "oracle.sql.BLOB@762074f6","EDIT": "Edit","DOWNLOAD": "Download","CLOB_VALUE": "oracle.sql.CLOB@4a068001","XML_VALUE": "","CREATE_DATE": "11.03.2022 13:04:46","_DefaultSort": ""} ]}
原PL/SQL函数(仅支持单行操作)
create or replace function changesResources (p_data varchar2) return varchar2 IS l_nullEx exception; PRAGMA EXCEPTION_INIT(l_nullEx, -1400); p_rez varchar2(100); p_session_id number; p_name varchar2(100); p_value VARCHAR2(500); p_description VARCHAR2(1000); p_type VARCHAR2(100); p_param_group VARCHAR2(100); p_blob_value VARCHAR2(1000); p_clob_value VARCHAR2(1000); p_xml_value VARCHAR2(1000); p_create_date varchar2(50); l_json_obj JSON_OBJECT_T; l_json_arr JSON_ARRAY_T; Begin l_json_obj := JSON_OBJECT_T.PARSE(p_data); l_json_arr := l_json_obj.get_array('Changes'); FOR i IN 0..l_json_arr.get_size()-1 LOOP p_session_id := JSON_VALUE(l_json_arr.get(i).to_string(), '$.SESSION_ID'); p_name := JSON_VALUE(l_json_arr.get(i).to_string(), '$.NAME'); p_value := JSON_VALUE(l_json_arr.get(i).to_string(), '$.VALUE'); p_description := JSON_VALUE(l_json_arr.get(i).to_string(), '$.DESCRIPTION'); p_type := JSON_VALUE(l_json_arr.get(i).to_string(), '$.TYPE'); p_param_group := JSON_VALUE(l_json_arr.get(i).to_string(), '$.PARAM_GROUP'); p_blob_value := JSON_VALUE(l_json_arr.get(i).to_string(), '$.BLOB_VALUE'); p_clob_value := JSON_VALUE(l_json_arr.get(i).to_string(), '$.CLOB_VALUE'); p_xml_value := JSON_VALUE(l_json_arr.get(i).to_string(), '$.XML_VALUE'); p_create_date := JSON_VALUE(l_json_arr.get(i).to_string(), '$.CREATE_DATE'); IF JSON_VALUE(l_json_arr.get(i).to_string(), '$.Changed') = 1 THEN UPDATE BF_RESOURCES_CONF SET description = p_description, value=p_value, type = p_type, param_group = p_param_group, blob_value = utl_raw.cast_to_raw(p_blob_value), clob_value = TO_CLOB(p_clob_value), xml_value=p_xml_value, create_date = TO_DATE(p_create_date,'DD.MM.YYYY HH24:MI:SS') where session_id = p_session_id and name = p_name; p_rez := '1|success!'; return p_rez; ELSIF JSON_VALUE(l_json_arr.get(i).to_string(), '$.Deleted') = 1 THEN DELETE FROM BF_RESOURCES_CONF WHERE session_id = p_session_id and name = p_name; p_rez := '1|success!'; return p_rez; ELSE INSERT INTO BF_RESOURCES_CONF (session_id,name, value,description, type,param_group,blob_value,clob_value,xml_value,create_date) VALUES (p_session_id, p_name, p_value, p_description, p_type, p_param_group, utl_raw.cast_to_raw(p_blob_value),TO_CLOB(p_clob_value),p_xml_value,TO_DATE(p_create_date,'DD.MM.YYYY HH24:MI:SS')); p_rez := '1|success!'; return p_rez; END IF; END LOOP; EXCEPTION WHEN l_nullEx THEN p_rez := '-1|Columns SESSION_ID, NAME I CREATE_DATE have to contain values!'; RETURN p_rez; --WHEN OTHERS THEN -- p_rez := '-1|Error!'; -- RETURN p_rez; END changesResources ;
问题根源
原函数在循环处理每一行数据后立即执行return p_rez,导致循环仅执行一次就退出,后续行完全未被处理。
解决方案
1. 核心修改:移除循环内的return语句
将返回结果的逻辑移到循环结束后,确保所有行都被处理。
2. 优化JSON解析性能
避免每次将JSON元素转为字符串再用JSON_VALUE解析,直接使用JSON_OBJECT_T的方法获取字段值,提升效率。
3. 增加原子性与异常处理
加入保存点,任何行处理失败时回滚所有操作,保证数据一致性;同时返回详细的操作统计与错误信息。
以下是优化后的函数:
create or replace function changesResources (p_data varchar2) return varchar2 IS l_nullEx exception; PRAGMA EXCEPTION_INIT(l_nullEx, -1400); p_rez varchar2(200); l_success_count number := 0; l_total_count number := 0; l_json_obj JSON_OBJECT_T; l_json_arr JSON_ARRAY_T; l_row_obj JSON_OBJECT_T; -- 字段变量 p_session_id number; p_name varchar2(100); p_value VARCHAR2(500); p_description VARCHAR2(1000); p_type VARCHAR2(100); p_param_group VARCHAR2(100); p_blob_value VARCHAR2(1000); p_clob_value VARCHAR2(1000); p_xml_value VARCHAR2(1000); p_create_date varchar2(50); Begin l_json_obj := JSON_OBJECT_T.PARSE(p_data); l_json_arr := l_json_obj.get_array('Changes'); l_total_count := l_json_arr.get_size(); -- 开启保存点,保证操作原子性 SAVEPOINT sp_before_changes; FOR i IN 0..l_json_arr.get_size()-1 LOOP l_row_obj := TREAT(l_json_arr.get(i) AS JSON_OBJECT_T); -- 解析当前行字段 p_session_id := l_row_obj.get_number('SESSION_ID'); p_name := l_row_obj.get_string('NAME'); p_value := l_row_obj.get_string('VALUE'); p_description := l_row_obj.get_string('DESCRIPTION'); p_type := l_row_obj.get_string('TYPE'); p_param_group := l_row_obj.get_string('PARAM_GROUP'); p_blob_value := l_row_obj.get_string('BLOB_VALUE'); p_clob_value := l_row_obj.get_string('CLOB_VALUE'); p_xml_value := l_row_obj.get_string('XML_VALUE'); p_create_date := l_row_obj.get_string('CREATE_DATE'); IF l_row_obj.get_number('Changed') = 1 THEN UPDATE BF_RESOURCES_CONF SET description = p_description, value = p_value, type = p_type, param_group = p_param_group, blob_value = utl_raw.cast_to_raw(p_blob_value), clob_value = TO_CLOB(p_clob_value), xml_value = p_xml_value, create_date = TO_DATE(p_create_date,'DD.MM.YYYY HH24:MI:SS') WHERE session_id = p_session_id AND name = p_name; l_success_count := l_success_count + SQL%ROWCOUNT; ELSIF l_row_obj.get_number('Deleted') = 1 THEN DELETE FROM BF_RESOURCES_CONF WHERE session_id = p_session_id AND name = p_name; l_success_count := l_success_count + SQL%ROWCOUNT; ELSE INSERT INTO BF_RESOURCES_CONF (session_id,name, value,description, type,param_group,blob_value,clob_value,xml_value,create_date) VALUES (p_session_id, p_name, p_value, p_description, p_type, p_param_group, utl_raw.cast_to_raw(p_blob_value),TO_CLOB(p_clob_value),p_xml_value, TO_DATE(p_create_date,'DD.MM.YYYY HH24:MI:SS')); l_success_count := l_success_count + SQL%ROWCOUNT; END IF; END LOOP; -- 构建返回结果 p_rez := '1|Success! Processed ' || l_success_count || '/' || l_total_count || ' rows.'; RETURN p_rez; EXCEPTION WHEN l_nullEx THEN ROLLBACK TO SAVEPOINT sp_before_changes; p_rez := '-1|Error: Columns SESSION_ID, NAME or CREATE_DATE cannot be null!'; RETURN p_rez; WHEN OTHERS THEN ROLLBACK TO SAVEPOINT sp_before_changes; p_rez := '-1|Error: ' || SQLERRM; RETURN p_rez; END changesResources ;
关键优化点说明
- 移除循环内的
return语句,确保所有行被遍历处理。 - 使用
TREAT将数组元素转为JSON_OBJECT_T,通过专属方法解析字段,避免字符串转换的性能损耗。 - 增加操作统计,返回成功行数与总行数的对比信息。
- 加入保存点,异常发生时回滚所有操作,保证数据一致性。
- 捕获所有异常并返回具体错误信息,便于问题排查。
内容的提问来源于stack exchange,提问作者JurajC
相关产品推荐
相关产品推荐

