我们可以将参数传递给 Oracle 中的 JSON_VALUE

can we pass parameter to the JSON_VALUE in Oracle

我有一个从列中获取 json 格式数据的查询。我想通过从列中动态传递 field_name 来获取 json 的数据。

例如

SELECT SUBJECT_MARKS
FROM STUDENT
WHERE STUDENT_ID = 101

结果是:

{
    "English": "70",
    "Hindi": "80",
    "Maths": "90",
    "Science": "90",
    "Social": "85"
}

如果我想得到特定的学科分数,那么查询是:

SELECT JSON_VALUE(SUBJECT_MARKS,'$.Maths')
FROM STUDENT
WHERE STUDENT_ID = 101

现在的结果是:

90

现在我的要求是动态获取主题标记,同时在查询中将主题名称作为参数提供

SELECT JSON_VALUE(SUBJECT_MARKS,:pSubjectMarks)
FROM STUDENT
WHERE STUDENT_ID = 101

当我们将 :pSubjectMarks 作为 '$.Science' 时执行查询 然后它抛出错误消息

ORA-40454: path expression not a literal 40454. 00000 - "path expression not a literal" *Cause: The provided path expression was not a literal (a constant). *Action: Provide a constant path expression. Error at Line: 29 Column: 45

谁能帮我找到查询的解决方案 提前致谢

从 Oracle 12 开始(当添加了对 JSON 函数的支持时),您可以使用以下任一方法将 CASE 表达式中的所有可能选项列入白名单:

SELECT CASE :pSubjectMarks
       WHEN 'English' THEN JSON_VALUE(SUBJECT_MARKS,'$.English')
       WHEN 'Hindi'   THEN JSON_VALUE(SUBJECT_MARKS,'$.Hindi')
       WHEN 'Maths'   THEN JSON_VALUE(SUBJECT_MARKS,'$.Maths')
       WHEN 'Science' THEN JSON_VALUE(SUBJECT_MARKS,'$.Science')
       WHEN 'Social'  THEN JSON_VALUE(SUBJECT_MARKS,'$.Social')
       END as subject_marks
FROM   STUDENT s

或:

SELECT CASE :pSubjectMarks
       WHEN 'English' THEN english
       WHEN 'Hindi'   THEN hindi
       WHEN 'Maths'   THEN maths
       WHEN 'Science' THEN science
       WHEN 'Social'  THEN social
       END as subject_marks
FROM   STUDENT s
       CROSS APPLY JSON_TABLE(
         s.subject_marks,
         '$'
         COLUMNS (
           ENGLISH NUMBER PATH '$.English',
           HINDI   NUMBER PATH '$.Hindi',
           MATHS   NUMBER PATH '$.Maths',
           SCIENCE NUMBER PATH '$.Science',
           SOCIAL  NUMBER PATH '$.Social'
         )
       ) j
WHERE  STUDENT_ID = 101;

db<>fiddle here

您可以将要从中获取信息的主题构建到 EXECUTE IMMEDIATE 语句中。由于您所有的主题都是简单的字符串,您可以使用 DBMS_ASSERT 程序包来验证 p_subject_name 参数的输入,以防止发生任何 SQL 注入。

以下是有关如何构建程序的示例。

设置

CREATE TABLE students
AS
    SELECT 101                                                                                        AS student_id,
              EMPTY_CLOB ()
           || '{ "English": "70", "Hindi": "80", "Maths": "90", "Science": "90", "Social": "85" }'    AS subject_marks
      FROM DUAL;
DECLARE
    PROCEDURE print_subject_score (p_student_id students.student_id%TYPE, p_subject_name VARCHAR2)
    IS
        l_sql     VARCHAR2 (1000);
        l_score   VARCHAR2 (5);
    BEGIN
        l_sql :=
               'select json_value(subject_marks, ''$.'
            || DBMS_ASSERT.SIMPLE_SQL_NAME (p_subject_name)
            || ''') from students where student_id = :p_student_id';

        EXECUTE IMMEDIATE l_sql
            INTO l_score
            USING p_student_id;

        DBMS_OUTPUT.put_line (l_score);
    EXCEPTION
        WHEN NO_DATA_FOUND
        THEN
            --Student ID does not exist
            NULL;
    END;
BEGIN
    print_subject_score (p_student_id => 101, p_subject_name => 'English');
    print_subject_score (p_student_id => 101, p_subject_name => 'Test');
    print_subject_score (p_student_id => 102, p_subject_name => 'Maths');
END;
/

既然你可以访问JSON_VALUE函数,那么你的Oracle版本至少是12.1。此版本还提供对 SQL 查询中的本地 PL/SQL 声明的访问,您可以在其中完全动态访问 JSON 键。

因此您可以为此使用 JSON_OBJECT_T 数据类型。如您所见,路径作为列值(表达式,而不是文字)传递,您可以使用绑定变量代替表达式。

with function get_mark(
  p_marks in varchar2,
  p_subj in varchar2
)
return number
as
  json JSON_OBJECT_T;
begin
  json := JSON_OBJECT_T(p_marks);
  return json.get_String(p_subj);
end;

select
  t.column_value as subj,
  get_mark(SUBJECT_MARKS, t.column_value) as mark
from students
  cross join sys.odcivarchar2list(
    'English',
    'Hindi',
    'Maths', 
    'Science',
    'Social',
    'something'
  )  t
where student_id = 101
SUBJ      | MARK
:-------- | ---:
English   |   70
Hindi     |   80
Maths     |   90
Science   |   90
Social    |   85
something | null

db<>fiddle here