SQL Server OPENJSON read nested JSON same key array - open-json

I am following the idea from Ed.Schavelev on article 'SQL Server OPENJSON read nested json'. But I need instead of repeating the rows I wanted them (4) to be in the columns. Is this possible?
I have tried as below:
DECLARE #json NVARCHAR(MAX)
SET #json =
N'[
{
"EmployeeScheduleXRefCode":"#DF_10212",
"EmployeeXRefCode":"7274",
"TimeStart":"2022-10-31T05:30:00",
"TimeEnd":"2022-10-31T15:30:00",
"Published":false,
"Breaks":[
],
"Activities":[
],
"Skills":[
],
"LaborMetrics":[
{
"CodeXRefCode":"MEDIUM",
"TypeXRefCode":"ACUITY_LM"
},
{
"CodeXRefCode":"ATA",
"TypeXRefCode":"EMPLOYEE_TYPE_LM"
},
{
"CodeXRefCode":"BM02KR",
"TypeXRefCode":"TEAM_NAME_LM"
},
{
"CodeXRefCode":"AV_LM",
"TypeXRefCode":"JOB_TYPE_LM"
}
],
"Segments":[
]
},
{
"EmployeeScheduleXRefCode":"#DF_10200",
"EmployeeXRefCode":"7269",
"TimeStart":"2022-10-31T05:30:00",
"TimeEnd":"2022-10-31T15:30:00",
"Published":false,
"Breaks":[
],
"Activities":[
],
"Skills":[
],
"LaborMetrics":[
{
"CodeXRefCode":"MEDIUM",
"TypeXRefCode":"ACUITY_LM"
},
{
"CodeXRefCode":"ATA",
"TypeXRefCode":"EMPLOYEE_TYPE_LM"
},
{
"CodeXRefCode":"BM01KR",
"TypeXRefCode":"TEAM_NAME_LM"
},
{
"CodeXRefCode":"AV_LM",
"TypeXRefCode":"JOB_TYPE_LM"
}
],
"Segments":[
]
}
]'
SELECT Shifts.*,LaborMetrics.* FROM
OPENJSON ( #json )
WITH (
EmployeeScheduleXRefCode varchar(200) '$.EmployeeScheduleXRefCode' ,
EmployeeXRefCode varchar(200) '$.EmployeeXRefCode',
TimeStart Datetime '$.TimeStart',
TimeEnd Datetime '$.TimeEnd',
LaborMetrics nvarchar(max) as json
) as Shifts
cross apply openjson (Shifts.LaborMetrics)
with
(
CodeXRefCode nvarchar(100),
TypeXRefCode nvarchar(100)
) as LaborMetrics
But the result is repeating 4 rows and I want them to have them in 4 different columns in a single row.

Related

How do I parse nested JSON-objects in SQL?

I have a string of JSON, returned by an API, that looks something like this:
{
"data": {
"transactions": {
"edges": [
{
"node": {
"text": "debet",
"invoiceAmount": "1.0000"
}
},
{
"node": {
"text": "kredit",
"invoiceAmount": "-1.0000"
}
}
]
}
}
}
It is valid JSON, however the only way that I am able to parse in sql is by manipulating the string first. Like this:
[
{
"node": {
"text": "debet",
"invoiceAmount": "1.0000"
}
},
{
"node": {
"text": "kredit",
"invoiceAmount": "-1.0000"
}
}
]
E.g. below. How do I achieve this without manipulating the string?
DECLARE #json NVARCHAR(MAX);
SET #json = N'[{"node":{"text":"debet","invoiceAmount":"1.0000"}},{"node":{"text":"kredit","invoiceAmount":"-1.0000"}}]';
SELECT *
FROM OPENJSON(#json)
WITH (
text NVARCHAR(50) '$.node.text',
invoiceAmount MONEY '$.node.surname'
)
I tried $.data.transactions.edges.node.text. SQL returns NULL
You can use
SELECT *
FROM OPENJSON(JSON_QUERY(#json, '$.data.transactions.edges'))
WITH (
text VARCHAR(100) '$.node.text',
invoiceAmount DECIMAL(10,4) '$.node.invoiceAmount'
)
to get the desired values out of your original string.
(DB Fiddle)
The JSON_QUERY first extracts the array and then that is expanded out with OPENJSON.

Select from json data is resulting in null values. How to select from this simple json data

I have the following JSON data:
declare #json nvarchar(max)=
'
[{
"images": [{
"key": "c:\\stapler\\booktest\\gray00024.jp2",
"imageNumber": 1
}],
"instrumentNumber": 94109416
},
{
"images": [{
"key": "c:\\stapler\\booktest\\gray00025.jp2",
"imageNumber": 1
},
{
"key": "c:\\stapler\\booktest\\gray00026.jp2",
"imageNumber": 2
}
],
"instrumentNumber": 94109417
},
{
"images": [{
"key": "c:\\stapler\\booktest\\gray00027.jp2",
"imageNumber": 1
},
{
"key": "c:\\stapler\\booktest\\gray00028.jp2",
"imageNumber": 2
}
],
"instrumentNumber": 94109418
}
]'
I am trying to pull the key and imageNumber from each of these records.
I have tried this query which will give me the instrument number, but gives NULL skey and imageNumber values:
select * from openjson (#json)
with(
instrumentNumber nvarchar(100) '$.instrumentNumber',
skey nvarchar(500) '$.images.key',
imageNumber nvarchar(500) '$.images.imageNumber')
Can anyone tell me what I am doing wrong?
Because you have a nested array (images) you need to extract those values in another openjson which you can then cross apply to the original:
select a.instrumentNumber, b.skey, b.imageNumber
from openjson (#json)
with (
instrumentNumber nvarchar(100) '$.instrumentNumber',
images nvarchar(max) as json
) a
cross apply openjson (a.images)
with (
skey nvarchar(500) '$.key',
imageNumber nvarchar(500) '$.imageNumber'
) b
Output for your sample data
instrumentNumber skey imageNumber
94109416 c:\stapler\booktest\gray00024.jp2 1
94109417 c:\stapler\booktest\gray00025.jp2 1
94109417 c:\stapler\booktest\gray00026.jp2 2
94109418 c:\stapler\booktest\gray00027.jp2 1
94109418 c:\stapler\booktest\gray00028.jp2 2
Demo on dbfiddle.uk

Is there an UPDATE equivalent command to SELECT json data

Is there a way to update the data retrieved from the below select (in this case, change "MS220" to something else)? It's difficult enough to do select from JSON in some cases. I'm not sure how to update just a single element.
CREATE TABLE JData (
JsonData nvarchar(max)
)
INSERT INTO JData
(JsonData)
VALUES
('[
{
"Categories": [
{
"QuerySourceNames": [
"QAsset"
],
"Id": "eceae85a-ffc6-49f4-8f6a-78ce2b4b274e",
"Name": "emsdba"
}
],
"Id": "525b4f07-0f67-43ac-8070-a0e6c1ceb1b9",
"Name": "MS220"
}
]')
SELECT
ParamName
FROM [dbo].[JData] jsonData
CROSS APPLY OPENJSON (jsonData)
WITH
(
Categories nvarchar(max) AS json,
Id uniqueidentifier,
ParamName varchar(10) '$.Name'
);
Try JSON_MODIFY() with the path '$[0].Name'
UPDATE d
SET jsonData = JSON_MODIFY(jsonData, '$[0].Name', 'New Value')
FROM [dbo].[JData] d
Results:
[
{
"Categories": [
{
"QuerySourceNames": [
"QAsset"
],
"Id": "eceae85a-ffc6-49f4-8f6a-78ce2b4b274e",
"Name": "emsdba"
}
],
"Id": "525b4f07-0f67-43ac-8070-a0e6c1ceb1b9",
"Name": "New Value"
}
]
db<>fiddle here

Update JSON array using SQL Server

I have JSON like below:
{
"property": {
"commonProperty": "abc",
"Items": [
{
"ID": 1,
"Name": "a"
},
{
"ID": 2,
"Name": "a"
},
{
"ID": 3,
"Name": "b"
}
]
}
}
And what I want to achieve is to update Names to "c" where it's currently "a" using SQL Server (so I want to have result like below).
{
"property": {
"commonProperty": "abc",
"Items": [
{
"ID": 1,
"Name": "c"
},
{
"ID": 2,
"Name": "c"
},
{
"ID": 3,
"Name": "b"
}
]
}
}
As far as I know I cannot use JSON_MODIFY because it does not handles queries inside it's arguments and OPENJSON cannot be updated. Is there any method I can use?
EDIT note: added common property above Items.
You may try one of the following options:
Parse the '$.property.Items' JSON array as table using OPENJSON(), make an update, output the table's content as JSON using FOR JSON and modify the original JSON using JSON_MODIFY():
Build a dynamic statement. You can modify the input JSON using JSON_MODIFY() and the appropriate path. The path needs to be defined as a literal or from SQL Server 2017 as a variable, but using a wildcard is not possible (e.g. the statement SELECT #json = JSON_MODIFY(#json, '$.property.Items[0].Name', N'c') modifies the first item in the Items JSON array).
JSON:
DECLARE #json nvarchar(max) = N'{
"property":{
"commonProperty":"abc",
"Items":[
{
"ID":1,
"Name":"a"
},
{
"ID":2,
"Name":"a"
},
{
"ID":3,
"Name":"b"
}
]
}
}'
Statement (that parses the JSON as a table, modifies the table and outputs the table's content as JSON):
SELECT #json = JSON_MODIFY(
#json,
'$.property.Items',
(
SELECT
ID AS 'ID',
CASE WHEN Name = N'a' THEN N'c' ELSE Name END AS 'Name'
FROM OPENJSON(#json, '$.property.Items') WITH (ID int, Name nvarchar(50))
FOR JSON PATH
)
)
Dynamic statement:
DECLARE #stm nvarchar(max)
SET #stm = (
SELECT N'SET #json = JSON_MODIFY(#json, ''$.property.Items[' + [key] + N'].Name'', N''c''); '
FROM OPENJSON(#json, '$.property.Items')
WHERE JSON_VALUE([value], '$.Name') = N'a'
FOR XML PATH('')
)
PRINT #stm
EXEC sp_executesql #stm, N'#json nvarchar(max) OUTPUT', #json OUTPUT

How can I correctly translate this complicated JSON into a SQL table?

I have the following JSON (some people may recall I've posted here before about a care management system I'm extracting info from in JSON form) to then use in an automated process of KPI gathering. A single resident strand of this particular JSON export is below, though there is one for each resident contained within the extract:
{
"ServiceUserDetails": [
{
"CellDetails": [
{
"CareNotes": []
},
{
"CareNotes": []
},
{
"CareNotes": []
},
{
"CareNotes": []
},
{
"CareNotes": []
},
{
"CareNotes": []
},
{
"CareNotes": []
},
{
"CareNotes": []
},
{
"CareNotes": []
},
{
"CareNotes": []
},
{
"CareNotes": []
},
{
"CareNotes": []
},
{
"CareNotes": []
},
{
"CareNotes": []
},
{
"CareNotes": []
},
{
"CareNotes": []
},
{
"CareNotes": []
},
{
"CareNotes": []
},
{
"CareNotes": []
},
{
"CareNotes": [
{
"careNoteID": "00000000-0000-0000-0000-000000000000",
"CareAttempted": true,
"hour": null,
"DateDone": "2019-10-20T18:51:36.966Z",
"worker": null,
"sliderOpt": 0,
"DisplayAnswerType": 1
}
]
},
{
"CareNotes": []
},
{
"CareNotes": []
},
{
"CareNotes": []
},
{
"CareNotes": []
},
{
"CareNotes": [
{
"careNoteID": "00000000-0000-0000-0000-000000000000",
"CareAttempted": true,
"hour": null,
"DateDone": "2019-10-25T12:55:02.006Z",
"worker": null,
"sliderOpt": 3,
"DisplayAnswerType": 1
}
]
},
{
"CareNotes": []
},
{
"CareNotes": []
},
{
"CareNotes": [
{
"careNoteID": "00000000-0000-0000-0000-000000000000",
"CareAttempted": true,
"hour": null,
"DateDone": "2019-10-28T13:42:42.136Z",
"worker": null,
"sliderOpt": 2,
"DisplayAnswerType": 1
},
{
"careNoteID": "00000000-0000-0000-0000-000000000000",
"CareAttempted": true,
"hour": null,
"DateDone": "2019-10-28T22:29:06.226Z",
"worker": null,
"sliderOpt": 4,
"DisplayAnswerType": 1
}
]
},
{
"CareNotes": [
{
"careNoteID": "00000000-0000-0000-0000-000000000000",
"CareAttempted": true,
"hour": null,
"DateDone": "2019-10-29T13:52:32.961Z",
"worker": null,
"sliderOpt": 7,
"DisplayAnswerType": 1
}
]
},
{
"CareNotes": []
},
{
"CareNotes": [
{
"careNoteID": "00000000-0000-0000-0000-000000000000",
"CareAttempted": true,
"hour": null,
"DateDone": "2019-10-31T16:36:05.347Z",
"worker": null,
"sliderOpt": 4,
"DisplayAnswerType": 1
},
{
"careNoteID": "00000000-0000-0000-0000-000000000000",
"CareAttempted": true,
"hour": null,
"DateDone": "2019-10-31T17:53:42.264Z",
"worker": null,
"sliderOpt": 2,
"DisplayAnswerType": 1
}
]
}
],
"ServiceUserName": "Resident Name",
"ServiceUserID": "45k045839-d88b-436b-92b3-8829dce683be",
"ServiceUserDateOfBirth": "16/01/30"
}
]
}
Basically, this is data regarding a number of days when a resident has potentially been for a number two! In this single example, the person in question is independent and only occasionally requires assistance, and the data export shows each day in the month, with the majority of "CareNotes" being empty to reflect this.
I have created the following query in SQL:
use CMUtility;
DECLARE #JSON VARCHAR(MAX)
SELECT #JSON = BulkColumn
FROM OPENROWSET
(BULK 'C:\Users\user\Documents\Embarcadero\Studio\Projects\CMUServer\Win32\Debug\BowelMovementChart-Home Name.json', SINGLE_CLOB)
AS j
DROP TABLE IF EXISTS [JSONBMs-Home Name]
SELECT * INTO [JSONBMs-Home Name]
FROM
OPENJSON(#JSON, '$.ServiceUserDetails')
WITH
(
ServiceUserName nvarchar(100) '$.ServiceUserName',
ServiceUserID nvarchar(255) '$.ServiceUserID',
ServiceUserDateOfBirth nvarchar(10) '$.ServiceUserDateOfBirth',
CareNotes nvarchar(max) '$.CellDetails' as JSON
) as j1
CROSS APPLY OPENJSON(j1.CareNotes) WITH
(
CareNoteID nvarchar(200) '$.CareNoteID',
CareAttempted nvarchar(10) '$.CareAttempted',
[hour] nvarchar(50) '$.hour',
DateDone nvarchar(10) '$.DateDone',
worker nvarchar(100) '$.worker',
sliderOpt nvarchar(10) '$.sliderOpt',
DisplayAnswerType nvarchar(10) '$.DisplayAnswerType'
) j2
SELECT * FROM [JSONBMs-Home Name]
If I run the code as is, I get the following result (1302 rows):
But if I quit at the end of the j1, before the cross apply and then run a select * from table, I get the following 42 rows (which is the number of residents):
It is obviously in the second half of this code that I am going wrong. In other JSON parsing, I've had to use OUTER APPLY rather than CROSS APPLY, but interchanging them has no impact on the results. The CareNotes JSON fields remain NULL, and I think it is to do with the number of additional elements in here? 🤷‍♂️ I still don't 100% understand the difference between objects and arrays within JSON, and this has puzzled me for the last 10hours or so!
My understanding would be that to get to the additional JSON in CareNotes, I would need to point from ServiceUserDetails > CellDetails > CareNotes, but it is at this point I come unstuck. If I add ", '$.CareNotes'" to the second half's OPENJSON, then I get no results at all. I would appreciate any assistance!!
Thanks
Ant
EDIT: I've added an additional layer of cross apply, to make it now look like this, and I do now get results in the last variables:
select * into [JSONBMs-Home Name]
from
OPENJSON(#JSON, '$.ServiceUserDetails')
with
(
ServiceUserName nvarchar(100) '$.ServiceUserName',
ServiceUserID nvarchar(255) '$.ServiceUserID',
ServiceUserDateOfBirth nvarchar(10) '$.ServiceUserDateOfBirth',
CellDetails nvarchar(max) '$.CellDetails' as JSON
) as j1
cross apply openjson(j1.CellDetails) with
(
CareNotes nvarchar(max) '$.CareNotes' as JSON
) as j2
cross apply openjson(j2.CareNotes) with
(
CareNoteID nvarchar(200) '$.CareNoteID',
CareAttempted nvarchar(10) '$.CareAttempted',
[hour] nvarchar(50) '$.hour',
DateDone nvarchar(10) '$.DateDone',
worker nvarchar(100) '$.worker',
sliderOpt nvarchar(10) '$.sliderOpt',
DisplayAnswerType nvarchar(10) '$.DisplayAnswerType'
) j3
select * from [JSONBMs-Home Name]
My results are now like this:
I think this is now what I was looking for, because if then order the output by ServiceUserName it shows me the individual entries that appear in the report itself. So I think I've answered my own question!
as a rule of thumb, every time there is a json array an apply operator is needed.
since CareNotes is an array, you'll need an APPLY on the CareNotes to get its elements.
SELECT *
FROM
OPENJSON(#JSON, '$.ServiceUserDetails')
WITH
(
ServiceUserName nvarchar(100) '$.ServiceUserName',
ServiceUserID nvarchar(255) '$.ServiceUserID',
ServiceUserDateOfBirth nvarchar(10) '$.ServiceUserDateOfBirth',
CareNotes nvarchar(max) '$.CellDetails' as JSON
) as j1
OUTER APPLY OPENJSON(j1.CareNotes) --, '$')
WITH
(
CareNotesElements nvarchar(max) '$.CareNotes' AS JSON
) as notearray
OUTER APPLY OPENJSON(notearray.CareNotesElements) WITH
(
CareNoteID nvarchar(200) '$.careNoteID', ---typo here, json has careN.. ,query had CareN...
CareAttempted nvarchar(10) '$.CareAttempted',
[hour] nvarchar(50) '$.hour',
DateDone nvarchar(10) '$.DateDone',
worker nvarchar(100) '$.worker',
sliderOpt nvarchar(10) '$.sliderOpt',
DisplayAnswerType nvarchar(10) '$.DisplayAnswerType'
) j2
Because I hadn't 'drilled down' low enough, I needed to add another cross apply to get to the JSON itself.
This is my final code, which does more or less give me the data I need. Apologies if this was obvious to some, but this isn't my day job and I'm learning as I go!
Thanks
use CMUtility;
DECLARE #JSON VARCHAR(MAX)
SELECT #JSON = BulkColumn
FROM OPENROWSET
(BULK 'C:\Users\user\Documents\Embarcadero\Studio\Projects\CMUServer\Win32\Debug\BowelMovementChart-Home Name.json', SINGLE_CLOB)
AS j
drop table if exists [JSONBMs-Home Name]
select * into [JSONBMs-Home Name]
from
OPENJSON(#JSON, '$.ServiceUserDetails')
with
(
ServiceUserName nvarchar(100) '$.ServiceUserName',
ServiceUserID nvarchar(255) '$.ServiceUserID',
ServiceUserDateOfBirth nvarchar(10) '$.ServiceUserDateOfBirth',
CellDetails nvarchar(max) '$.CellDetails' as JSON
) as j1
cross apply openjson(j1.CellDetails) with
(
CareNotes nvarchar(max) '$.CareNotes' as JSON
) as j2
cross apply openjson(j2.CareNotes) with
(
CareNoteID nvarchar(200) '$.CareNoteID',
CareAttempted nvarchar(10) '$.CareAttempted',
[hour] nvarchar(50) '$.hour',
DateDone nvarchar(10) '$.DateDone',
worker nvarchar(100) '$.worker',
sliderOpt nvarchar(10) '$.sliderOpt',
DisplayAnswerType nvarchar(10) '$.DisplayAnswerType'
) j3
select * from [JSONBMs-Home Name]
order by ServiceUserName