Reputation: 781
I am trying to use Db2 JSON capabilities and in particular nested tables.
CREATE TABLE JSON.TEST1 (COL1 VARBINARY(2000));
INSERT INTO JSON.TEST1 (COL1) VALUES (JSON_TO_BSON(
'{"id" : 103,
"orderDate": "2014-06-20",
"items": {
"item": [ { "partNum": "872-AA",
"productName": "Lawnmower",
"quantity": 1,
"USPrice": 749.99
},
{ "partNum": "837-CM",
"productName": "Digital Camera",
"quantity": 2,
"USPrice": 199.99
}
]
}
}'
));
This works fine, however obviously items in the array are hardcoded references.
SELECT id
,orderDate
,product1
,product2
FROM json.TEST1 AS js,
JSON_TABLE
(js.COL1, 'strict $'
COLUMNS( id INTEGER PATH '$.id'
,orderDate DATE PATH '$.orderDate'
,product1 VARCHAR(32) PATH '$.items.item[0].productName'
,product2 VARCHAR(32) PATH '$.items.item[1].productName'
)
ERROR ON ERROR) AS t
;
The following is what I am trying to get working:
SELECT id
,orderDate
,productName
FROM json.TEST1 AS js,
JSON_TABLE
(js.COL1, '$'
COLUMNS( id INTEGER PATH '$.id'
,orderDate DATE PATH '$.orderDate'
,NESTED 'lax $.items.item[]'
COLUMNS (
"productName" VARCHAR(32)
)
)
) as t;
For reference the error I am receiving
1) [Code: -104, SQL State: 42601] An unexpected token "'lax $.items.item[]'
COLUMNS (
" was found following ",NESTED". Expected tokens may include: "<space>".. SQLCODE=-104, SQLSTATE=42601, DRIVER=4.26.14
2) [Code: -727, SQL State: 56098] An error occurred during implicit system action type "2". Information returned for the error includes SQLCODE "-104", SQLSTATE "42601" and message tokens "'lax $.items.item[]'
COLUMNS (
|,N".. SQLCODE=-727, SQLSTATE=56098, DRIVER=4.26.14
Upvotes: 0
Views: 2676
Reputation: 12339
A couple of UDFs with the same functionality which should work faster, since they don't use RCTE.
The example of use is in my another older answer here.
-- Uses XML, should work in all environments
CREATE OR REPLACE FUNCTION UNNEST_JSON2 (P_DOC CLOB(1M), P_PATH VARCHAR(128))
RETURNS TABLE
(
INDEX INT
, ITEM CLOB (1M)
)
DETERMINISTIC
NO EXTERNAL ACTION
BEGIN ATOMIC
DECLARE L_IDX INT DEFAULT 0;
DECLARE L_XML XML;
L1:
WHILE TRUE DO
IF NOT JSON_EXISTS (P_DOC, P_PATH || '[' || L_IDX || ']') THEN LEAVE L1; END IF;
SET (L_XML, L_IDX) =
(
XMLCONCAT (L_XML, XMLELEMENT (NAME "A", JSON_QUERY (P_DOC, P_PATH || '[' || L_IDX || ']')))
, L_IDX + 1
);
END WHILE L1;
RETURN
SELECT SEQ - 1, T.ITEM
FROM XMLTABLE
(
'$D' PASSING L_XML AS "D"
COLUMNS
SEQ FOR ORDINALITY
, ITEM CLOB (1M) PATH '.'
) T
WHERE L_XML IS NOT NULL;
END
@
-- Doesn't work in DPF environment, but should be the fastest one
CREATE OR REPLACE FUNCTION UNNEST_JSON3 (P_DOC CLOB(1M), P_PATH VARCHAR(128))
RETURNS TABLE
(
INDEX INT
, ITEM CLOB (1M)
)
DETERMINISTIC
NO EXTERNAL ACTION
BEGIN
DECLARE L_IDX INT DEFAULT 0;
L1:
WHILE TRUE DO
IF NOT JSON_EXISTS (P_DOC, P_PATH || '[' || L_IDX || ']') THEN LEAVE L1; END IF;
PIPE (L_IDX, JSON_QUERY (P_DOC, P_PATH || '[' || L_IDX || ']'));
SET L_IDX = L_IDX + 1;
END WHILE L1;
RETURN;
END
@
Upvotes: 1
Reputation: 12339
Unfortunately, you must unnest JSON arrays on your own, for example, with Recursive Common Table Expression (RCTE):
-- A table with JSON documents
WITH TAB (DOC_ID, DOC) AS
(
VALUES
(
1,
'{"id" : 103,
"orderDate": "2014-06-20",
"items": {
"item": [ { "partNum": "872-AA",
"productName": "Lawnmower",
"quantity": 1,
"USPrice": 749.99
},
{ "partNum": "837-CM",
"productName": "Digital Camera",
"quantity": 2,
"USPrice": 199.99
}
]
}
}'
)
)
-- get a JSON array only for each record
, ITEMS_ARRAY (DOC_ID, ITEMS) AS
(
SELECT DOC_ID, JSON_OBJECT(KEY 'items' VALUE JSON_QUERY(DOC, '$.items.item') FORMAT JSON)
FROM TAB
)
-- Use RCTE to unnest it
, ITEMS (DOC_ID, INDEX, ITEM) AS
(
SELECT DOC_ID, 0, JSON_QUERY(ITEMS, '$.items[0]')
FROM ITEMS_ARRAY
WHERE JSON_EXISTS(ITEMS, '$.items[0]')
UNION ALL
SELECT I.DOC_ID, I.INDEX+1, JSON_QUERY(A.ITEMS, '$.items['|| TRIM(I.INDEX+1) ||']')
FROM ITEMS I, ITEMS_ARRAY A
WHERE I.DOC_ID = A.DOC_ID AND JSON_EXISTS(A.ITEMS, '$.items['|| TRIM(I.INDEX+1) ||']')
)
SELECT D.*, IT.*
--, I.*
FROM TAB T
JOIN ITEMS I ON I.DOC_ID = T.DOC_ID
-- array element to row
CROSS JOIN JSON_TABLE
(
I.ITEM, 'strict $' COLUMNS
(
PARTNUM VARCHAR(20) PATH '$.partNum'
, PRODCUCTNAME VARCHAR(20) PATH '$.productName'
, QUANTITY INT PATH '$.quantity'
, USPRICE DECFLOAT PATH '$.USPrice'
) ERROR ON ERROR
) IT
-- other elements of original JSON to row
CROSS JOIN JSON_TABLE
(
T.DOC, 'strict $' COLUMNS
(
ID INT PATH '$.id'
, ORDERDATE DATE PATH '$.orderDate'
) ERROR ON ERROR
) D
;
There result is:
|ID |ORDERDATE |PARTNUM|PRODCUCTNAME |QUANTITY|USPRICE|
|---|----------|-------|--------------|--------|-------|
|103|2014-06-20|872-AA |Lawnmower |1 |749.99 |
|103|2014-06-20|837-CM |Digital Camera|2 |199.99 |
db<>fiddle example.
It's convenient to create a generic function suitable for any JSON array:
-- WITH A GENERIC TABLE FUNCTION
CREATE OR REPLACE FUNCTION UNNEST_JSON (P_DOC CLOB(1M), P_PATH VARCHAR(128))
RETURNS TABLE
(
INDEX INT
, ITEM CLOB(1M)
)
RETURN
WITH ITEMS_ARRAY (ITEMS) AS
(
VALUES JSON_OBJECT(KEY 'items' VALUE JSON_QUERY(P_DOC, P_PATH) FORMAT JSON)
)
, ITEMS (INDEX, ITEM) AS
(
SELECT 0, JSON_QUERY(ITEMS, '$.items[0]')
FROM ITEMS_ARRAY
WHERE JSON_EXISTS(ITEMS, '$.items[0]')
UNION ALL
SELECT I.INDEX+1, JSON_QUERY(A.ITEMS, '$.items['|| TRIM(I.INDEX+1) ||']')
FROM ITEMS I, ITEMS_ARRAY A
WHERE JSON_EXISTS(A.ITEMS, '$.items['|| TRIM(I.INDEX+1) ||']')
)
SELECT INDEX, ITEM
FROM ITEMS
@
Such a generic function simplifies the solution:
WITH TAB (DOC_ID, DOC) AS
(
VALUES
(
1,
'{"id" : 103,
"orderDate": "2014-06-20",
"items": {
"item": [ { "partNum": "872-AA",
"productName": "Lawnmower",
"quantity": 1,
"USPrice": 749.99
},
{ "partNum": "837-CM",
"productName": "Digital Camera",
"quantity": 2,
"USPrice": 199.99
}
]
}
}'
)
,
(
2,
'{"id" : 203,
"orderDate": "2014-06-20",
"items": {
"item": [ { "partNum": "002-AA",
"productName": "Lawnmower",
"quantity": 10,
"USPrice": 749.99
},
{ "partNum": "002-BB",
"productName": "Digital Camera",
"quantity": 20,
"USPrice": 199.99
}
]
}
}'
)
)
SELECT T.DOC_ID, A.INDEX, D.*, IT.*
FROM
TAB T
-- unnesting
, TABLE(UNNEST_JSON(T.DOC, '$.items.item')) A
-- array element to row
, JSON_TABLE
(
A.ITEM, 'strict $' COLUMNS
(
PARTNUM VARCHAR(20) PATH '$.partNum'
, PRODCUCTNAME VARCHAR(20) PATH '$.productName'
, QUANTITY INT PATH '$.quantity'
, USPRICE DECFLOAT PATH '$.USPrice'
) ERROR ON ERROR
) IT
-- other elements of original JSON to row
, JSON_TABLE
(
T.DOC, 'strict $' COLUMNS
(
ID INT PATH '$.id'
, ORDERDATE DATE PATH '$.orderDate'
) ERROR ON ERROR
) D;
The result is:
|DOC_ID|INDEX|ID |ORDERDATE |PARTNUM|PRODCUCTNAME |QUANTITY|USPRICE|
|------|-----|---|----------|-------|--------------|--------|-------|
|1 |0 |103|2014-06-20|872-AA |Lawnmower |1 |749.990|
|1 |1 |103|2014-06-20|837-CM |Digital Camera|2 |199.990|
|2 |0 |203|2014-06-20|002-AA |Lawnmower |10 |749.990|
|2 |1 |203|2014-06-20|002-BB |Digital Camera|20 |199.990|
Upvotes: 3