MS SQL Query a field containing JSON - json

I have the following JSON in a SQL field in a table:
{
"type": "info",
"date": "2019/11/12 14:28:51",
"state": {
"6ee8587f-3b8c-4e5c-89a9-9f04752607f0": {
"state": "open",
"color": "#0000ff"
}
},
...
}
I query this in MS SQL using the folloing:
SELECT
JSON_VALUE(json_data, '$.type') AS msg_type
,JSON_VALUE(json_data, '$."date"') AS event_date
,JSON_QUERY(json_data, '$.state."6ee8587f-3b8c-4e5c-89a9-9f04752607f0".state') AS json_state
,JSON_QUERY(json_data, '$.state."6ee8587f-3b8c-4e5c-89a9-9f04752607f0".color') AS json_color
FROM
[dbo].[tbl_json_dump]
To get the date (a reserved word) back I have to put the the field name in like $."date"
I cannot seem to get the data back for the state or color fields and I think it has to do with that it is nested under "6ee8587f-3b8c-4e5c-89a9-9f04752607f0" because when I query :
JSON_QUERY(json_data, '$.state."6ee8587f-3b8c-4e5c-89a9-9f04752607f0"') AS json_state
I get the object back -
{"state":"open","color":"#0000ff"}
but using
JSON_QUERY(json_data, '$.state."6ee8587f-3b8c-4e5c-89a9-9f04752607f0".state') AS json_state
it is not working
Any suggestions on what I'm doing wrong??

Just replace JSON_QUERY with JSON_VALUE since you're interested in getting the value.
JSON_QUERY is supposed to return a JSON fragment and designed to work on objects and arrays, not values.

Salman A already provided the answer. Just to add a few points.
JSON_VALUE() - Extracts a Scalar value
JSON_QUERY() - Extracts an object or an array from a JSON string.
If you see the syntax , JSON_QUERY ( expression [ , path ] ) & JSON_VALUE ( expression , path ) , both are more or less except the [] square brackets for path and it means optional. It is because JSON_QUERY() can extract whole JSON field if required.
And on the return types,
JSON_VALUE() returns a JSON fragment of type nvarchar(max)
JSON_QUERY() returns a single text value of type nvarchar(4000)
Overall comparison
DECLARE #data NVARCHAR(4000)
SET #data=N'{
"type": "info",
"date": "2019/11/12 14:28:51",
"state": {
"6ee8587f-3b8c-4e5c-89a9-9f04752607f0": {
"state": "open",
"color": "#0000ff"
}
},
}'
SELECT
JSON_VALUE(#data,'$.state."6ee8587f-3b8c-4e5c-89a9-9f04752607f0"') AS 'JSON_VALUE_FAILED',
JSON_QUERY(#data,'$.state."6ee8587f-3b8c-4e5c-89a9-9f04752607f0"') AS 'JSON_QUERY_SUCCEED',
JSON_VALUE(#data,'$.state."6ee8587f-3b8c-4e5c-89a9-9f04752607f0".state') AS 'JSON_VALUE_SUCCEED',
JSON_QUERY(#data,'$.state."6ee8587f-3b8c-4e5c-89a9-9f04752607f0".state') AS 'JSON_QUERY_SUCCEED';
Check Output here

You may try with another possible approach (more complicated), which parses all nested JSON objects.
Table:
CREATE TABLE Data (
JsonData nvarchar(max)
)
INSERT INTO Data
(JsonData)
VALUES
(N'{
"type": "info",
"date": "2019/11/12 14:28:51",
"state": {
"6ee8587f-3b8c-4e5c-89a9-9f04752607f0": {
"state": "open",
"color": "#0000ff"
},
"6ee8587f-3b8c-4e5c-89a9-9f04752607f1": {
"state": "open",
"color": "#0000ff"
}
}
}')
Statement:
SELECT
j1.[type], j1.[date], j2.[key], j3.state, j3.color
FROM Data d
CROSS APPLY OPENJSON(d.JsonData) WITH (
[type] nvarchar(100) '$.type',
[date] datetime '$.date',
[state] nvarchar(max) '$.state' AS JSON
) j1
CROSS APPLY OPENJSON(j1.state) j2
CROSS APPLY OPENJSON(j2.[value]) WITH (
state nvarchar(10) '$.state',
color nvarchar(10) '$.color'
) j3
Result:
type date key state color
info 12/11/2019 14:28:51 6ee8587f-3b8c-4e5c-89a9-9f04752607f0 open #0000ff
info 12/11/2019 14:28:51 6ee8587f-3b8c-4e5c-89a9-9f04752607f1 open #0000ff
Notes:
If the input JSON has only one key "6ee8587f-3b8c-4e5c-89a9-9f04752607f0" in the "state" JSON object, you may get the value with JSON_VALUE() using the correct path $.state."6ee8587f-3b8c-4e5c-89a9-9f04752607f0".state.

Related

Using OPENJSON in SQL Server to parse a Non-Array Object

I'm using SQL Server v15, called from a .NET application.
A website I'm using (not mine - I don't control the data) has a JSON dataset formatted strangely. Instead of being an array like:
[{"id":"1","Name":"Charlie"},{"id":"2","Name"="Sally"}]
It's an object with each element named as its ID:
{"1":{"id":"1","Name":"Charlie"}, "2":{"id":"2","Name"="Sally"}}
I know how to use the OPENJSON to read data from an array, but is it possible to have it parse this format? Or is my best bet to have a script loop through the objects one at a time?
Please try the following solution.
SQL
DECLARE #json NVARCHAR(MAX) =
N'{
"1": {
"id": "1",
"Name": "Charlie"
},
"2": {
"id": "2",
"Name": "Sally"
}
}';
SELECT rs.*
FROM OPENJSON (#json) AS seq
CROSS APPLY OPENJSON(seq.value)
WITH
(
[id] INT '$.id'
, [Name] VARCHAR(20) '$.Name'
) AS rs;
Output
id
Name
1
Charlie
2
Sally

Querying on mysql json array using mysql workbench

Here is my json data:
{
"TransactionId": "1",
"PersonApplicant": [
{
"PersonalId": "1005",
"ApplicantPhone": [
{
"PhoneType": "LANDLINE",
"PhoneNumber": "8085063644",
"IsPrimaryPhone": true
}
]
},
{
"PersonalId": "1006",
"ApplicantPhone": [
{
"PhoneType": "LANDLINE",
"PhoneNumber": "9643645364",
"IsPrimaryPhone": true
},
{
"PhoneType": "HOME",
"PhoneNumber": "987654321",
"IsPrimaryPhone": false
}
]
}
]
}
I want to get phone no of the people who have phonetype as landline.
How to do that?
I tried this approach:
#find phoneNumber when phoneType='LANDLINE'
SELECT
#path_to_name := json_unquote(json_search(applicationData, 'one', 'LANDLINE')) AS path_to_name,
#path_to_parent := trim(TRAILING '.PhoneType' from #path_to_name) AS path_to_parent,
#event_object := json_extract(applicationData, #path_to_parent) as event_object,
json_unquote(json_extract(#event_object, '$.PhoneNumber')) as PhoneNumber
FROM application;
The issue with this is that I am using 'one' so I am able to achieve results but here in my json I have 2 people who have type as landline.
Using json search I am getting array of values and I am not able to decide how to extract these array row values in a manner where I can extract paths.
SELECT
#path_to_name := json_unquote(json_search(applicationData, 'all', 'LANDLINE')) from application;
result:
as you can see at 3rd and 4th row i am getting 2 data as an array.
How do I store this data to get the appropriate result?
I also tried one more query but not able to retrieve results for array of data.
I cannot use stored procedure and I have to use mysql workbench.
Please note that I am fresher so I don't know how I can approach this solution for more complex queries where I may have to retrieve id of a person having type as landline (multiple people in single array).
SELECT test.id, jsontable.*
FROM test
CROSS JOIN JSON_TABLE(test.data,
'$.PersonApplicant[*]'
COLUMNS ( PersonalId INT PATH '$.PersonalId',
PhoneType VARCHAR(255) PATH '$.ApplicantPhone[0].PhoneType',
PhoneNumber VARCHAR(255) PATH '$.ApplicantPhone[0].PhoneNumber')) jsontable
WHERE jsontable.PhoneType = 'LANDLINE';
https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=4089207ccfba5068a48e06b52865e759

how to extract properly when sqlite json has value as an array

I have a sqlite database and in one of the fields I have stored complete json object . I have to make some json select requests . If you see my json
the ALL key has value which is an array . We need to extract some data like all comments where "pod" field is fb . How to extract properly when sqlite json has value as an array ?
select json_extract(data,'$."json"') from datatable ; gives me entire thing . Then I do
select json_extract(data,'$."json"[0]') but i dont want to do it manually . i want to iterate .
kindly suggest some source where i can study and work on it .
MY JSON
{
"ALL": [{
"comments": "your site is awesome",
"pod": "passcode",
"originalDirectory": "case1"
},
{
"comments": "your channel is good",
"data": ["youTube"],
"pod": "library"
},
{
"comments": "you like everything",
"data": ["facebook"],
"pod": "fb"
},
{
"data": ["twitter"],
"pod": "tw",
"ALL": [{
"data": [{
"codeLevel": "3"
}],
"pod": "mo",
"pod2": "p"
}]
}
]
}
create table datatable ( path string , data json1 );
insert into datatable values("1" , json('<abovejson in a single line>'));
Simple List
Where your JSON represents a "simple" list of comments, you want something like:
select key, value
from datatable, json_each( datatable.data, '$.ALL' )
where json_extract( value, '$.pod' ) = 'fb' ;
which, using your sample data, returns:
2|{"comments":"you like everything","data":["facebook"],"pod":"fb"}
The use of json_each() returns a row for every element of the input JSON (datatable.data), starting at the path $.ALL (where $ is the top-level, and ALL is the name of your array: the path can be omitted if the top-level of the JSON object is required). In your case, this returns one row for each comment entry.
The fields of this row are documented at 4.13. The json_each() and json_tree() table-valued functions in the SQLite documentation: the two we're interested in are key (very roughly, the "row number") and value (the JSON for the current element). The latter will contain elements called comment and pod, etc..
Because we are only interested in elements where pod is equal to fb, we add a where clause, using json_extract() to get at pod (where $.pod is relative to value returned by the json_each function).
Nested List
If your JSON contains nested elements (something I didn't notice at first), then you need to use the json_tree() function instead of json_each(). Whereas the latter will only iterate over the immediate children of the node specified, json_tree() will descend recursively through all children from the node specified.
To give us some data to work with, I have augmented your test data with an extra element:
create table datatable ( path string , data json1 );
insert into datatable values("1" , json('
{
"ALL": [{
"comments": "your site is awesome",
"pod": "passcode",
"originalDirectory": "case1"
},
{
"comments": "your channel is good",
"data": ["youTube"],
"pod": "library"
},
{
"comments": "you like everything",
"data": ["facebook"],
"pod": "fb"
},
{
"data": ["twitter"],
"pod": "tw",
"ALL": [{
"data": [{
"codeLevel": "3"
}],
"pod": "mo",
"pod2": "p"
},
{
"comments": "inserted by TripeHound",
"data": ["facebook"],
"pod": "fb"
}]
}
]
}
'));
If we were to simply switch to using json_each(), then we see that a simple query (with no where clause) will return all elements of the source JSON:
select key, value
from datatable, json_tree( datatable.data, '$.ALL' ) limit 10 ;
ALL|[{"comments":"your site is awesome","pod":"passcode","originalDirectory":"case1"},{"comments":"your channel is good","data":["youTube"],"pod":"library"},{"comments":"you like everything","data":["facebook"],"pod":"fb"},{"data":["twitter"],"pod":"tw","ALL":[{"data":[{"codeLevel":"3"}],"pod":"mo","pod2":"p"},{"comments":"inserted by TripeHound","data":["facebook"],"pod":"fb"}]}]
0|{"comments":"your site is awesome","pod":"passcode","originalDirectory":"case1"}
comments|your site is awesome
pod|passcode
originalDirectory|case1
1|{"comments":"your channel is good","data":["youTube"],"pod":"library"}
comments|your channel is good
data|["youTube"]
0|youTube
pod|library
Because JSON objects are mixed in with simple values, we can no longer simply add where json_extract( value, '$.pod' ) = 'fb' because this produces errors when value does not represent an object. The simplest way around this is to look at the type values returned by json_each()/json_tree(): these will be the string object if the row represents a JSON object (see above documentation for other values).
Adding this to the where clause (and relying on "short-circuit evaluation" to prevent json_extract() being called on non-object rows), we get:
select key, value
from datatable, json_tree( datatable.data, '$.ALL' )
where type = 'object'
and json_extract( value, '$.pod' ) = 'fb' ;
which returns:
2|{"comments":"you like everything","data":["facebook"],"pod":"fb"}
1|{"comments":"inserted by TripeHound","data":["facebook"],"pod":"fb"}
If desired, we could use json_extract() to break apart the returned objects:
.mode column
.headers on
.width 30 15 5
select json_extract( value, '$.comments' ) as Comments,
json_extract( value, '$.data' ) as Data,
json_extract( value, '$.pod' ) as POD
from datatable, json_tree( datatable.data, '$.ALL' )
where type = 'object'
and json_extract( value, '$.pod' ) = 'fb' ;
Comments Data POD
------------------------------ --------------- -----
you like everything ["facebook"] fb
inserted by TripeHound ["facebook"] fb
Note: If your structure contained other objects, of different formats, it may not be sufficient to simply select for type = 'object': you may have to devise a more subtle filtering process.

Multiple SELECT statements into a single JSON

I'm convinced this must be answered somewhere but for the life of me I just can't seem to find anything no matter how much I change my search phrases.
I need to select data from two completely independent tables and export the information to JSON. In this case, they're both 1 record in each table.
If I select just 1 at a time and export to JSON, they're 1 record, but when I join the two single records in SQL and then export to JSON, they're 1 record arrays.
Just 1 record SQL Input:
DECLARE #Json nvarchar(max) =
(
SELECT 'Data1' AS [Data1], 'Data2' AS [Data2]
FOR JSON PATH
, INCLUDE_NULL_VALUES
, WITHOUT_ARRAY_WRAPPER
);
SELECT #Json;
GO
Just 1 record JSON Output (note there's no array):
{
"Data1": "Data1",
"Data2": "Data2"
}
2 record SQL Input:
DECLARE #Json nvarchar(max) =
(
SELECT
(
SELECT 'Data1' AS [Data1], 'Data2' AS [Data2]
FOR JSON PATH
, INCLUDE_NULL_VALUES
) AS [Part1]
,
(
SELECT 'Text1' AS [Text1], 'Text2' AS [Text2]
FOR JSON PATH
, INCLUDE_NULL_VALUES
) AS [Part2]
FOR JSON PATH
, WITHOUT_ARRAY_WRAPPER
);
SELECT #Json;
GO
2 record JSON Output (note the inclusion of arrays):
{
"Part1": [
{
"Data1": "Data1",
"Data2": "Data2"
}
],
"Part2": [
{
"Text1": "Text1",
"Text2": "Text2"
}
]
}
I "think" that WITHOUT_ARRAY_WRAPPER is the correct attribute to add which will resolve this but as soon as I add that, I get the entire record as a string:
{
"Part1": "{\"Data1\":\"Data1\",\"Data2\":\"Data2\"}",
"Part2": "{\"Text1\":\"Text1\",\"Text2\":\"Text2\"}"
}
I understand that there's text manipulation methods I can use to get this to work, but I'm hoping for a clean SQL > JSON statement.
I'm currently working on SQL Server 2016 but I can if necessary get a 2017 or 2019 server. Not sure if later SQL handles this better or if it's just my query that needs optimisation.
Edit: My desired output is:
{
"Part1": {
"Data1": "Data1",
"Data2": "Data2"
},
"Part2": {
"Text1": "Text1",
"Text2": "Text2"
}
}
According to the accepted answer of FOR JSON PATH. how to not use escape characters on SQL Server's forum on MSDN:
FOR JSON will escape any text unless if it is generated as JSON result by some JSON function/query. In your example, FOR JSON cannot know do you really want raw JSON or you are just sending some free text that looks like JSON.
Properly defined JSON is generated with FOR JSON (unless if it has WITHOUT_ARRAY_WRAPPER option) or JSON_QUERY. If you wrap your JSON literal with JSON_QUERY it will not be escaped.
This answer got me to try the following code:
DECLARE #Json nvarchar(max) =
(
SELECT
JSON_QUERY((
SELECT 'Data1' AS [Data1], 'Data2' AS [Data2]
FOR JSON PATH
, INCLUDE_NULL_VALUES
, WITHOUT_ARRAY_WRAPPER
)) AS [Part1]
,
JSON_QUERY((
SELECT 'Text1' AS [Text1], 'Text2' AS [Text2]
FOR JSON PATH
, INCLUDE_NULL_VALUES
, WITHOUT_ARRAY_WRAPPER
)) AS [Part2]
FOR JSON PATH
, WITHOUT_ARRAY_WRAPPER
);
SELECT #Json;
As as it turns out - this is working like a charm. Results:
{
"Part1": {
"Data1": "Data1",
"Data2": "Data2"
},
"Part2": {
"Text1": "Text1",
"Text2": "Text2"
}
}
DB<>Fiddle
Update
Look what I found buried in official documentation:
To avoid automatic escaping, provide newValue by using the JSON_QUERY function. JSON_MODIFY knows that the value returned by JSON_MODIFY is properly formatted JSON, so it doesn't escape the value.

How to query JSON values from a column into one JSON array in MS-SQL 2016?

I'm starting to fiddle out how to handle JSON in MSSQL 2016+
I simply created a table having a ID (int) and a JSON (nvarchar) column.
Here are my queries to show the issue:
First query just returns the relational table result, nice and as expected.
SELECT * FROM WS_Test
-- Results:
1 { "name": "thomas" }
2 { "name": "peter" }
Second query returns just the json column as "JSON" created my MSSQL.
Not nice, because it outputs the json column content as string and not as parsed JSON.
SELECT json FROM WS_Test FOR JSON PATH
-- Results:
[{"json":"{ \"name\": \"thomas\" }"},{"json":"{ \"name\": \"peter\" }"}]
Third query gives me two result rows with json column content as parsed JSON, good.
SELECT JSON_QUERY(json, '$') as json FROM WS_Test
-- Results:
{ "name": "thomas" }
{ "name": "peter" }
Fourth query gives me the json column contents as ONE (!) JSON object, perfectly parsed.
SELECT JSON_QUERY(json, '$') as json FROM WS_Test FOR JSON PATH
-- Results:
[{"json":{ "name": "thomas" }},{"json":{ "name": "peter" }}]
BUT:
I don't want to have the "json" property containing the json column content in each array object of example four. I just want ONE array containing the column contents, not less, not more. Like this:
[
{
"name": "peter"
},
{
"name": "thomas"
}
]
How can I archive this with just T-SQL? Is this even possible?
The FOR JSON clause will always include the column names - however, you can simply concatenate all the values in your json column into a single result, and then add the square brackets around that.
First, create and populate sample table (Please save us this step in your future questions):
CREATE TABLE WS_Test
(
Id int,
Json nvarchar(1000)
);
INSERT INTO WS_Test(Id, Json) VALUES
(1, '{ "name": "thomas" }'),
(2, '{ "name": "peter" }');
For SQL Server 2017 or higher, use the built in string_agg function:
SELECT '[' + STRING_AGG(Json, ',') + ']' As Result
FROM WS_Test
For lower versions, you can use for xml path with stuff to get the same result as the string_agg:
SELECT STUFF(
(
SELECT ',' + Json
FROM WS_Test
FOR XML PATH('')
), 1, 1, '[')+ ']' As Result
The result for both of these queries will be this:
Result
[{ "name": "thomas" },{ "name": "peter" }]
You can see a live demo on DB<>Fiddle