JSON Path Expressions

This section describes the elements from the SQL/JSON path language that are supported in Exasol.

The parts of the SQL/JSON path language that are not mentioned in this section are not supported.

JSON path modes and prefixes

The SQL/JSON path language supports both strict and lax JSON syntax using the prefixes strict and lax. Exasol only supports the lax prefix. The lax prefix can be added to any path in Exasol, but it has no effect on the query result.

Example:
CREATE OR REPLACE TABLE json_input(id INT, json VARCHAR(2000000));
INSERT INTO json_input VALUES(1, '{"firstname" : "Claire", "lastname" : "Doe"}');
INSERT INTO json_input VALUES(2, '{"firstname" : "John", "age" : 20}');

// SELECT statement 1
SELECT id, JSON_VALUE(json, '$.lastname') AS RESULT_VALUE FROM json_input;

// SELECT statement 2
SELECT id, JSON_VALUE(json, 'lax $.lastname') AS RESULT_VALUE FROM json_input;

Both SELECT statements will return the same result:

ID RESULT_VALUE
1 Doe
2 NULL

Variables

The SQL/JSON path language supports several variables. Exasol supports arbitrary expressions such as json_path_expr, which means it does not have to support the PASSING clause and JSONpath named variables.

The '@' variable is not supported in Exasol.

JSONpath context variable

The context variable '$' is the root element in a JSON path.

Example:
CREATE OR REPLACE TABLE json_input(id INT, json VARCHAR(2000000));
INSERT INTO json_input VALUES(1, 'true');
INSERT INTO json_input VALUES(2, 'false');
INSERT INTO json_input VALUES(3, 'null');
INSERT INTO json_input VALUES(4, '42');
INSERT INTO json_input VALUES(5, '1.0');
INSERT INTO json_input VALUES(6, '"Exasol"');

SELECT id, JSON_VALUE(json, '$') AS RESULT_VALUE FROM json_input;

Result:

ID RESULT_VALUE
1 true
2 false
3 NULL
4 42
5 1.0
6 Exasol

Accessors

Accessors are used to navigate through a JSON structure.

Member accessor

A member accessor gives access to a JSON object member by its name. You can concatenate an arbitrary number of names to access nested objects.

The member accessor is case sensitive. If a name starts with the character $, or if the name contains special characters (including spaces), you must enclose the name with double quotes.

The member accessor adheres to the lexical grammar of the ECMAScript language.

Example:
CREATE OR REPLACE TABLE json_input(id INT, json VARCHAR(2000000));
INSERT INTO json_input VALUES(1, '{"customer" : { "first name" : "Ann" } }');
INSERT INTO json_input VALUES(2, '{"first name" : "Adam" }');
INSERT INTO json_input VALUES(4, '{"First name" : "Luke" }');
INSERT INTO json_input VALUES(5, '{"$firstname" : "Sue" }');
INSERT INTO json_input VALUES(6, '{"Full name" : "John Doe" }');

SELECT id,
    JSON_VALUE(json, '$.customer."first name"') AS CUSTOMER_FIRST_NAME,
    JSON_VALUE(json, '$."first name"') AS "first name",
    JSON_VALUE(json, '$.First name') AS "First name",
    JSON_VALUE(json, '$."$firstname"') AS "$firstname",
    JSON_VALUE(json, '$."Full name"') AS "Full name"
FROM json_input;

Result:

ID CUSTOMER_FIRST_NAME first name First name $firstname Full name
1 Ann NULL NULL NULL NULL
2 NULL Adam NULL NULL NULL
3 NULL NULL Luke NULL NULL
4 NULL NULL NULL Sue NULL
5 NULL NULL NULL NULL John Doe

Wildcard member accessor

You can use the wildcard member accessor * to select all JSON object members in the path.

Example:
CREATE OR REPLACE TABLE json_input(id INT, json VARCHAR(2000000));
INSERT INTO json_input VALUES(1, '{"customer" : { "first name" : "Ann" } }');
INSERT INTO json_input VALUES(2, '{"customer" : { "first name" : "John" } }');
INSERT INTO json_input VALUES(3, '{"customer" : { "first name" : "Ann", "last name" : "Doe" } }');

SELECT id,
       JSON_VALUE(json, '$.*.first name') AS FIRST_NAME,
       JSON_VALUE(json, '$.*.*') AS DOUBLE_ASTERISK,
       JSON_VALUE(json, '$.*.last name') AS LAST_NAME
FROM json_input;

Result:

ID FIRST_NAME DOUBLE_ASTERISK LAST_NAME
1 Ann Ann NULL
2 John John NULL
3 Ann NULL Doe

Element accessor

You can use an element accessor to select one or more indexes from a JSON array.

Exasol supports access to single elements as well as ranges. You can also combine single element access with range access. However, Exasol does not support overlapping or unordered indexes.

Example 1
CREATE OR REPLACE TABLE json_input(id INT, json VARCHAR(2000000));
INSERT INTO json_input VALUES(1, '["First", "Second", "Third"]');
INSERT INTO json_input VALUES(2, '[["First", "Second"], ["Third"]]');
INSERT INTO json_input VALUES(3, '[1, 2, 3, 4, {"name" : "Ann"}, 6]');

SELECT id,
    JSON_VALUE(json, '$[0]') AS FIRST_ELEMENT,
    JSON_VALUE(json, '$[2]') AS THIRD_ELEMENT,
    JSON_VALUE(json, '$[0][1]') AS NESTED_ELEMENT,
    JSON_VALUE(json, '$[4].name') AS NAME,
    JSON_VALUE(json, '$[1, 2 to 3, 4 to 5, 6].name') AS NAME_WITH_RANGE
FROM json_input;

Result:

ID FIRST_ELEMENT THIRD_ELEMENT NESTED_ELEMENT NAME NAME_WITH_RANGE
1 First Third NULL NULL NULL
2 NULL NULL Second NULL NULL
3 1 3 NULL Ann Ann
Example 2
SELECT id, JSON_VALUE(json, '$[0,0]') FROM json_input;
-- Returns error: data exception - invalid JSON path: array indices overlap

SELECT id, JSON_VALUE(json, '$[0 to 5, 1 to 7]') FROM json_input;
--returns error: data exception - invalid JSON path: array indices overlap

SELECT id, JSON_VALUE(json, '$[3, 1]') FROM json_input;
--returns error: data exception - invalid JSON path: array indices not in ascending order

Wildcard element accessor

The wildcard element accessor [*] gives access to all elements in an array.

Because Exasol always uses lax path mode (see JSON path modes and prefixes), you do not have to specify the wildcard element accessor for a single array, only for nested arrays. If the wildcard element accessor is applied to a JSON value that is not an array, the lax mode automatically wraps the value into an array.

Example:
CREATE OR REPLACE TABLE json_input(id INT, json VARCHAR(2000000));
INSERT INTO json_input VALUES(1, '{ "customers" : [ { "name" : "John" }, { "first name" : "Eve" } ] }');
INSERT INTO json_input VALUES(2, '{ "customers" : [ null, [ { "name" : "John" }, { "first name" : "Eve" } ] ] }');

SELECT id,
       JSON_VALUE(json, '$.customers[*].name') AS WILDCARD,
       JSON_VALUE(json, '$.customers.name') AS WITHOUT_WILDCARD,
       JSON_VALUE(json, '$.customers.name[*]') AS NON_ARRAY_WILDCARD,
       JSON_VALUE(json, '$.customers[*][*].name[*]') AS DOUBLE_WILDCARD
FROM json_input;

Result:

ID WILDCARD WITHOUT_WILDCARD NON_ARRAY_WILDCARD DOUBLE_WILDCARD
1 John John John John
2 John NULL NULL John

Functions

You can call functions on an SQL/JSON value defined by a path. Exasol supports the functions type() and size().

Function type()

Returns the type of the SQL/JSON value defined by a path. The return type is (VAR)CHAR.

Examples
CREATE OR REPLACE TABLE json_input(id INT, json VARCHAR(2000000));
INSERT INTO json_input VALUES(1, NULL);
INSERT INTO json_input VALUES(2, '{"name" : "John"}');
INSERT INTO json_input VALUES(3, '[{"name" : "John"}]');
INSERT INTO json_input VALUES(4, 'true');
INSERT INTO json_input VALUES(5, 'false');
INSERT INTO json_input VALUES(6, 'null');
INSERT INTO json_input VALUES(7, '42');
INSERT INTO json_input VALUES(8, '1.0');
INSERT INTO json_input VALUES(9, '"Exasol"');

SELECT id, JSON_VALUE(json, '$.type()') AS JSON_RESULT
FROM json_input;

Result:

ID JSON_RESULT
1 NULL
2 object
3 array
4 boolean
5 boolean
6 null
7 number
8 number
9 string

Function size()

Returns the size of a JSON array. For all other JSON values it returns 1.

Examples
CREATE OR REPLACE TABLE json_input(id INT, json VARCHAR(2000000));
INSERT INTO json_input VALUES(1, '{"first name" : "Ann", "last name" : "Smith"}');
INSERT INTO json_input VALUES(2, '["First", "Second", "Third"]');
INSERT INTO json_input VALUES(3, '[["First", "Second"], ["Third"]]');
INSERT INTO json_input VALUES(4, 'true');
INSERT INTO json_input VALUES(5, 'false');
INSERT INTO json_input VALUES(6, 'null');
INSERT INTO json_input VALUES(7, '42');
INSERT INTO json_input VALUES(8, '1.0');
INSERT INTO json_input VALUES(9, '"Exasol"');

SELECT id, json, JSON_VALUE(json, '$.size()') AS FUNCTION_SIZE
FROM json_input;

Result

ID JSON FUNCTION_SIZE
1 {"first name" : "Ann", "last name" : "Smith"} 1
2 ["First", "Second", "Third"] 3
3 ["First", "Second", "Third"] 2
4 true 1
5 false 1
6 null 1
7 42 1
8 1.0 1
9 "Exasol" 1

Exasol extensions

For manual exploration of JSON data and for debugging purposes, Exasol provides several extensions to the SQL/JSON path language. The extensions in this category are specific to Exasol and not part of the SQL standard.

Function json()

Converts a JSON value (or values) specified by a path prefix into a JSON string. A possible application is the extraction of complete JSON objects or JSON arrays as (VAR)CHAR.

The json() function removes whitespace characters between JSON elements from the original JSON input.

For additional information, see also JSON_VALUE.

Example:
CREATE OR REPLACE TABLE json_input(id INT, json VARCHAR(2000000));
INSERT INTO json_input VALUES(1, '{ "customers" : [ { "name" : "John Doe" }, { "first name" : "Eve" } ] }');
INSERT INTO json_input VALUES(2, '["First", "Second", "Third"]');
INSERT INTO json_input VALUES(3, '"Exasol"');

SELECT id,
       JSON_VALUE(json, '$.json()') AS COMPLETE_JSON,
       JSON_VALUE(json, '$.customers.json()') AS JSON_ARRAY,
       JSON_VALUE(json, '$.customers[0].json()') AS JSON_OBJECT
FROM json_input;

Result:

ID COMPLETE_JSON JSON_ARRAY JSON_OBJECT
1 {"customers":[{"name":"John Doe"},{"first name":"Eve"}]} [{"name":"John Doe"},{"first name":"Eve"}] {"name":"John Doe"}
2 ["First","Second","Third"] NULL NULL
3 "Exasol" NULL NULL

Function path()

Returns the actual path that Exasol uses as (VAR)CHAR. This can help to debug paths with wildcards.

Example:
CREATE OR REPLACE TABLE json_input(id INT, json VARCHAR(2000000));
INSERT INTO json_input VALUES(1, '{ "customers" : [ { "name" : "John" } ] }');
INSERT INTO json_input VALUES(2, '{ "name list" : [ { "name" : "Ann" } ] }');
INSERT INTO json_input VALUES(3, '{ "customers" : [ null, null, { "name" : "Smith" } ] }');
INSERT INTO json_input VALUES(4, '{ "telephone list" : [ { "number" : "+49123456789" } ] }');

SELECT id,
       JSON_VALUE(json, '$.*.*') AS JSON_RESULT,
       JSON_VALUE(json, '$.*.*.path()') AS PATH_FUNCTION
FROM json_input;

Result:

ID JSON_RESULT PATH_FUNCTION
1 John $.customers[0].name
2 Ann $."name list"[0].name
3 Smith $.customers[2].name
4 +49123456789 $."telephone list"[0].number

Function jsonpath()

Determines the actual path that Exasol uses and splits it into an array of accessors. The function returns a (VAR)CHAR result that contains a JSON array with the identified accessors of the path.

Example:
CREATE OR REPLACE TABLE json_input(id INT, json VARCHAR(2000000));
INSERT INTO json_input VALUES(1, '{ "customers" : [ { "name" : "John" } ] }');
INSERT INTO json_input VALUES(2, '{ "name list" : [ { "name" : "Ann" } ] }');
INSERT INTO json_input VALUES(3, '{ "customers" : [ null, null,  { "name" : "Smith" } ] }');
INSERT INTO json_input VALUES(4, '{ "telephone list" : [ { "number" : "+49123456789" } ] }');

SELECT id,
    JSON_VALUE(json, '$.*.*') AS JSON_RESULT,
    JSON_VALUE(json, '$.*.*.path()') AS PATH_FUNCTION,
    JSON_VALUE(json, '$.*.*.jsonpath()') AS JSONPATH_FUNCTION
FROM json_input;

Result:

ID JSON_RESULT PATH_FUNCTION JSONPATH_FUNCTION
1 John $.customers[0].name ["customers",0,"name"]
2 Ann $."name list"[0].name ["name list",0,"name"]
3 Smith $.customers[2].name ["customers",2,"name"]
4 +49123456789 $."telephone list"[0].number ["telephone list",0,"number"]

Function name()

Returns the name of a name/value pair defined in a JSON object.

Example:
CREATE OR REPLACE TABLE json_input(id INT, json VARCHAR(2000000));
INSERT INTO json_input VALUES(1, '{ "customers" : [ { "first name" : "John" } ] }');
INSERT INTO json_input VALUES(2, '{ "telephone list" : [ { "number" : "+49123456789" } ] }');

SELECT id,
       JSON_VALUE(json, '$.*.name()') AS JSON_NAME,
       JSON_VALUE(json, '$.*.*.name()') AS NESTED_JSON_NAME,
       JSON_VALUE(json, '$.*.*') AS NESTED_JSON_VALUE
FROM json_input;

Result:

ID JSON_NAME NESTED_JSON_NAME NESTED_JSON_VALUE
1 customers first name John
2 telephone list number +49123456789

Function id()

Prints a unique identifier for each JSON element. The identifier is only unique within one JSON input.

Example:
CREATE OR REPLACE TABLE json_input(id INT, json VARCHAR(2000000));
INSERT INTO json_input VALUES(1, '[{"first name" : "Nick"}, {"first name" : "Eve"}]');
INSERT INTO json_input VALUES(2, '[{"first name" : "Ann"}, {"last name" : "Smith", "first name" : "John"}]');

SELECT id,
    JSON_VALUE(json, '$[0].id()') AS ELEMENT_ID_0,
    JSON_VALUE(json, '$[0]."first name".id()') AS NAME_ID_0,
    JSON_VALUE(json, '$[0]."first name"') AS VALUE_ID_0,
    JSON_VALUE(json, '$[1].id()') AS ELEMENT_ID_1,
    JSON_VALUE(json, '$[1]."first name".id()') AS NAME_ID_1,
    JSON_VALUE(json, '$[1]."first name"') AS VALUE_ID_1
FROM json_input;

Result:

ID ELEMENT_ID_0 NAME_ID_0 VALUE_ID_0 ELEMENT_ID_1 NAME_ID_1 VALUE_ID_1
1 1 3 Nick 4 6 Eve
2 1 3 Ann 4 8 John

Function position()

Returns the position of an element within an array.

The index of the first element in an array is 0 (zero). For elements that are not part of an array, the function also returns 0. This can help in identifying specific data elements.

Example 1
CREATE OR REPLACE TABLE json_input(id INT, json VARCHAR(2000000));
INSERT INTO json_input VALUES(1, '[{"first name" : "Nick", "name" : "foo"}, {"first name" : "Eve"}]');
INSERT INTO json_input VALUES(2, '{"company" : "Exasol"}');
INSERT INTO json_input VALUES(3, '"Exasol"');

SELECT id,
       JSON_VALUE(json, '$[1].position()') AS POSITION_1,
       JSON_VALUE(json, '$[1]."first name".position()') AS FIRST_NAME_POSITION,
       JSON_VALUE(json, '$.position()') AS CONTEXT_POSITION
FROM json_input;

Result:

ID POSITION_1 FIRST_NAME_POSITION CONTEXT_POSITION
1 1 0 0
2 NULL NULL 0
3 NULL NULL 0
Example 2
CREATE OR REPLACE TABLE json_input(id INT, json VARCHAR(2000000));
INSERT INTO json_input VALUES(1, '{"customers" : [{"name" : "Nick"}, {"name" : "Eve"}]}');
INSERT INTO json_input VALUES(2, '{"customers" : [{"name" : "Ann"}, {"name" : "John"}]}');

SELECT id,
       JSON_EXTRACT(json, '$.customers#.position()', '$.customers#.name')
           EMITS (JSON_POSITION INT, NAME VARCHAR(100))
FROM json_input;

Result:

ID JSON_POSITION NAME
1 0 Nick
1 1 Eve
2 0 Ann
2 1 John

Function toarray()

Returns all SQL/JSON values identified by the path as an array. This is useful to identify paths that return more than one value.

Example:
CREATE OR REPLACE TABLE json_input(id INT, json VARCHAR(2000000));
INSERT INTO json_input VALUES(1, '{"customers" : [{"name" : "Nick"}, {"name" : "Eve"}]}');
INSERT INTO json_input VALUES(2, '{"customers" : [{"name" : "Ann"}], "family" : [{"name" : "John"}]}');
INSERT INTO json_input VALUES(3, '{"contacts" : {"name" : "Chris", "name" : "Kate"}}');
INSERT INTO json_input VALUES(4, '"Exasol"');

SELECT id,
       JSON_VALUE(json, '$.customers.name.json().toarray()') as CUSTOMERS_AS_JSON,
       JSON_VALUE(json, '$.customers[*].json().toarray()') as CUSTOMER_ENTRIES,
       JSON_VALUE(json, '$.*.name.toarray()') as ALL_NAMES,
       JSON_VALUE(json, '$.*.name.json().toarray()') as ALL_NAMES_AS_JSON,
       JSON_VALUE(json, '$.toarray()') as CONTEXT_AS_ARRAY
FROM json_input;

Result:

ID CUSTOMERS_AS_JSON CUSTOMER_ENTRIES ALL_NAMES ALL_NAMES_AS_JSON CONTEXT_AS_ARRAY
1 ["Nick","Eve"] [{"name":"Nick"},{"name":"Eve"}] [Nick,Eve] ["Nick","Eve"] NULL
2 ["Ann"] [{"name":"Ann"}] [Ann,John] ["Ann","John"] NULL
3 [] [] [Chris,Kate] ["Chris","Kate"] NULL
4 [] [] [] [] [Exasol]

Multi-level wildcard accessor

You can use this accessor to select all leaf values of a JSON input at once.

Example:
CREATE OR REPLACE TABLE json_input(id INT, json VARCHAR(2000000));
INSERT INTO json_input VALUES(1, '{"customers" : [{"name" : "Nick"}, {"name" : "Eve"}]}');
INSERT INTO json_input VALUES(2, '{"customers" : [{"name" : "Ann"}], "family" : [{"name" : "John"}]}');
INSERT INTO json_input VALUES(3, '{"contacts" : {"name" : "Chris", "name" : "Kate"}, "telephone list" : [ { "number" : "+49123456789" } ] } ');
INSERT INTO json_input VALUES(4, '"Exasol"');
INSERT INTO json_input VALUES(5, '[1, 2, 3, 4, 5, {"measurement" : {"run" : "first run", "value" : 42.0}}]');

SELECT id, JSON_VALUE(json, '$.**.json().toarray()') as CUSTOMERS_AS_JSON
FROM json_input;

Result:

ID CUSTOMERS_AS_JSON
1 ["Nick","Eve"]
2 ["Ann","John"]
3 ["Chris","Kate","+49123456789"]
4 ["Exasol
5 [1,2,3,4,5,"first run",42.0]

JSON_EXTRACT extensions

JSON_EXTRACT is an Exasol-specific function that converts JSON into a relational format during ETL jobs. For this purpose, there are several path extensions that are only available in JSON_EXTRACT.

For more details, see JSON_EXTRACT.

Extract multiple rows from nested data

You can use JSON_EXTRACT to extract several rows from a single JSON input. If a path specifies more than one JSON value, it is possible to split the values into several rows by using the # path extension. However, a single JSON_EXTRACT call can only split up paths inside the same hierarchy chain. It is not possible to split up two independent paths within the same JSON_EXTRACT call.

Example 1
CREATE OR REPLACE TABLE json_input2(id INT, json VARCHAR(2000000));
INSERT INTO json_input2 VALUES(1, '{"data" : [{"country" : "United Kingdom", "customers" : [{"name" : "John Doe", "phone numbers" : [{"type" : "private", "number" : "+4412345678"}]}]}]}');
INSERT INTO json_input2 VALUES(2, '{"data" : [{"country" : "Germany", "customers" : [{"name" : "Hans Meier", "phone numbers" : [{"type" : "private", "number" : "+4912345678"}, {"type" : "mobile", "number" : "+49987654321"}]} ,{"name" : "Achim Schmidt", "phone numbers" : [{"type" : "private", "number" : "+49214365879"}, {"type" : "mobile", "number" : "+4989674523"}]}]}]}');

SELECT id, 
       JSON_EXTRACT(json,
            '$.data.country'
            '$.data.customers#.name'
            '$.data.customers#."phone numbers"#.type'
            '$.data.customers#."phone numbers"#.number'
        )
        EMITS(
              country VARCHAR(100), 
              name VARCHAR(100), 
              phone_number_type VARCHAR(100), 
              phone_number VARCHAR(100)
        )
FROM json_input2
ORDER BY id, name, phone_number;

Result:

ID COUNTRY NAME PHONE_NUMBER_TYPE PHONE_NUMBER
1 United Kingdom John Doe private +4412345678
2 Germany Achim Schmidt private +49214365879
2 Germany Achim Schmidt mobile +4989674523
2 Germany Hans Meier private +4912345678
2 Germany Hans Meier mobile +49987654321
Example 2
CREATE OR REPLACE TABLE json_input(id INT, json VARCHAR(2000000));
INSERT INTO json_input VALUES(1, '{"customers" : {"name" : "Ann"}, "family" : {"name" : "Michael"}}');
INSERT INTO json_input VALUES(2, '{"customers" : [{"name" : "Eve"}, {"name" : "Joe"}], "family": [{"name" : "Sven"}]}');

SELECT id,
       JSON_EXTRACT(json, '$.*.name#.id()'
                   '$.*.name#'
        )
        EMITS (
                GENERATED_ID INT
                NAME VARCHAR(1000)
        )
FROM json_input;

Result:

ID GENERATED_ID NAME
1 4 Ann
1 8 Michael
2 5 Eve
2 8 Joe
2 13 Sven
Example 3

CREATE OR REPLACE TABLE json_input(id INT, json VARCHAR(2000000));
INSERT INTO json_input VALUES(1, '{"customers" : [{"name" : "Nick"}, {"name" : "Eve"}], "articles" : [{"item number" : 42}, {"item number" : 234}}]');

SELECT id,
       JSON_EXTRACT(json, 
            '$.customers#.name'
            '$.articles#."item number"'
        )
        EMITS (
               CUSTOMERS VARCHAR(1000), 
               ITEM_NUMBERS VARCHAR(1000)
        )
FROM json_input;
--ERROR: data exception - invalid JSON path: All nested path prefixes ending with a '#' character must be a prefix of the longest nested path.

Function error()

The default behavior of JSON_EXTRACT is ERROR ON EMPTY and ERROR ON ERROR for all paths. You can change this behavior to LOG ON EMPTY and LOG ON ERROR by adding the special path $.error() as the last element to the JSON_EXTRACT arguments. If LOG ON EMPTY or LOG ON ERROR is specified, JSON_EXTRACT writes all errors into an error column.

Examples
CREATE OR REPLACE TABLE json_input(id INT, json VARCHAR(2000000));

INSERT INTO json_input VALUES(1, '{"customers" : [{"name" : "Nick"}, {"name" : "Eve"}]}');

SELECT JSON_EXTRACT(json,
                    '$.articles."item number"'
                    ) 
            EMITS (
                ITEM_NUMBERS VARCHAR(1000)
        )
FROM json_input;

--ERROR: data exception - no SQL/JSON item in column: ITEM_NUMBERS
SELECT ID, 
        JSON_EXTRACT(json, 
                 '$.customers.name'
         ) 
         EMITS (
                CUSTOMERS VARCHAR(1000)
         )
FROM json_input;

--Error: data exception - more than one SQL/JSON item in column: CUSTOMERS
SELECT ID,
       JSON_EXTRACT(json, 
              '$.articles."item number"'
              '$.customers.name',
              '$.error()'
         )
         EMITS (
                ITEM_NUMBERS VARCHAR(1000), 
                CUSTOMERS VARCHAR(1000), 
                ERROR_COLUMN VARCHAR(1000)
         )
FROM json_input;

Results:

ID ITEM_NUMBER CUSTOMERS ERROR_COLUMN
1 NULL NULL [{ "column" : "ITEM_NUMBERS", "error" : "no SQL/JSON item" }, { "column" : "CUSTOMERS", "error" : "more than one SQL/JSON item" }]

Error handling suffixes

To specify separate error handling by JSON_EXTRACT for each JSON path, you can use error handling suffixes.

You can append [NULL|LOG|ERROR] ON EMPTY and [NULL|LOG|ERROR] ON ERROR to any path in JSON_EXTRACT except $.error().

If used on a single path, [NULL|LOG|ERROR] ON EMPTY has to be specified before [NULL|LOG|ERROR] ON ERROR.

For more details, see JSON Error Handling.

The following table shows the error behavior if json_path_expr points to a non-existing value.

ON EMPTY Suffix $.error() column Example Result
NONE NOT PRESENT

SELECT JSON_EXTRACT('{"name" : "John"}', '$.lastname') EMITS (JSON_RESULT VARCHAR(1000));

Exception: data exception - no SQL/JSON item

ERROR NOT PRESENT

SELECT JSON_EXTRACT('{"name" : "John"}', '$.lastnameERROR ON EMPTY') EMITS (JSON_RESULT VARCHAR(1000));

Exception: data exception - no SQL/JSON item

NULL NOT PRESENT

SELECT JSON_EXTRACT('{"name" : "John"}', '$.lastnameNULL ON EMPTY') EMITS (JSON_RESULT VARCHAR(1000));

NULL
LOG NOT PRESENT

SELECT JSON_EXTRACT('{"name" : "John"}', '$.lastnameLOG ON EMPTY') EMITS (JSON_RESULT VARCHAR(1000));

Exception: data exception - invalid JSON path: LOG ON EMPTY used without $.error() column

NONE PRESENT

SELECT JSON_EXTRACT('{"name" : "John"}', '$.lastname', '$.error()') EMITS (JSON_RESULT VARCHAR(1000), ERROR_COLUMN VARCHAR(1000));

NULL, ERROR_COLUMN: [{ "column" : "JSON_RESULT", "error" : "no SQL/JSON item" }]

ERROR PRESENT

SELECT JSON_EXTRACT('{"name" : "John"}', '$.lastnameERROR ON EMPTY', '$.error()') EMITS (JSON_RESULT VARCHAR(1000), ERROR_COLUMN VARCHAR(1000));

Exception: data exception - no SQL/JSON item

NULL PRESENT

SELECT JSON_EXTRACT('{"name" : "John"}', '$.lastnameNULL ON EMPTY', '$.error()') EMITS (JSON_RESULT VARCHAR(1000), ERROR_COLUMN VARCHAR(1000));

NULL
LOG PRESENT

SELECT JSON_EXTRACT('{"name" : "John"}', '$.lastnameLOG ON EMPTY', '$.error()') EMITS (JSON_RESULT VARCHAR(1000), ERROR_COLUMN VARCHAR(1000));

NULL, ERROR_COLUMN: [{ "column" : "JSON_RESULT", "error" : "no SQL/JSON item" }]

The following table shows the error behavior if the json_path_expr is valid but an error occurs during the JSON processing.

ON ERROR Suffix $.error() column Example Result
NONE NOT PRESENT

SELECT JSON_EXTRACT('{"name" : "John"}', '$.name') EMITS (JSON_RESULT INT);

Exception: data exception - cast error in column

ERROR NOT PRESENT

SELECT JSON_EXTRACT('{"name" : "John"}', '$.name ERROR ON ERROR') EMITS (JSON_RESULT INT);

Exception: data exception - cast error in column

NULL NOT PRESENT

SELECT JSON_EXTRACT('{"name" : "John"}', '$.name NULL ON ERROR') EMITS (JSON_RESULT INT);

NULL
LOG NOT PRESENT

SELECT JSON_EXTRACT('{"name" : "John"}', '$.name LOG ON ERROR') EMITS (JSON_RESULT INT);

Exception: data exception - invalid JSON path: ERROR ON EMPTY used without $.error() column

NONE PRESENT

SELECT JSON_EXTRACT('{"name" : "John"}', '$.name', '$.error()') EMITS (JSON_RESULT INT, ERROR_COLUMN VARCHAR(1000));

NULL, ERROR_COLUMN: [{ "column" : "JSON_RESULT", "error" : "cast error" }]

ERROR PRESENT

SELECT JSON_EXTRACT('{"name" : "John"}', '$.name ERROR ON ERROR', '$.error()') EMITS (JSON_RESULT INT, ERROR_COLUMN VARCHAR(1000));

Exception: data exception - cast error in column

NULL PRESENT

SELECT JSON_EXTRACT('{"name" : "John"}', '$.name NULL ON ERROR', '$.error()') EMITS (JSON_RESULT INT, ERROR_COLUMN VARCHAR(1000));

NULL
LOG PRESENT

SELECT JSON_EXTRACT('{"name" : "John"}', '$.name LOG ON ERROR', '$.error()') EMITS (JSON_RESULT INT, ERROR_COLUMN VARCHAR(1000));

NULL, ERROR_COLUMN: [{ "column" : "JSON_RESULT", "error" : "cast error" }]

Example:
CREATE OR REPLACE TABLE json_input(id INT, json VARCHAR(2000000));
INSERT INTO json_input VALUES(1, '{"customers" : [{"name" : "Nick"}, {"name" : "Eve"}], "articles" : [{"item number" : 10}, {"item number" : 234}]}');
INSERT INTO json_input VALUES(2, '{"articles" : [{"item number" : 42}]}');

SELECT ID,
       JSON_EXTRACT(json, 
              '$.customers.name LOG ON EMPTY NULL ON ERROR'
              '$.articles."item number" NULL ON EMPTY LOG ON ERROR'
              '$.error()'
         )
         EMITS (
                CUSTOMERS VARCHAR(1000), 
                ITEM_NUMBER VARCHAR(1000), 
                ERROR_COLUMN VARCHAR(1000)
         )
FROM json_input;

Result:

ID CUSTOMERS ITEM_NUMBERS ERROR_COLUMN
1 NULL NULL [{ "column" : "ITEM_NUMBER", "error" : "more than one SQL/JSON item" }]
2 NULL 42 [{ "column" : "CUSTOMERS", "error" : "no SQL/JSON item" }]