'can we pass parameter to the JSON_VALUE in Oracle

I have a query which fetches json format data from a column. i want to fetch the data of json by passing a field_name dynamically from the column.

for example

SELECT SUBJECT_MARKS
FROM STUDENT
WHERE STUDENT_ID = 101

result is:

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

If I want to get the particular subject marks, then the query is:

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

Now the result is:

90

Now my requirement is to get the SUBJECT MARKS dynamically while giving the subject name as parameter in the query

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

while execute the query when we give the :pSubjectMarks as '$.Science' then it is throwing the error message

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

Can anyone help me to find the solution for the query Thanks in advance



Solution 1:[1]

You could build the subject you want to get info from into an EXECUTE IMMEDIATE statement. Since all of your subjects are simple strings, you can use the DBMS_ASSERT package to validate the input of the p_subject_name parameter to prevent any SQL injection from happening.

Below is an example on how to build the procedure.

Setup

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;
/

Solution 2:[2]

From Oracle 12 (when support for JSON functions was added), you could whitelist all the possible options in a CASE expression using either:

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

or:

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

Solution 3:[3]

Since you have an access to the JSON_VALUE function, then your Oracle version is at least 12.1. This version also provides an access to the local PL/SQL declarations in SQL queries, where you have full dynamic access to JSON keys.

So you may use JSON_OBJECT_T data type for this. As you may see, path is passed as a column value (expression, not a literal), and you may use bind variable in place of an expression.

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

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 EJ Egyed
Solution 2 MT0
Solution 3 astentx