Skip to Main Content
  • Questions
  • JSON_TABLE functionality change from 12.1 to 19c

Breadcrumb

Question and Answer

Connor McDonald

Thanks for the question, Adam.

Asked: May 11, 2023 - 9:06 pm UTC

Last updated: February 28, 2024 - 5:42 am UTC

Version: 19.18

Viewed 1000+ times

You Asked

Apologies if my JSON terminology is not quite accurate, but hopefully the test case shows the issue.

We are in the process of upgrading DBs for a legacy in house application from 12.1.0.2 to 19.18.
We noticed during testing that when we are selecting sets of nested objects from json data using json_table, the behavior has changed when that data contains nulls.

In 12.1, records would only be returned if there was data; now on 19c a null in json returns a null row.

Test case:

select x.pol_num, x.pol_name
  from json_table('{data:[{"id":"abcdefg","sfid":"hijklmnop","audits":[{"policy_num":"10","policy_name":"Sally"},{"policy_num":"20","policy_name":"John"}]},' ||
                  '       {"id":"azclkjd","sfid":"sldkfjd","audits":null}]}', '$.data[ * ].audits[ * ]'
                  columns(id for ordinality,
                          pol_num     path '$.policy_num',
                          pol_name    path '$.policy_name')) x;


When run in 19c, this returns
POL_NUM POL_NAME
10 Sally
20 John
- -


When run in 12.1, this returns
POL_NUM POL_NAME
10 Sally
20 John


What we have been unable to determine from looking through documentation and working with Oracle Support is whether this change is expected behavior we will need to work around, or a bug.

with LiveSQL Test Case:

and Chris said...

The 19c behaviour is correct. The default for JSON_table at the table and column level is NULL ON ERROR. So the database generates a row for the null audits element and sets the value for the columns to null. You can set ERROR ON ERROR instead if you want to change this.

FWIW I'm not able to reproduce the 12.1 behaviour you see:

select * from v$version;

BANNER                                                                    CON_ID    
Oracle Database 12c EE High Perf Release 12.1.0.2.0 - 64bit Production            0 
PL/SQL Release 12.1.0.2.0 - Production                                            0 
CORE 12.1.0.2.0 Production                                                      0 
TNS for Linux: Version 12.1.0.2.0 - Production                                    0 
NLSRTL Version 12.1.0.2.0 - Production                                            0

select id, x.pol_num, x.pol_name 
  from json_table('{data:[{"id":"abcdefg","sfid":"hijklmnop","audits":[{"policy_num":"10","policy_name":"Sally"},{"policy_num":"20","policy_name":"John"}]},' || 
                  '       {"id":"azclkjd","sfid":"sldkfjd","audits":null}]}', '$.data[*].audits[*]' 
                  columns(id for ordinality, 
                          pol_num     path '$.policy_num' , 
                          pol_name    path '$.policy_name')) x;
/*
POL_NUM    POL_NAME    
10         Sally       
20         John        
<null>     <null>      
*/

select id, x.pol_num, x.pol_name 
  from json_table('{data:[{"id":"abcdefg","sfid":"hijklmnop","audits":[{"policy_num":"10","policy_name":"Sally"},{"policy_num":"20","policy_name":"John"}]},' || 
                  '       {"id":"azclkjd","sfid":"sldkfjd","audits":null}]}', '$.data[*].audits[*]' 
                  error on error
                  columns(id for ordinality, 
                          pol_num     path '$.policy_num' , 
                          pol_name    path '$.policy_name')) x;
                          
--ORA-40462: JSON_VALUE evaluated to no value

Rating

  (3 ratings)

Is this answer out of date? If it is, please let us know via a Comment

Comments

A reader, May 12, 2023 - 3:25 pm UTC

Fantastic, thank you so much for the prompt reply.

mathguy, May 13, 2023 - 4:33 pm UTC

In 12.1, records would only be returned if there was data

I believe this is where the confusion begins. "There was data" is ambiguous, as I will explain.

The observed behavior (which was indeed the same in 12.1 and later versions, as Chris demonstrated already) was correctly documented since 12.1, but the documentation was clarified even further in later versions. For 19 we can read this (emphasis added):

JSON_table_on_error_clause
Use this clause to specify the value returned by the function when errors occur:
NULL ON ERROR
.....
- If no match is found when the row path expression is evaluated, no rows are returned.
- Sets the default error behavior for all column expressions to NULL ON ERROR.

In your example, the row path expression is '$.data[ * ].audits[ * ]' and this is found in all rows. So far, "there is data". Now, if you are paying very close attention, you will observe that in your second row the nested object with key "audits" is found, but its value is not an array, it is the special value null. So it seems that there should still be no match. In this case "there is data" only because Oracle uses implicit array wrapping and unwrapping.

On the other hand, the column-level path expressions can't be matched in your second row. That error is handled by returning NULL instead.

You are thinking about "there is data" globally, combining the row-level expression, given as argument to JSON_TABLE, and the column path expressions given in the COLUMNS clause. But that is not how JSON_TABLE works.

For a quick illustration, compare the two examples below, which show exactly how the two different rules are applied. Running this on 12.2.0.1 but I am sure the behavior has been unchanged from 12.1 to 23 or whatever the current version is. The behavior you think you remember from 12.1 is what you see in the second example; but notice that in that example, the row path expression can't be matched. In the first example only the column path expression can't be matched. The row path expression can, which is why we do get a row (with NULL in the column because there is no match for the column path expression).

set null NULL
column val format a10

select val
from   json_table('{values:null}', '$' columns (val path '$.data'));

VAL       
----------
NULL

select val
from   json_table('{values:null}', '$.data' columns (val path '$'));

no rows selected

Chris Saxon
May 15, 2023 - 1:57 pm UTC

Good explanation, thanks

query using json_table in 19c Vs 23c

Rajeshwaran Jeyabal, February 27, 2024 - 1:58 am UTC

the following query when run from 19c returns the output correct, but not in 23c (23.2)

so what does the "count()" and "size()" item method denotes in json path expression? are they "both" same for an "array" element

19c
demo@ATP19C> with datas(x) as (
  2     select q'~{
  3                      "items":[
  4                          {"subject":"one","replies":[ ]},
  5                          {"subject":"two","replies":[ {"text":"foo"} ]}
  6                      ]
  7                  }~' from dual )
  8  select jt.*
  9  from datas, json_table( datas.x,'$.items[*]'
 10     columns( subject varchar2(10) path '$.subject',
 11             c1 varchar2(20) format json path '$.replies',
 12             c2 number path '$.replies.size()',
 13             c3 number path '$.replies.count()') ) jt ;

SUBJECT    C1                           C2         C3
---------- -------------------- ---------- ----------
one        []                            0          0
two        [{"text":"foo"}]              1          1

demo@ATP19C>

23c

demo@FREEPDB1> with datas(x) as (
  2     select q'~{
  3                      "items":[
  4                          {"subject":"one","replies":[ ]},
  5                          {"subject":"two","replies":[ {"text":"foo"} ]}
  6                      ]
  7                  }~' from dual )
  8  select jt.*
  9  from datas, json_table( datas.x,'$.items[*]'
 10     columns( subject varchar2(10) path '$.subject',
 11             c1 varchar2(20) format json path '$.replies',
 12             c2 number path '$.replies.size()',
 13             c3 number path '$.replies.count()') ) jt ;

SUBJECT    C1                           C2         C3
---------- -------------------- ---------- ----------
one        []                            0          2
two        [{"text":"foo"}]              1          2

Connor McDonald
February 28, 2024 - 5:42 am UTC

progressing this internally

More to Explore

SQL

The Oracle documentation contains a complete SQL reference.