Read in nested JSON file in SQL Server - json

I am trying to read the following information taken from a nested json file in SQL Server:
Declare #json nvarchar(max)
SELECT #json =
N'{
"Energy":
{
"Energy-X/A": [
[
100.123456, null
],
[
101.123456, null
]
],
"Energy-X/B": [
[
102.123456, null
],
[
103.123456, null
]
]
}
}'
select
JSON_VALUE(a.value, '$.energy.Energy-X/A') as [Energy-X/A],
JSON_VALUE(b.value, '$.energy.Energy-X/B') as [Energy-X/B]
from OPENJSON(#json, '$.Energy') as a
CROSS APPLY OPENJSON(a.value, '$.energy') as b
The expected output should be that there are two columns with two entries for each row:
Energy-X/A
100.123456, null
101.123456, null
Energy-X/B
102.123456, null
103.123456, null
However, I am encountering two problems which I could not figure out:
If I execute the SQL statement nothing happens which indicates that I am obviously doing something wrong.
I have almost 1000 entries like "Energy-X/A", "Energy-X/B", "Energy-X/AC", etc.. Is there any better approach to extract the information without reusing the "JSON_VALUE()" function and introducing, e.g., b. value?
I am grateful for any help!

SQL Server won't return the data quite like you're expecting. json_value() is used to return scalar values such as strings, numbers and booleans, but when you want to return an array (or array of arrays) or objects you can use json_query() to return a snippet of the JSON data. For example:
select
json_query(Energy, '$."Energy-X/A"') as [Energy-X/A],
json_query(Energy, '$."Energy-X/B"') as [Energy-X/B]
from openjson (#json) with (
Energy nvarchar(max) as json
) as a;
Returns the output:
Energy-X/A Energy-X/B
------------------------ ----------------------------
[ [
[ [
100.123456, null 102.123456, null
], ],
[ [
101.123456, null 103.123456, null
] ]
] ]
The query could be simplified to the following, which yields the same result:
select
json_query(#json, '$.Energy."Energy-X/A"') as [Energy-X/A],
json_query(#json, '$.Energy."Energy-X/B"') as [Energy-X/B];
Edit...
If the JSON snippets of Energy-X/A and Energy-X/B are not sufficient for your purpose then you need to know their structure and parse them manually.
If they will consistently be 2 element arrays of 2 element arrays and you want to produce a row for each outer array that contains a comma-delimited list of the inner array's elements you can use a query such as the following (note: this requires SQL Server 2017 or later to make use of the string_agg() function):
select
commaDelimited.*
from openjson (#json) with (
energyXA nvarchar(max) '$.Energy."Energy-X/A"' as json,
energyXB nvarchar(max) '$.Energy."Energy-X/B"' as json
) as energy
cross apply (
select
(select string_agg(isnull(value, 'null'), ',') from openjson(energyXA, '$[0]')),
(select string_agg(isnull(value, 'null'), ',') from openjson(energyXB, '$[0]'))
union all
select
(select string_agg(isnull(value, 'null'), ',') from openjson(energyXA, '$[1]')),
(select string_agg(isnull(value, 'null'), ',') from openjson(energyXB, '$[1]'))
) commaDelimited ([Energy-X/A], [Energy-X/B]);
Which produces the results...
Energy-X/A
Energy-X/B
100.123456,null
102.123456,null
101.123456,null
103.123456,null

Related

Nested SELECT statements and reading in nested JSON file in SQL Server

The discussed problem has been solved partly in here:
Read in nested JSON file in SQL Server
but now the JSON file was extended with more objects of different format.
Declare #json nvarchar(max)
SELECT #json =
N'{
"Model": {
"Energy-X/A": {
"x": 1,
"y": 2,
"z": 3
},
"Energy-X/B": {
"x": 4,
"y": 5,
"z": 6
}
},
"Energy":
{
"Energy-X/A": [
[
100.123456, null
],
[
101.123456, null
]
],
"Energy-X/B": [
[
102.123456, null
],
[
103.123456, null
]
]
}
}'
select * from openjson(#json, '$.Model')
with (x [int] '$."Energy-X/A".x',
y [int] '$."Energy-X/A".y',
z [int] '$."Energy-X/A".z',
x [int] '$."Energy-X/B".x',
y [int] '$."Energy-X/B".y',
z [int] '$."Energy-X/B".z'
);
select commaDelimited.* from openjson (#json)
with (energyXA nvarchar(max) '$.Energy."Energy-X/A"' as json,
energyXB nvarchar(max) '$.Energy."Energy-X/B"' as json
) as energy
cross apply (
select
(select string_agg(isnull(value, 'null'), ',') from openjson(energyXA, '$[0]')),
(select string_agg(isnull(value, 'null'), ',') from openjson(energyXB, '$[0]'))
union all
select
(select string_agg(isnull(value, 'null'), ',') from openjson(energyXA, '$[1]')),
(select string_agg(isnull(value, 'null'), ',') from openjson(energyXB, '$[1]'))
) commaDelimited ([Energy-X/A], [Energy-X/B]);
The solution works and the values can be extracted but now I want to combine both SELECT statements into one query and construct a correlated subquery. The columns should appear when "Energy-X/A" and Energy-X/B" match like:
Energy-X/A
Energy-X/A
x
y
z
100.123456, null
101.123456, null
1
2
3
Energy-X/B
Energy-X/B
x
y
z
102.123456, null
103.123456, null
4
5
6
or also better output would be to sum up the values of Energy-X/A and Energy-X/B in one, separate column (using a delimiter such as semicolon):
Energy-X/A
x
y
z
100.123456, null ; 101.123456, null
1
2
3
Energy-X/B
x
y
z
102.123456, null ; 103.123456, null
1
2
3
I am grateful for any help!
Since you changed your expected results significantly, I've completely re-written your query.
Start by unpivoting the A and B values into separate rows using a (values) table and json_query.
Then read those columns using openjson.
In the case of Energy you need two levels of aggregation also, in order to get your second expected result.
select
commaDelimited.*,
model.*
from (values
(json_query(#json, '$.Model.BCS'), json_query(#json, '$.Energy."Energy-X/A"')),
(json_query(#json, '$.Model.BCSA'), json_query(#json, '$.Energy."Energy-X/B"'))
) j(model, energy)
outer apply openjson(j.model)
with (
x int,
y int,
z int
) model
outer apply (
select
Energy = string_agg(c.Energy, ' ; ')
from openjson(j.energy) energy
cross apply (
select
Energy = string_agg(isnull(Xinner.value, 'null'), ', ')
from openjson(energy.value) Xinner
) c
) commaDelimited;
db<>fiddle

JSON_EXTRACT for a range of dates (MYSQL)

I read multiple times the MYSQL guide for JSON (https://dev.mysql.com/doc/refman/8.0/en/json-search-functions.html#function_json-extract)
But I still don't find a way to query a range of dates.
I have the following stored JSON:
{"2014/12/10": [22.12323, 1212312.36], "2014/12/11": [24.983516, 59239590.36], "2014/12/15": [24.353891, 10350984.54], "2014/12/16": [24.756853, 51752318.09], "2014/12/17": [24.782038, 31848161.91]}
And the following query:
SELECT
JSON_EXTRACT(contenido, '$."2014/12/11"', '$."2014/12/16"') AS resultado
FROM
tabla_prueba;
I expect the result of:
[24.983516, 59239590.36], [24.353891, 10350984.54], [24.756853, 51752318.09]
But instead, I am getting only the two mentioned values:
[24.983516, 59239590.36], [24.756853, 51752318.09]
I also tried the key word to (for ranges) but with no results. What I am doing wrong?
Using JSON_TABLE
Try:
SET #json = '{
"2014/12/10": [
22.12323,
1212312.36
],
"2014/12/11": [
24.983516,
59239590.36
],
"2014/12/15": [
24.353891,
10350984.54
],
"2014/12/16": [
24.756853,
51752318.09
],
"2014/12/17": [
24.782038,
31848161.91
]
}';
SELECT a.dateStr, json_extract(#json, concat('$."', a.dateStr, '"')) as vals
FROM json_table(json_keys(#json), '$[*]' COLUMNS (dateStr CHAR(10) PATH '$')) a
WHERE a.dateStr BETWEEN '2014/12/11' AND '2014/12/16';
Result:
dateStr
vals
2014/12/11
[24.983516, 59239590.36]
2014/12/15
[24.353891, 10350984.54]
2014/12/16
[24.756853, 51752318.09]

Complex JSON using JSON_MODIFY without nested arrays or escape characters (WITHOUT_ARRAY_WRAPPER)

I am using JSON_MODIFY to build complex JSON. Moving from MySQL I am struggling with the JSON functions provided by SQL Server. The issue I'm having is that SQL Server seems to construct all JSON objects in an array. There is the WITHOUT_ARRAY_WRAPPER mechanism, which seems like it should do what I want, however; there are two undesirable consequences.
It only returns one result depending on how it is used
The result is a single string with escape characters
I have constructed a simple query which illustrates my needs and the issue.
QUERY 1
SELECT JSON_MODIFY(
JSON_QUERY('{"definitions": {"id": "INT", "name": "VARCHAR(23)"}}'),
'append $.data',
(
SELECT * FROM (
SELECT 1 AS id, '123abc' AS "name" UNION
SELECT 2 AS id, '234bcd' AS "name"
) AS "data"
FOR JSON PATH, WITHOUT_ARRAY_WRAPPER
)
) AS "data";
OUTPUT 1
{
"definitions":{
"id":"INT",
"name":"VARCHAR(23)"
},
"data":[
"{\"id\":1,\"name\":\"123abc\"},{\"id\":2,\"name\":\"234bcd\"}"
]
}
QUERY 2
SELECT JSON_MODIFY(
JSON_QUERY('{"definitions": {"id": "INT", "name": "VARCHAR(23)"}}'),
'append $.data',
(
SELECT * FROM (
SELECT 1 AS id, '123abc' AS "name" UNION
SELECT 2 AS id, '234bcd' AS "name"
) AS "data"
FOR JSON PATH
)
) AS "data";
OUTPUT 2
{
"definitions":{
"id":"INT",
"name":"VARCHAR(23)"
},
"data":[
[
{"id":1, "name":"123abc"},
{"id":2, "name":"234bcd"}
]
]
}
QUERY 1
The data object is an array (which is expected), but the problem is what is in the array... A single string with escape characters.
QUERY 2
The data object is an array, which contains an array. In order to access the actual array of data, I would use something like for each obj in data[0].... The problem this poses is, for anyone consuming the JSON object, I would have to tell them:
"In this particular object the data element is an array of
arrays--You'll want to use the first and only the first
element to access the actual array of data."
I've naively tried many different combinations of JSON_MODIFY, JSON_QUERY, and CONCAT to no avail. How can I properly use JSON_MODIFY to get the following output, without the double array in data?
{
"definitions":{
"id":"INT",
"name":"VARCHAR(23)"
},
"data":[
{"id":1, "name":"123abc"},
{"id":2, "name":"234bcd"}
]
}
You are over-thinking this by trying to JSON_MODIFY an existing object.
Construct the definitions and data properties that you need, inside a subquery if necessary.
Then use FOR JSON a second time to get the outer object.
SELECT
definitions = JSON_QUERY('{"id": "INT", "name": "VARCHAR(23)"}'),
data =
(
SELECT id, name
FROM (VALUES
(1, '123abc'),
(2, '234bcd')
) v(id, name)
FOR JSON PATH
)
FOR JSON PATH;
SQL Fiddle
By trial and error, I found the solution.
Removed the append keyword from the path parameter in the JSON_MODIFY statement
Removed the WITHOUT_ARRAY_WRAPPER parameter from the FOR JSON statement.
Now the results are as expected and I don't need to explain to any consumers to "Just use data[0]"
The Query
SELECT JSON_MODIFY(
JSON_QUERY('{"definitions": {"id": "INT", "name": "VARCHAR(23)"}}'),
'$.data',
(
SELECT * FROM (
SELECT 1 AS id, '123abc' AS "name" UNION
SELECT 2 AS id, '234bcd' AS "name"
) AS "data"
FOR JSON PATH
)
) AS "data";
Produces the following output
{
"definitions":{
"id":"INT",
"name":"VARCHAR(23)"
},
"data":[
{"id":1, "name":"123abc"},
{"id":2, "name":"234bcd"}
]
}

How to Parse JSON Arrays to SQL SERVER 2016

I'm looking to create a table of data (see "Output Table") from the below JSON. I can't seem to get to the "final mile". Can someone show me the way to properly handle the parsing of the arrays into the desired output table?
Thanks! Russ
Desired Output Table
Output I am getting
declare #json nvarchar(max) =
'{
"totalCount": 1,
"nextPageKey": null,
"result": [
{
"metricId": "builtin:host.cpu.usage",
"data": [
{
"dimensions": [
"HOST-CCCC3F95D7CE56"
],
"dimensionMap": {
"dt.entity.host": "HOST-CCCC3F95D7CE56"
},
"timestamps": [
1612634400000,
1612645200000,
1612656000000
],
"values": [
0.37900028935185187,
0.3709309895833333,
0.5088975694444444
]
}
]
}
]
}'
Select TableA.totalCount, TableResult.metricId,
TableDim.*
FROM OPENJSON(#json)
WITH(
totalCount int,
result NVARCHAR(MAX) as JSON
) as TableA
CROSS APPLY OPENJSON(TableA.result)
WITH(
metricId VARCHAR(100),
data NVARCHAR(MAX) as JSON
)TableResult
CROSS APPLY OPENJSON(TableResult.data)
WITH(
dimensions NVARCHAR(MAX) as JSON,
timestamps NVARCHAR(MAX) as JSON,
[values] NVARCHAR(MAX) as JSON
)TableDim
Hard to know what the correlation is between timestamps and values, but I've assumed it's by array index.
Is dimensions always a single value in the array? I have assumed so.
You then need to use OPENJSON (and no WITH block) to break out the two arrays, and join them together on index, which is supplied in key:
Select TableA.totalCount, TableResult.metricId,
TableDim.dimensions, Vals.*
FROM OPENJSON(#json)
WITH(
totalCount int,
result NVARCHAR(MAX) as JSON
) as TableA
CROSS APPLY OPENJSON(TableA.result)
WITH(
metricId VARCHAR(100),
data NVARCHAR(MAX) as JSON
)TableResult
CROSS APPLY OPENJSON(TableResult.data)
WITH(
dimensions NVARCHAR(MAX) '$.dimensions[0]',
timestamps NVARCHAR(MAX) as JSON,
[values] NVARCHAR(MAX) as JSON
)TableDim
CROSS APPLY (
SELECT tm.value AS Timestamp, v.value AS Value
FROM OPENJSON(TableDim.timestamps) tm
JOIN OPENJSON(TableDim.[values]) v ON v.[key] = tm.[key]
) Vals

How do you query property values with SQL Server JSON without knowing the parent object keys?

Given the following JSON in SQL Server, how do you get the "Foo" values without knowing the parent keys ("John" and "Jane")?
declare #json nvarchar(max) = '{
"John" : {
"favoriteMovie": "Star Wars",
"Foo": 42
},
"Jane" : {
"favoriteMovie": "Caddyshack",
"Foo": 66
}
}'
In SQL Server 2016 or later, you can use OPENJSON and APPLY for the number of levels in your hierarchy.
select b.[key], b.[value]
from openjson(#json) a
cross apply openjson(a.[value]) b
where b.[key] like N'Foo';
Results:
key value
Foo 42
Foo 66
Here is one way is to use XML node method to get the desired result. However, the method is odd but it would helps
select
ltrim(replace(replace(a.value('.', 'nvarchar(max)'), char(10), ''), '}', '')) json from
(
select cast('<m>'+replace(#json, ',', '</m><m>')+'</m>' as xml) as json
)a cross apply json.nodes ('/m') as json(a)
where a.value('.', 'varchar(max)') like '%foo%'
Result :
json
"Foo": 42
"Foo": 66