Retrieving sub-fields from parsed JSON in snowflake - json

I'm having some difficulty getting the individual components of the address component
with data as (select
PARSE_JSON('{ "data" : [
[ "row-ea6u~fkaa~32ry", "00000000-0000-0000-01B7-0B8F94EE5292", 0, 1486063689, null, 1486063689, null, "{ }", "410", "21206", "Frankford", "2", "NORTHEASTERN", [ "{\"address\": \"4509 BELAIR ROAD\", \"city\": \"Baltimore\", \"state\": \"MD\", \"zip\": \"\"}", null, null, null, true ], null, null, null ]
}') as j
)
select f.value[1][0]::text
from data d,
lateral flatten(input=> d.j:data,recursive=>TRUE) f;
f.value[1][0] has a field address
{"address": "4509 BELAIR ROAD", "city": "Baltimore", "state": "MD", "zip": ""}
but
f.value[1][0].address returns null
How do I get the individual attributes of f.value[1] like address, city, etc?

The problem is given you have three levels of nested data, you should not be using recursive=>TRUE as the objects are not the same, so you cannot make anything of value out of the data. You need to break the different layers apart manually.
with data as (
select
PARSE_JSON('{ data: [ [ "row-ea6u~fkaa~32ry", "0B8F94EE5292", 0, 1486063689, null, 1486063689, null, "{ }", "410", "21206", "Frankford", "2", "NORTHEASTERN", [ "{\\"address\\": \\"4509 BELAIR ROAD\\", \\"city\\": \\"Baltimore\\", \\"state\\": \\"MD\\", \\"zip\\": \\"\\"}", null, null, null, true ], null, null, null ]]}') as j
), data_rows as (
select f.value as r
from data d,
lateral flatten(input=> d.j:data) f
)
select dr.r[0] as v0
,dr.r[1] as v1
,dr.r[2] as v2
,dr.r[3] as v3
,f.value as addr_n
from data_rows dr,
lateral flatten(input=> dr.r[13]) f;
so this get all the rows (of which your example has only one) the unpacks the values of interest (you will need to complete this part and give v0 - vN meaning) but there is an array or addresses
V0 V1 V2 V3 ADDR_N
"row-ea6u~fkaa~32ry" "0B8F94EE5292" 0 1486063689 "{\"address\": \"4509 BELAIR ROAD\", \"city\": \"Baltimore\", \"state\": \"MD\", \"zip\": \"\"}"
"row-ea6u~fkaa~32ry" "0B8F94EE5292" 0 1486063689 null
"row-ea6u~fkaa~32ry" "0B8F94EE5292" 0 1486063689 null
"row-ea6u~fkaa~32ry" "0B8F94EE5292" 0 1486063689 null
"row-ea6u~fkaa~32ry" "0B8F94EE5292" 0 1486063689 true
now to decode the address as json ,parse_json(f.value) as addr_n does that, so you can break it apart like:
with data as (
select
PARSE_JSON('{ data: [ [ "row-ea6u~fkaa~32ry", "0B8F94EE5292", 0, 1486063689, null, 1486063689, null, "{ }", "410", "21206", "Frankford", "2", "NORTHEASTERN", [ "{\\"address\\": \\"4509 BELAIR ROAD\\", \\"city\\": \\"Baltimore\\", \\"state\\": \\"MD\\", \\"zip\\": \\"\\"}", null, null, null, true ], null, null, null ]]}') as j
), data_rows as (
select f.value as r
from data d,
lateral flatten(input=> d.j:data) f
)
select dr.r[0] as v0
,dr.r[1] as v1
,dr.r[2] as v2
,dr.r[3] as v3
,parse_json(f.value) as addr_n
,addr_n:address::text as addr_address
,addr_n:city::text as addr_city
,addr_n:state::text as addr_state
,addr_n:zip::text as addr_zip
from data_rows dr,
lateral flatten(input=> dr.r[13]) f;
you can ether leave the addr_n dummy variable or swap it out by cut'n'pasting it like so:
,parse_json(f.value):address::text as addr_address
,parse_json(f.value):city::text as addr_city
,parse_json(f.value):state::text as addr_state
,parse_json(f.value):zip::text as addr_zip

You can follow the article for step-by-step for achieving it:
https://community.snowflake.com/s/article/Using-lateral-flatten-to-extract-data-from-JSON-internal-field
Hope this helps!

Related

Get a ordered JSON tree from Postgres

I'm working on a dynamic menu and I need to get a JSON tree with data to build the options in the interface (using CSS, HTML and JS), based on the constraints defined in three tables ( SQL Fiddle still has postgres 9.6, but I'm using postgres 15.0 )
The general idea is to first find the nodes that point to a file (file IS NOT NULL), which are active (status = A) and which are from subsystem 1 (id_subsystem = 1), as long as the user 1 (id_user = 1) is also active (status = A) and has access permission (according to the users_modules table)
With these records, it would be enough to find each parent recursively related and add them to the tree, but keeping the order defined by the group_order field
users
CREATE TABLE users (
id SMALLINT NOT NULL,
name CHARACTER VARYING(20) NOT NULL,
status CHAR(1) NOT NULL
);
id
name
status
1
John Doe
A
2
Jane Doe
A
3
Duh Doe
I
modules
CREATE TABLE modules (
id SMALLINT NOT NULL,
id_subsystem SMALLINT NOT NULL,
id_master SMALLINT,
group_order SMALLINT NOT NULL,
label CHARACTER VARYING(30) NOT NULL,
file CHARACTER VARYING(30),
icon CHARACTER VARYING(20),
status CHAR(1) NOT NULL
);
INSERT INTO
modules
VALUES
(1,1,NULL,1,'Dashboard','dashboard','dashboard','A'),
(2,1,NULL,2,'Registrations',NULL,'cabinet','A'),
(3,1,2,1,'Customers','customers',NULL,'A'),
(4,1,2,2,'Suppliers','suppliers',NULL,'A'),
(5,1,2,3,'Products','products',NULL,'A'),
(6,1,2,4,'Staff',NULL,NULL,'A'),
(7,1,6,1,'Countries','countries',NULL,'A'),
(8,1,6,2,'States','states',NULL,'A'),
(9,1,6,3,'Cities','cities',NULL,'A'),
(10,1,6,4,'Means of contacts',NULL,NULL,'A'),
(11,1,10,1,'Electronic contacts','electronic_contacts',NULL,'A'),
(12,1,10,2,'Phone contacts','phone_contacts',NULL,'A'),
(13,1,10,3,'Deprecated contacts','deprecated_contacts',NULL,'I'),
(14,1,NULL,3,'Settings','settings','sliders','A'),
(15,2,NULL,1,'Dashboard','dashboard','dashboard','A'),
(16,2,NULL,2,'Financial',NULL,'cash','A'),
(17,2,16,1,'Bills to pay','bills_to_pay',NULL,'A'),
(18,2,16,2,'Bills to receive','bills_to_receive',NULL,'A');
id
id_subsystem
id_master
order
label
file
icon
status
1
1
NULL
1
Dashboard
dashboard
dashboard
A
2
1
NULL
2
Registrations
NULL
cabinet
A
3
1
2
1
Customers
customers
NULL
A
4
1
2
2
Suppliers
suppliers
NULL
A
5
1
2
3
Products
products
NULL
A
6
1
2
4
Staff
NULL
NULL
A
7
1
6
1
Countries
countries
NULL
A
8
1
6
2
States
states
NULL
A
9
1
6
3
Cities
cities
NULL
A
10
1
6
4
Means of contacts
NULL
NULL
A
11
1
10
1
Electronic contacts
electronic_contacts
NULL
A
12
1
10
2
Phone contacts
phone_contacts
NULL
A
13
1
10
3
Deprecated contacts
deprecated_contacts
NULL
I
14
1
NULL
3
Settings
settings
sliders
A
15
2
NULL
1
Dashboard
dashboard
dashboard
A
16
2
NULL
2
Financial
NULL
cash
A
17
2
16
1
Bills to pay
bills_to_pay
NULL
A
18
2
16
2
Bills to receive
bills_to_receive
NULL
A
users_modules
CREATE TABLE users_modules (
id_user SMALLINT NOT NULL,
id_module SMALLINT NOT NULL
);
INSERT INTO
users_modules
VALUES
(1,1),
(1,3),
(1,4),
(1,5),
(1,7),
(1,8),
(1,11),
(1,12);
id_user
id_module
1
1
1
3
1
4
1
5
1
7
1
8
1
11
1
12
I created the query below and it seems to be pretty close to solving the problem, but I still can't figure out what it is
WITH RECURSIVE
sub1 (id_master,sub) AS
(
(
/*
THE FIRST PART OF A RECURSIVE CTE IS FOR NON-RECURSIVE DATA
HERE I GET ALL THE RECORDS THAT POINT TO A FILE THAT CAN BE ACCESSED BY THE USER, BUT ONLY IF IT DOESN'T HAVE THE ROOT AS THE PARENT
*/
SELECT
B.id_master,
JSONB_AGG(
JSONB_BUILD_OBJECT(
'icon',B.icon,
'label',B.label,
'module',B.file
)
ORDER BY
B.group_order
) AS sub
FROM
(
SELECT
X.id_module
FROM
users_modules X
INNER JOIN
users Y
ON
X.id_user=Y.id
WHERE
X.id_user=1 AND
Y.status='A'
) A
INNER JOIN
modules B
ON
A.id_module=B.id
WHERE
B.id_master IS NOT NULL AND
B.id_subsystem=1 AND
B.status='A'
GROUP BY
B.id_master
)
UNION ALL
(
/*
THE SECOND PART OF A RECURSIVE CTE IS FOR RECURSIVE DATA
HERE I ASSEMBLE THE TREE CONNECTING ALL FINAL NODES AND BRANCHES POINTED RECURSIVELY
*/
SELECT
A.id_master,
JSONB_BUILD_OBJECT(
'icon',A.icon,
'label',A.label,
'sub',B.sub
) AS sub
FROM
modules A
INNER JOIN
sub1 B
ON
A.id=B.id_master
WHERE
A.status='A'
ORDER BY
A.group_order
)
)
SELECT
JSONB_AGG(sub ORDER BY group_order)
FROM
(
SELECT
sub,
group_order
FROM
(
(
/*
TYING AT THE ROOT ALL KNOTS POINTING TO THE ROOT
*/
SELECT
JSONB_BUILD_OBJECT(
'icon',A.icon,
'label',A.label,
'sub',B.sub
) AS sub,
A.group_order
FROM
modules A
INNER JOIN
sub1 B
ON
A.id=B.id_master
WHERE
A.id_master IS NULL AND
A.id_subsystem=1 AND
A.status='A'
)
UNION ALL
(
/*
ADDING ALL USER ACCESSIBLE FILE NODES THAT HAVE ROOT AS THE PARENT
*/
SELECT
JSONB_BUILD_OBJECT(
'icon',B.icon,
'label',B.label,
'module',B.file
) AS sub,
B.group_order
FROM
(
SELECT
A.id_module
FROM
users_modules A
INNER JOIN
modules B
ON
A.id_module=B.id
WHERE
A.id_user=1 AND
B.id_master IS NULL AND
B.status='A'
GROUP BY
A.id_module
) A
INNER JOIN
modules B
ON
A.id_module=B.id AND
B.status='A'
)
) sub2
) sub3
What I get is this:
[
{
"icon": "dashboard",
"label": "Dashboard",
"module": "dashboard"
},
{
"icon": "cabinet",
"label": "Registrations",
"sub":
[
{
"icon": null,
"label": "Customers",
"module": "customers"
},
{
"icon": null,
"label": "Suppliers",
"module": "suppliers"
},
{
"icon": null,
"label": "Products",
"module": "products"
}
]
},
{
"icon": "cabinet",
"label": "Registrations",
"sub":
{
"icon": null,
"label": "Staff",
"sub":
[
{
"icon": null,
"label": "Countries",
"module": "countries"
},
{
"icon": null,
"label": "States",
"module": "states"
}
]
}
},
{
"icon": "cabinet",
"label": "Registrations",
"sub":
{
"icon": null,
"label": "Staff",
"sub":
{
"icon": null,
"label": "Means of contacts",
"sub":
[
{
"icon": null,
"label": "Electronic contacts",
"module": "electronic_contacts"
},
{
"icon": null,
"label": "Phone contacts",
"module": "phone_contacts"
}
]
}
}
}
]
But what I need is this:
[
{
"icon": "dashboard",
"label": "Dashboard",
"module": "dashboard"
},
{
"icon": "cabinet",
"label": "Registrations",
"sub":
[
{
"icon": null,
"label": "Customers",
"module": "customers"
},
{
"icon": null,
"label": "Suppliers",
"module": "suppliers"
},
{
"icon": null,
"label": "Products",
"module": "products"
},
{
"icon": null,
"label": "Staff",
"sub":
[
{
"icon": null,
"label": "Countries",
"module": "countries"
},
{
"icon": null,
"label": "States",
"module": "states"
},
{
"icon": null,
"label": "Means of contacts",
"sub":
[
{
"icon": null,
"label": "Electronic contacts",
"module": "electronic_contacts"
},
{
"icon": null,
"label": "Phone contacts",
"module": "phone_contacts"
}
]
}
]
}
]
},
{
"icon": "sliders",
"label": "Settings",
"module": "settings"
}
]
Part of the members of the registrations node was not nested inside it and the settings node was lost
Almost there...
The query here below provides the expected result according to your data set :
WITH RECURSIVE list AS (
SELECT m.id_subsystem
, p.id :: integer AS id_master
, array_agg(m.id :: integer ORDER BY m.group_order) AS children_array
, bool_and(NOT EXISTS(SELECT 1 FROM modules where id_master = m.id)) AS leaves_only
, jsonb_build_object('icon', p.icon, 'label', p.label,'sub', jsonb_agg(jsonb_build_object('icon', m.icon, 'label', m.label, 'module', m.file) ORDER BY m.group_order)) AS module_hierarchy
FROM modules m
LEFT JOIN modules p
ON p.id = m.id_master
GROUP BY m.id_subsystem, p.id, p.icon, p.label
), tree AS (
SELECT id_subsystem
, id_master
, module_hierarchy
FROM list
WHERE leaves_only -- starts with the leaf modules
UNION ALL
SELECT t.id_subsystem
, l.id_master
, jsonb_set(l.module_hierarchy, array['sub', (array_position(l.children_array, t.id_master) - 1) :: text], t.module_hierarchy, true)
FROM tree t
INNER JOIN list l
ON l.children_array #> array[t.id_master] -- bottom-up tree build
AND l.id_subsystem = t.id_subsystem
WHERE t.id_master IS NOT NULL
)
SELECT id_subsystem, jsonb_pretty(module_hierarchy->'sub')
FROM tree
WHERE id_master IS NULL
Some conditions are not implemented yet : status of the modules and users, users_modules relationship, but adding these conditions should not be a big deal.
This query provides the expected result only if there is only one branch with 2 levels or more which is the case in your data set. When there are 2 or more branches with 2 levels or more, we need to merge the branches all together with a dedicated aggregate function :
CREATE OR REPLACE FUNCTION jsonb_merge (x jsonb, y jsonb)
RETURNS jsonb LANGUAGE sql IMMUTABLE AS $$
SELECT jsonb_agg(
CASE
WHEN x.content->>'sub' IS NOT NULL AND y.content->>'sub' IS NOT NULL
THEN (x.content - 'sub') || jsonb_build_object('sub', jsonb_merge(x.content->'sub', y.content->'sub'))
WHEN x.content->>'sub' IS NOT NULL
THEN x.content
ELSE y.content
END
ORDER BY x.id
)
FROM jsonb_path_query(COALESCE(x, y), '$[*]') WITH ORDINALITY AS x(content, id)
INNER JOIN jsonb_path_query(y, '$[*]') WITH ORDINALITY AS y(content, id)
ON x.id = y.id
$$ ;
CREATE OR REPLACE AGGREGATE jsonb_merge(jsonb)
( stype = jsonb, sfunc = jsonb_merge) ;
This aggregate function cannot be called directly inside the recursive query because postgres doesn't accept aggregate functions in the recursive part of the query, but it can be called after :
WITH RECURSIVE list AS (
SELECT m.id_subsystem
, p.id :: integer AS id_master
, array_agg(m.id :: integer ORDER BY m.group_order) AS children_array
, bool_and(NOT EXISTS(SELECT 1 FROM modules where id_master = m.id)) AS leaves_only
, jsonb_build_object('icon', p.icon, 'label', p.label,'sub', jsonb_agg(jsonb_build_object('icon', m.icon, 'label', m.label, 'module', m.file) ORDER BY m.group_order)) AS module_hierarchy
FROM modules m
LEFT JOIN modules p
ON p.id = m.id_master
GROUP BY m.id_subsystem, p.id, p.icon, p.label
), tree AS (
SELECT id_subsystem
, id_master
, module_hierarchy
FROM list
WHERE leaves_only
UNION ALL
SELECT t.id_subsystem
, l.id_master
, jsonb_set(l.module_hierarchy, array['sub', (array_position(l.children_array, t.id_master) - 1) :: text], t.module_hierarchy, true)
FROM tree t
INNER JOIN list l
ON l.children_array #> array[t.id_master]
AND l.id_subsystem = t.id_subsystem
WHERE t.id_master IS NOT NULL
)
SELECT id_subsystem
, jsonb_pretty(jsonb_merge(module_hierarchy->'sub'))
FROM tree
WHERE id_master IS NULL
GROUP BY id_subsystem
This query should work for any kind of trees with any levels.
see test result in dbfiddle

SQL OPENJSON is not returning the values from sub-arrays

I have a JSON file that is properly formatted according to the Microsoft ISJSON function. However, it refuses to return a value from the nested array.
Here is an excerpt from the JSON file.
I want to return the following fields: id, symbol, name, and price.
I can get the first three, but the price always shows up null in the SQL query results.
JSON FILE SNIPPET:
{
"status": {
"timestamp": "2021-01-06T07:14:42.132Z",
"error_code": 0,
"error_message": null,
"elapsed": 14,
"credit_count": 1,
"notice": null,
"total_count": 4180
},
"data": [
{
"id": 1,
"name": "Bitcoin",
"symbol": "BTC",
"slug": "bitcoin",
"num_market_pairs": 9772,
"date_added": "2013-04-28T00:00:00.000Z",
"tags": [
"mineable",
"pow",
"sha-256",
"store-of-value",
"state-channels"
],
"max_supply": 21000000,
"circulating_supply": 18592156,
"total_supply": 18592156,
"platform": null,
"cmc_rank": 1,
"last_updated": "2021-01-06T07:13:02.000Z",
"quote": {
"USD": {
"price": 36248.609255662224,
"volume_24h": 225452557837159.16,
"percent_change_1h": 2.74047145,
"percent_change_24h": 19.54362963,
"percent_change_7d": 29.31750604,
"market_cap": 673939798064.3159,
"last_updated": "2021-01-06T07:13:02.000Z"
}
}
}
Here is the SQL Query that I'm using:
DECLARE #JSON VARCHAR(MAX)
SELECT #JSON = BulkColumn
FROM OPENROWSET
(BULK 'C:\TSP\output.json', SINGLE_CLOB) AS j
Select iif(ISJSON(#JSON)=1,'YES','NO') JSON_OK
Select * FROM OPENJSON (#JSON, '$.data')
WITH (
id int
,symbol varchar(20)
,[name] varchar(50)
,price float '$.data.quote.USD[0]'
)
I've tried everything I can think of to get the price to appear, but I'm missing something as it's not cooperating. Also, I set the database compatibility level to 130 as I read that could be the problem.... Still no luck.
Any help would be much appreciated.
$.data.quote.USD is not an array, it's a set of properties. It's also already inside the $.data context so should not include data in its path. Try the following instead:
select *
from openjson(#JSON, '$.data') with
(
id int
,symbol varchar(20)
,[name] varchar(50)
,price float '$.quote.USD.price'
)

Extracting data from very nested JSON in Azure SQL

I have nested JSON file:
"vehicleStatusResponse": {
"vehicleStatuses": [
{
"vin": "ABC1234567890",
"triggerType": {
"triggerType": "TIMER",
"context": "RFMS",
"driverId": {
"tachoDriverIdentification": {
"driverIdentification": "123456789",
"cardIssuingMemberState": "BRA",
"driverAuthenticationEquipment": "CARD",
"cardReplacementIndex": "0",
"cardRenewalIndex": "1"
}
}
},
"receivedDateTime": "2020-02-12T04:11:19.221Z",
"hrTotalVehicleDistance": 103306960,
"totalEngineHours": 3966.6216666666664,
"driver1Id": {
"tachoDriverIdentification": {
"driverIdentification": "BRA1234567"
}
},
"engineTotalFuelUsed": 48477520,
"accumulatedData": {
"durationWheelbaseSpeedOverZero": 8309713,
"distanceCruiseControlActive": 8612200,
"durationCruiseControlActive": 366083,
"fuelConsumptionDuringCruiseActive": 3064170,
"durationWheelbaseSpeedZero": 5425783,
"fuelWheelbaseSpeedZero": 3332540,
"fuelWheelbaseSpeedOverZero": 44709670,
"ptoActiveClass": [
{
"label": "wheelbased speed >0",
"seconds": 16610,
"meters": 29050,
"milliLitres": 26310
},
{
"label": "wheelbased speed =0",
"seconds": 457344,
"milliLitres": 363350
It is already imported from Azure BLOB Storage to SQL DB and now I need to extract data from it to table. I've already used a T-SQL request to do that, but it returned me blank table with only headers:
SELECT response.*
FROM OPENROWSET (BULK 'response3.json', DATA_SOURCE = 'VCBI24', SINGLE_CLOB) as j
CROSS APPLY OPENJSON(BulkColumn)
WITH ( vehiclestatusResponse nvarchar (100), vehicleStatuses nvarchar (100), vin nvarchar (100), triggerType nvarchar (100), context nvarchar (100) and etc...) AS response
How can I handle it?
Thanks a lot for your attention!
You can supply a path with OPENJSON which allows you to drill in to nested JSON, eg
SELECT *
FROM OPENJSON( #json, '$.vehicleStatusResponse.vehicleStatuses' )
WITH (
vin VARCHAR(50) '$.vin',
triggerType VARCHAR(50) '$.triggerType.triggerType',
context VARCHAR(50) '$.triggerType.context',
driverIdentification VARCHAR(50) '$.triggerType.driverId.tachoDriverIdentification.driverIdentification',
cardIssuingMemberState VARCHAR(50) '$.triggerType.driverId.tachoDriverIdentification.cardIssuingMemberState',
receivedDateTime DATETIME '$.receivedDateTime',
engineTotalFuelUsed INT '$.engineTotalFuelUsed'
)
Full script example:
DECLARE #json VARCHAR(MAX) = '{
"vehicleStatusResponse": {
"vehicleStatuses": [
{
"vin": "ABC1234567890",
"triggerType": {
"triggerType": "TIMER",
"context": "RFMS",
"driverId": {
"tachoDriverIdentification": {
"driverIdentification": "123456789",
"cardIssuingMemberState": "BRA",
"driverAuthenticationEquipment": "CARD",
"cardReplacementIndex": "0",
"cardRenewalIndex": "1"
}
}
},
"receivedDateTime": "2020-02-12T04:11:19.221Z",
"hrTotalVehicleDistance": 103306960,
"totalEngineHours": 3966.6216666666664,
"driver1Id": {
"tachoDriverIdentification": {
"driverIdentification": "BRA1234567"
}
},
"engineTotalFuelUsed": 48477520,
"accumulatedData": {
"durationWheelbaseSpeedOverZero": 8309713,
"distanceCruiseControlActive": 8612200,
"durationCruiseControlActive": 366083,
"fuelConsumptionDuringCruiseActive": 3064170,
"durationWheelbaseSpeedZero": 5425783,
"fuelWheelbaseSpeedZero": 3332540,
"fuelWheelbaseSpeedOverZero": 44709670,
"ptoActiveClass": [
{
"label": "wheelbased speed >0",
"seconds": 16610,
"meters": 29050,
"milliLitres": 26310
},
{
"label": "wheelbased speed =0",
"seconds": 457344,
"milliLitres": 363350
}
]
}
}
]
}}}}}}}}'
SELECT *
FROM OPENJSON( #json, '$.vehicleStatusResponse.vehicleStatuses' )
WITH (
vin VARCHAR(50) '$.vin',
triggerType VARCHAR(50) '$.triggerType.triggerType',
context VARCHAR(50) '$.triggerType.context',
driverIdentification VARCHAR(50) '$.triggerType.driverId.tachoDriverIdentification.driverIdentification',
cardIssuingMemberState VARCHAR(50) '$.triggerType.driverId.tachoDriverIdentification.cardIssuingMemberState',
receivedDateTime DATETIME '$.receivedDateTime',
engineTotalFuelUsed INT '$.engineTotalFuelUsed'
)
My results:
Read more about OPENJSON here:
https://learn.microsoft.com/en-us/sql/t-sql/functions/openjson-transact-sql?view=sql-server-ver15
A second example. With OPENJSON you can either supply the json path expression is more explicit and gives you more control particularly for nested JSON. If the JSON is relatively simple, you do not have to supply the paths, eg
DECLARE #json VARCHAR(MAX) = '{
"ptoActiveClass": [
{
"label": "wheelbased speed >0",
"seconds": 16610,
"meters": 29050,
"milliLitres": 26310
},
{
"label": "wheelbased speed =0",
"seconds": 457344,
"milliLitres": 363350
}
]
}'
SELECT *
FROM OPENJSON( #json, '$.ptoActiveClass' )
WITH (
label VARCHAR(50),
seconds INT,
meters INT,
milliLitres INT
)
SELECT *
FROM OPENJSON( #json, '$.ptoActiveClass' )
WITH (
label VARCHAR(50) '$.label',
seconds VARCHAR(50) '$.seconds',
meters VARCHAR(50) '$.meters',
milliLitres VARCHAR(50) '$.milliLitres'
)

Parsing JSON without key names to retrieve a column

I am loading json from data.gov that does not have key names for the values in the json data, e.g. below: the metadata is available separately.
I am able to load the json into a variant column, but cannot see how to parse and query for specific columns, e.g. Frankford below - I have tried JSONcol:data[0] which returns the entire entry, but am unable to see how to specify column 4, say.
{
data: [ [ "row-ea6u~fkaa~32ry", "0B8F94EE5292", 0, 1486063689, null, 1486063689, null, "{ }", "410", "21206", "Frankford", "2", "NORTHEASTERN", [ "{\"address\": \"4509 BELAIR ROAD\", \"city\": \"Baltimore\", \"state\": \"MD\", \"zip\": \"\"}", null, null, null, true ], null, null, null ]]
}
The following code is used to create and load the snowflake table:
create or replace table snowpipe.public.snowtable(jsontext variant);
copy into snowpipe.public.snowtable
from #snowpipe.public.snowstage
file_format = (type = 'JSON')
Not exactly sure how your varient data is look once you have loaded it, but experimenting on variant via PARSE_JSON for you object. Which I has to double slash the \ to make it valid sql.
select
PARSE_JSON('{ data: [ [ "row-ea6u~fkaa~32ry", "0B8F94EE5292", 0, 1486063689, null, 1486063689, null, "{ }", "410", "21206", "Frankford", "2", "NORTHEASTERN", [ "{\\"address\\": \\"4509 BELAIR ROAD\\", \\"city\\": \\"Baltimore\\", \\"state\\": \\"MD\\", \\"zip\\": \\"\\"}", null, null, null, true ], null, null, null ]]}') as j
,j:data as jd
,jd[0] as jd0
,jd0[3] as jd0_3
,array_slice(j:data[0],3,5) as jd0_3to4
;
shows that you can use [0] notation to index arrays, and thus get the results:
J: { "data": [ [ "row-ea6u~fkaa~32ry", "0B8F94EE5292", 0, 1486063689, null, 1486063689, null, "{ }", "410", "21206", "Frankford", "2", "NORTHEASTERN", [ "{\"a...
JD: [ [ "row-ea6u~fkaa~32ry", "0B8F94EE5292", 0, 1486063689, null, 1486063689, null, "{ }", "410", "21206", "Frankford", "2", "NORTHEASTERN", [ "{\"address\": \"4509 BELAIR ROAD\", \"city\": \"...
JD0: [ "row-ea6u~fkaa~32ry", "0B8F94EE5292", 0, 1486063689, null, 1486063689, null, "{ }", "410", "21206", "Frankford", "2", "NORTHEASTERN", [ "{\"address\": \"4509 BELAIR ROAD\", \"city\": \"Baltimore\", \"state\": \"MD\", \"...
JD0_3: 1486063689
JD0_3TO4: [ 1486063689, null ]
so if you have unknown amount of first level elements in data that you want to access, then use LATERAL FLATTEN like so:
WITH data as (
select PARSE_JSON('{ data: [ [ "row-1", "0B8", 0 ],["row-2", "F94", 2],
["row-3", "EE5", 4]]}') as j
)
select f.value[0]::text as row_name
,f.value[1]::text as serial_number
,f.value[2]::number as num
from data d,
lateral flatten(input=> d.j:data) f;
gives:
ROW_NAME SERIAL_NUMBER NUM
row-1 0B8 0
row-2 F94 2
row-3 EE5 4

How to convert json to FoxPro cursor

File contains invoice list in json format like:
{
"status": "OK", "statusCode": 200, "messages": null,
"data": [{
"payment_type": "banktransfer", "fine": "0.200000", "quote_id": null, "order_id": null, "prepayment_id": null, "credited_invoices": [],
"interested_party_address_id": 279, "project_id": 875, "currency": "EUR", "owner_id": 3, "date": "2019-03-15", "deadline": "2019-03-20",
},
{
"payment_type": "banktransfer", "fine": "0.30000", "quote_id": null, "order_id": null, "prepayment_id": null, "credited_invoices": [],
"interested_party_address_id": 79, "project_id": 85, "currency": "EUR", "owner_id": 3, "date": "2019-04-15", "deadline": "2019-43-20",
}
.... more same type elements
]
}
How to convert it to FoxPRo cursor ?
Cursor should contain payment_type, fine, quote_id etc columns.
I tried
https://archive.codeplex.com/?p=qdfoxjson
and
http://www.sweetpotatosoftware.com/blog/index.php/2008/12/19/visual-foxpro-json-class-update/
but it looks like they require json to be in different format than my json.
Andrus
First thing is your JSON is invalid. There shouldn't be a hanging comma after the "deadline" date values.
The second thing is that there is no mechanism to tell VFP how to map elements to fields. So you have to do it yourself.
To do this I would use the njJson library, specifically nfJsonRead.
Assuming your JSON is in file 'test.json' and you have nfJsonRead.prg then for example:
Close All
Clear All
Clear
Set Procedure To nfJsonRead additive
* -- Add the other fields as appropriate.
Create Cursor mydata (payment_type c(20), fine n(12, 2))
loJson = nfjsonread(FileToStr("test.json"))
For each loData in loJson.data
Insert into mydata values (loData.payment_type, Val(loData.fine))
EndFor
Just a quick something I came up with for you. If I had more stuff in similar format, I would have done some cursor/table structure of "looking for begin delimiter", "looking for end delimiter", "final data type", etc... then looping through for each part. Here, I actually am showing how each part is being extracted and how to get one data row at a time.
** Example to put into a string called "json", no other context to json
TEXT TO json noshow
{
"status": "OK", "statusCode": 200, "messages": null,
"data": [{1
"payment_type": "banktransfer", "fine": "0.200000", "quote_id": null, "order_id": null, "prepayment_id": null, "credited_invoices": [],
"interested_party_address_id": 279, "project_id": 875, "currency": "EUR", "owner_id": 3, "date": "2019-03-15", "deadline": "2019-03-20",
},
{2
"payment_type": "banktransfer", "fine": "0.30000", "quote_id": null, "order_id": null, "prepayment_id": null, "credited_invoices": [],
"interested_party_address_id": 79, "project_id": 85, "currency": "EUR", "owner_id": 3, "date": "2019-04-15", "deadline": "2019-43-20",
}
.... more same type elements
]
}
ENDTEXT
** Strip the "Status" component ang get just the Data
json = SUBSTR( json, ATC( ["data":], json ))
CREATE CURSOR C_ImportRecs;
( PayType c(20),;
Fine n(10,6),;
QuoteID i,;
OrderID i,;
PrepayID i,;
Invoices c(20),;
IntPartyAddrID i,;
ProjectID i,;
TransCurrency c(3),;
OwnerID i,;
DatePart c(15),;
TransDate d,;
DeadPart c(15),;
Deadline d )
** scatter all fields to an object which will have fields by same name
SCATTER NAME newRec
** start counter to 1
nRow = 1
DO while .t.
** Look within the json string for the "nRow" instance of data starting / ending with { / }
oneData = STREXTRACT( json, "{", "}", nRow )
** if no more entries, get out
IF EMPTY( ALLTRIM( oneData ))
exit
ENDIF
** if memory variables are same name as cursor/table columns, simplifies insert
newRec.PayType = STREXTRACT( oneData, ["payment_type": "], ["] )
newRec.Fine = VAL( STREXTRACT( oneData, ["fine": "], ["] ))
** not including closing quote as ending delimiter since null allowed, looking for the "," after the IDs
newRec.QuoteID = INT( VAL( STREXTRACT( oneData, ["quote_id": ], [,] ) ))
newRec.OrderID = INT( VAL( STREXTRACT( oneData, ["order_id": ], [,] ) ))
newRec.PrepayID = INT( VAL( STREXTRACT( oneData, ["prepayment_id": ], [,] ) ))
newRec.Invoices = STREXTRACT( oneData, ["credited_invoices": ], [,] )
newRec.IntPartyAddrID = INT( VAL( STREXTRACT( oneData, ["interested_party_address_id": ], [,] ) ))
newRec.ProjectID = INT( VAL( STREXTRACT( oneData, ["project_id": ], [,] ) ))
** Currency is reserved word.
newRec.TransCurrency = STREXTRACT( oneData, ["currency": "], ["] )
newRec.OwnerID = INT( VAL( STREXTRACT( oneData, ["owner_id": ], [,] ) ))
** Date is reserved word
newRec.DatePart = ""
newRec.DatePart = STREXTRACT( oneData, ["date": "], ["] )
** Nice for macro substitution. Take incoming date format of ex: "2019-03-15"
** and change to "2019,03,15" This is because the DATE() function expects
** 3 parameters for YEAR, MONTH, DAY
tryDate = STRTRAN( newRec.DatePart, "-", "," )
** Now, by using macro via "&" forces the line below to immediately run as if
** run as if it was DATE( 2019,03,15) and create an actual date value
TRY
newRec.TransDate = DATE( &tryDate )
CATCH
newRec.TransDate = CTOD( "" )
ENDTRY
** same for deadline
newRec.DeadPart = STREXTRACT( oneData, ["deadline": "], ["] )
tryDate = STRTRAN( newRec.DeadPart, "-", "," )
TRY
** your second record has a month 43 which is an invalid month
newRec.Deadline = DATE( &tryDate )
CATCH
newRec.Deadline = CTOD( "" )
ENDTRY
** Since the cursor "C_ImportRecs" is already the active focus,
** we can just insert from memory variables
INSERT INTO C_ImportRecs FROM NAME newRec
** try if any more records
nRow = nRow + 1
ENDDO
BROWSE NORMAL NOCAPTIONS NOWAIT