Skip to Main Content
  • Questions
  • JSON_TABLE, NESTED, and cartesian joins

Breadcrumb

Question and Answer

Chris Saxon

Thanks for the question, Phil.

Asked: November 30, 2023 - 12:05 am UTC

Last updated: December 04, 2023 - 4:01 pm UTC

Version: 19c

Viewed 1000+ times

You Asked

using the example data from https://oracle-base.com/articles/19c/sql-nested-clause-instead-of-json_table-19c

If I want the Address *and* the Contact Details, the intuitive thing would be to add another NESTED clause, like this

SELECT jt.*
FROM   json_documents
       NESTED data
         COLUMNS (FirstName, LastName, Job, Active,
           NESTED ContactDetails COLUMNS (Email, Phone),
           NESTED Address columns (Street, City, Country, Postcode)
         )
        jt;


However, what I end up with is

"FIRSTNAME" "LASTNAME" "JOB" "ACTIVE" "EMAIL" "PHONE" "STREET" "CITY" "COUNTRY" "POSTCODE"
"John" "Doe" "Clerk" "true" "" "" "99 My Street" "My City" "UK" "A12 34B"
"John" "Doe" "Clerk" "true" "john.doe@example.com" "44 123 123456" "" "" "" ""
"Jayne" "Doe" "Manager" "false" "" "" "100 My Street" "My City" "UK" "A12 34B"
"Jayne" "Doe" "Manager" "false" "jayne.doe@example.com" "" "" "" "" ""

I am getting one row with the first NESTED, and another with the other NESTED. Is it because NESTED is shorthand for JSON_TABLE, so it is creating 2 JSON_TABLEs, and hence I am getting a cartesian join? And is that what I *should* expect?

For the query to work correctly when I have more than one NESTED object, I have to forgo the NESTED clause for the 2nd level nesting and explicitly set out each column and its PATH, like this

SELECT j.id, jt.first_name, jt.last_name, jt.job, jt.email, jt.phone, jt.active
FROM   json_documents j NESTED data
         COLUMNS (first_name    VARCHAR2(50 CHAR)  PATH FirstName,
                  last_name     VARCHAR2(50 CHAR)  PATH LastName,
                  job           VARCHAR2(10 CHAR)  PATH Job,
                  email         VARCHAR2(100 CHAR) PATH ContactDetails.Email,
                  phone         VARCHAR2(50 CHAR)  PATH ContactDetails.Phone,
                  street        VARCHAR2(100 CHAR) PATH Address.Street,
                  city          VARCHAR2(50 CHAR)  PATH Address.City,
                  country       VARCHAR2(100 CHAR) PATH Address.Country,
                  postcode      VARCHAR2(50 CHAR)  PATH Address.Postcode,
                  active        VARCHAR2(5 CHAR)   PATH Active) jt;


What it comes down to is why does this

SELECT jt.*
FROM   json_documents
       NESTED data
         COLUMNS (FirstName, LastName, Job, Active,
           NESTED ContactDetails COLUMNS (Email, Phone)
         )
        jt;


or this

SELECT jt.*
FROM   json_documents
       NESTED data
         COLUMNS (FirstName, LastName, Job, Active,
           NESTED Address columns (Street, City, Country, Postcode)
         )
        jt;


work correctly and give me 2 rows

but this

SELECT jt.*
FROM   json_documents
       NESTED data
         COLUMNS (FirstName, LastName, Job, Active,
           NESTED ContactDetails COLUMNS (Email, Phone),
           NESTED Address columns (Street, City, Country, Postcode)
         )
        jt;


give me 4 rows?

and Chris said...

Yes, this is intentional.

Sibling nested columns are unioned together. So you'll get a row for each nested attribute that exists in the document. If the nested attributes are arrays, the total number of rows returned for each parent JSON object is the sum of the elements in these arrays (not the Cartesian product).

I have to forgo the NESTED clause for the 2nd level nesting and explicitly set out each column

You can mix and match simplified and extended syntax however you want!

To avoid the unioning effect, you can only have one nested clause. You can combine one NESTED column with others where you list the full path though. You can omit the data types for all the columns.

So you can write something like this:

SELECT *
FROM   json_documents j NESTED data
         COLUMNS (FirstName, LastName, Job VARCHAR2(10),
                  NESTED ContactDetails COLUMNS (Email, Phone),
                  street     PATH Address.Street,
                  postcode   PATH Address.Postcode,
                  active     PATH Active) jt;
                  
ID                                  FIRSTNAME    LASTNAME    JOB        STREET           POSTCODE    ACTIVE    EMAIL                    PHONE         
0BB1AD4639C5B69FE0636B00000A6B61    John         Doe         Clerk      99 My Street     A12 34B     true      john.doe@example.com     44 123 123456 
0BB1AD4639C6B69FE0636B00000A6B61    Jayne        Doe         Manager    100 My Street    A12 34B     false     jayne.doe@example.com    <null>        
0BB1AD4639C7B69FE0636B00000A6B61    <null>       <null>      <null>     <null>           <null>      <null>    <null>                   <null>       


For more on nested paths, see this blog post from Beda Hammerschmidt; Architect for JSON here at Oracle:

https://blogs.oracle.com/database/post/the-new-sqljson-query-operators-part5-json_table-nested-path-ordinality-column

Rating

  (1 rating)

More to Explore

JSON

Need more information on JSON? Check out the JSON dev guide for the Oracle Database