{ Abhilash Kumar Bhattaram : Follow on LinkedIn }

Oracle Database and JSON

JSON is important because it offers a lightweight, human-readable format that is easy for both developers and machines to work with. Its language- and platform-independent nature makes it ideal for data interchange across diverse systems, especially in modern web services and APIs. JSON’s flexible structure supports complex, nested data, making it well-suited for semi-structured use cases like IoT, logging, and analytics. As a result, it has become the standard format for communication between front-end and back-end systems, enabling faster development and more dynamic application designs.

Now what does Oracle offer for JSON handling of data ?

  1. Native JSON Storage: Oracle 19c supports storing JSON in VARCHAR2, CLOB, and BLOB with validation using IS JSON constraints. Oracle 21c adds a dedicated JSON datatype with binary-optimized storage.
  2. Powerful Query Functions: Offers SQL/JSON functions like JSON_VALUE, JSON_QUERY, and JSON_EXISTS for efficient querying.
  3. Relational Mapping: JSON_TABLE lets you map JSON data to relational columns for easy integration with traditional SQL.
  4. Indexing & Performance: Supports functional and automatic indexing on JSON paths for optimized query performance.
  5. APIs & Tools: Includes RESTful access via ORDS and document-style APIs through SODA for flexible JSON operations.

Sample JSON Data Conversion

Using Oracle example schemas as available here here , we are making use of SH.COSTS table to export all the table contents into a JSON file

Checking Tables before JSON Conversion

Let us check the SH.COSTS Table , I see there are 82112 rows which I plan to make them into a JSON format

cdbl> select count(*) from SH.COSTS;

            COUNT(*)
--------------------
               82112

Elapsed: 00:00:00.00

-- Displaying first 10 rows 
cdbl> select * from SH.COSTS where rownum < 10;

             PROD_ID|TIME_ID          |            PROMO_ID|          CHANNEL_ID|           UNIT_COST|          UNIT_PRICE
--------------------|-----------------|--------------------|--------------------|--------------------|--------------------
                  14|03-MAR-1998 00:00|                 999|                   4|              863.64|             1176.23
                  15|02-JAN-1998 00:00|                 999|                   4|              846.71|             1013.99
                  15|10-JAN-1998 00:00|                 999|                   3|              875.22|              999.99
                  15|22-JAN-1998 00:00|                 999|                   3|              870.04|              999.99
                  15|05-FEB-1998 00:00|                 999|                   4|              887.35|             1003.99
                  15|16-FEB-1998 00:00|                 999|                   3|               908.8|             1003.49
                  18|18-JAN-1998 00:00|                 999|                   3|              1133.3|             1632.79
                  18|23-FEB-1998 00:00|                 999|                   3|             1155.97|             1655.65
                  18|10-MAR-1998 00:00|                 999|                   2|             1170.86|             1697.83

9 rows selected.


cdbl> @part    
Enter value for table_name: COSTS
Enter value for owner: SH

TABLE_NAME                              |TABLE_OWNER                             |PARTITION_NAME                          |TABLESPACE_NAME   |  SUBPARTITION_COUNT|            NUM_ROWS
----------------------------------------|----------------------------------------|----------------------------------------|------------------|--------------------|--------------------
COSTS                                   |SH                                      |COSTS_Q3_1998                           |USERS             |                   0|                4129
COSTS                                   |SH                                      |COSTS_Q4_1998                           |USERS             |                   0|                4577
COSTS                                   |SH                                      |COSTS_Q1_1999                           |USERS             |                   0|                5884
COSTS                                   |SH                                      |COSTS_Q4_1999                           |USERS             |                   0|                5060
COSTS                                   |SH                                      |COSTS_Q1_2000                           |USERS             |                   0|                3772
COSTS                                   |SH                                      |COSTS_Q2_2000                           |USERS             |                   0|                3715
COSTS                                   |SH                                      |COSTS_Q3_2000                           |USERS             |                   0|                4798
COSTS                                   |SH                                      |COSTS_Q4_2000                           |USERS             |                   0|                5088
COSTS                                   |SH                                      |COSTS_Q1_2001                           |USERS             |                   0|                7328
COSTS                                   |SH                                      |COSTS_Q2_2001                           |USERS             |                   0|                5882
COSTS                                   |SH                                      |COSTS_Q4_2001                           |USERS             |                   0|                9011
COSTS                                   |SH                                      |COSTS_Q3_2001                           |USERS             |                   0|                7545
COSTS                                   |SH                                      |COSTS_Q1_1998                           |USERS             |                   0|                4411
COSTS                                   |SH                                      |COSTS_Q2_1998                           |USERS             |                   0|                2397
COSTS                                   |SH                                      |COSTS_Q2_1999                           |USERS             |                   0|                4179
COSTS                                   |SH                                      |COSTS_Q3_1999                           |USERS             |                   0|                4336
COSTS                                   |SH                                      |COSTS_1995                              |USERS             |                   0|                   0
COSTS                                   |SH                                      |COSTS_1996                              |USERS             |                   0|                   0
COSTS                                   |SH                                      |COSTS_H1_1997                           |USERS             |                   0|                   0
COSTS                                   |SH                                      |COSTS_H2_1997                           |USERS             |                   0|                   0
COSTS                                   |SH                                      |COSTS_Q1_2002                           |USERS             |                   0|                   0
COSTS                                   |SH                                      |COSTS_Q1_2003                           |USERS             |                   0|                   0
COSTS                                   |SH                                      |COSTS_Q2_2002                           |USERS             |                   0|                   0
COSTS                                   |SH                                      |COSTS_Q2_2003                           |USERS             |                   0|                   0
COSTS                                   |SH                                      |COSTS_Q3_2002                           |USERS             |                   0|                   0
COSTS                                   |SH                                      |COSTS_Q3_2003                           |USERS             |                   0|                   0
COSTS                                   |SH                                      |COSTS_Q4_2002                           |USERS             |                   0|                   0
COSTS                                   |SH                                      |COSTS_Q4_2003                           |USERS             |                   0|                   0

28 rows selected.

Elapsed: 00:00:00.02
cdbl>

Using 19c JSON_OBJECT Functions

Below is the SQL file I am using to use the 19c JSON_OBJECT function which will help me spool the SQL output into a JSON file

There is large collection of JSON Functiosn which is avaialble is the JSON Developers guide here

NOTE : You would need to use a "set head off" and "set feedback off" as SQL output needs to be a clean JSON text , we will see in later section on how to verify this.

cdbl> ! cat gen_json.sql
set head off 
set feedback off 
set timing off
spool /home/oracle/costs.json
SELECT JSON_OBJECT(
         'PROD_ID' VALUE PROD_ID,
         'TIME_ID' VALUE TIME_ID,
         'PROMO_ID' VALUE CHANNEL_ID,
         'UNIT_COST' VALUE UNIT_COST,
         'UNIT_PRICE' VALUE UNIT_PRICE
       ) AS employee_json
FROM SH.COSTS;
spool off

Generating JSON data from Table

Now I would just run the gen_json.sql as above

cdbl> @gen_json.sql
.
.
.
"PROD_ID":129,"TIME_ID":"2001-10-17T00:00:00","PROMO_ID":3,"UNIT_COST":152.73,"UNIT_PRICE":189.58}
{"PROD_ID":130,"TIME_ID":"2001-10-14T00:00:00","PROMO_ID":2,"UNIT_COST":76.37,"UNIT_PRICE":98.89}
{"PROD_ID":135,"TIME_ID":"2001-12-02T00:00:00","PROMO_ID":4,"UNIT_COST":43.85,"UNIT_PRICE":51.85}
{"PROD_ID":140,"TIME_ID":"2001-10-02T00:00:00","PROMO_ID":4,"UNIT_COST":27.18,"UNIT_PRICE":32.08}
{"PROD_ID":144,"TIME_ID":"2001-12-10T00:00:00","PROMO_ID":4,"UNIT_COST":6.9,"UNIT_PRICE":7.63}
{"PROD_ID":147,"TIME_ID":"2001-10-18T00:00:00","PROMO_ID":3,"UNIT_COST":6.36,"UNIT_PRICE":8.07}
{"PROD_ID":148,"TIME_ID":"2001-12-24T00:00:00","PROMO_ID":3,"UNIT_COST":17.92,"UNIT_PRICE":23.14}
cdbl>

You can see above that the row contents are in JSON format spooled top /home/oracle/cust.json

Now to verify JSON with Unix commands

Oracle database has done it's part now it's upto other applications to understand this JSON format.

-- Including empty lines 
[oracle@machine1 ~]$ cat /home/oracle/costs.json | wc -l
82525

-- Escluding empty lines 
[oracle@machine1 ~]$ cat /home/oracle/costs.json | grep -v ^$ | wc -l
82112. ----->>> Now this matches my table count.

-- Making a clean JSON file 
cat /home/oracle/costs.json | grep -v ^$ > /home/oracle/costs_formatted.json

There is a utility called jq which is available to verify the JSON contents

[oracle@machine1 ~]$ jq empty /home/oracle/costs.json
[oracle@machine1 ~]$ 

NOTE : The empty output means your JSON is valid

Now moving on to filtering JSON data

[oracle@machine1 ~]$ jq 'select(.PROD_ID == 15)' /home/oracle/costs_formatted.json > /home/oracle/costs_formatted_PRODID_15.json
[oracle@machine1 ~]$ 

-- An example of the filtered data
[oracle@machine1 ~]$ tail -50 /home/oracle/costs_formatted_PRODID_15.json
}
{
  "PROD_ID": 15,
  "TIME_ID": "2001-11-16T00:00:00",
  "PROMO_ID": 2,
  "UNIT_COST": 786.6,
  "UNIT_PRICE": 926.79
}
{
  "PROD_ID": 15,
  "TIME_ID": "2001-12-02T00:00:00",
  "PROMO_ID": 4,
  "UNIT_COST": 746.2,
  "UNIT_PRICE": 877.9
}
{
  "PROD_ID": 15,
  "TIME_ID": "2001-12-16T00:00:00",
  "PROMO_ID": 3,
  "UNIT_COST": 801.1,
  "UNIT_PRICE": 914.38
}
{
  "PROD_ID": 15,
  "TIME_ID": "2001-12-16T00:00:00",
  "PROMO_ID": 4,
  "UNIT_COST": 746.2,
  "UNIT_PRICE": 877.9
}
{
  "PROD_ID": 15,
  "TIME_ID": "2001-12-26T00:00:00",
  "PROMO_ID": 3,
  "UNIT_COST": 801.1,
  "UNIT_PRICE": 914.38
}
{
  "PROD_ID": 15,
  "TIME_ID": "2001-10-16T00:00:00",
  "PROMO_ID": 2,
  "UNIT_COST": 791.91,
  "UNIT_PRICE": 929.71
}
{
  "PROD_ID": 15,
  "TIME_ID": "2001-10-03T00:00:00",
  "PROMO_ID": 4,
  "UNIT_COST": 754.86,
  "UNIT_PRICE": 889
}
[oracle@machine1 ~]$

Verifying filtered data with SQL and with jq

Here is the SQL version of JSON record count

dbl> select count(*) from SH.COSTS where PROD_ID=15;

            COUNT(*)
--------------------
                 874

cdbl>

Now checking the same with jq

[oracle@machine1 ]$ jq 'select(.PROD_ID == 15)' /home/oracle/costs_formatted.json | grep PROD_ID | wc -l 
874.  <<<---- 874 matches !!!

You can see now that the count 874 matches with SQL and with jq , there are a million such possibilities that each applications offers to convert raw SQL data to JSON format.

Hope you learnt something from this blog.