Using JSON_VALUE + JSON_QUERY to create new JSON - json

I was wondering if it would be possible to reshape JSON and return it as JSON. I have JSON which, in simplified form, looks like:
Name
Details
fieldId
fieldValue
Other
Id
Value
And would like to return:
Name
Details
fieldId
fieldValue
I can return Name and Details with JSON_VALUE and JSON_QUERY but would like it as one combined JSON field.
create table #test (
[id] int,
[json] varchar(max)
);
insert into #test (id, json) values (1, '{
"Name": "Test 1",
"Details": [
{
"fieldId": "100",
"fieldValue": "ABC"
}],
"Other": [
{
"Id": "1",
"Value": "ABC"
}]
}');
insert into #test (id, json) values (2, '{
"Name": "Test 2",
"Details": [
{
"fieldId": "101",
"fieldValue": "ABCD"
}],
"Other": [
{
"Id": "2",
"Value": "ABCD"
}]
}');
select id, JSON_VALUE(json, '$.Name'), JSON_QUERY(json, '$.Details')
from #test

As an additional option, you may parse the JSON with OPENJSON() and explicit schema (columns definitions) and then build the new JSON again:
SELECT
id,
newjson = (
SELECT Name, Details
FROM OPENJSON(json) WITH (
Name varchar(max) '$.Name',
Details nvarchar(max) '$.Details' AS JSON
)
FOR JSON PATH, WITHOUT_ARRAY_WRAPPER
)
FROM #test
And the same approach with JSON_VALUE() and JSON_QUERY():
SELECT
id,
newjson = (
SELECT JSON_VALUE(json, '$.Name') AS [Name], JSON_QUERY(json, '$.Details') AS [Details]
FOR JSON PATH, WITHOUT_ARRAY_WRAPPER
)
FROM #test

One solution is to use JSON_MODIFY to re-construct the JSON:
SELECT
id,
JSON_MODIFY(
JSON_MODIFY(
'{}',
'$.Name',
JSON_VALUE(json, '$.Name')
),
'$.Details',
JSON_QUERY(json, '$.Details')
) AS new_json
FROM #test
An alternate would be to delete the Other node using JSON_MODIFY but you have to know the name of node(s) to remove.

Related

Conditionally update JSON column

I have a table which has ID & JSON columns. ID is auto incrementing column. Here are my sample data.
Row 1
1 | {
"HeaderInfo":
{
"Name": "ABC",
"Period": "2010",
"Code": "123"
},
"HData":
[
{ "ID1": "1", "Value": "$1.00", "Code": "A", "Desc": "asdf" },
{ "ID1": "2", "Value": "$1.00", "Code": "B", "Desc": "pqr" },
{ "ID1": "3", "Value": "$1.00", "Code": "C", "Desc": "xyz" }
]
}
Row 2
2 | {
"HeaderInfo":
{
"Name": "ABC",
"Period": "2010",
"Code": "123"
},
"HData":
[
{ "ID1": "76", "Value": "$1.00", "Code": "X", "Desc": "asdf" },
{ "ID1": "25", "Value": "$1.00", "Code": "Y", "Desc": "pqr" },
{ "ID1": "52", "Value": "$1.00", "Code": "Z", "Desc": "lmno" },
{ "ID1": "52", "Value": "$1.00", "Code": "B", "Desc": "xyz" }
]
}
and it keep goes. Items inside the HData section is infinite. It can be any numbers of items.
On this JSON I need to update the Value = "$2.00" where "Code" is "B". I should be able to do this with 2 scenarios. My parameter inputs are #id=2, #code="B", #value="$2.00". #id sometimes will be null. So,
If #id is null then the update statement should go through all records and update the Value="$2.00" for all items inside the HData section which has Code="B".
If #id = 2 then the update statement should update only the second row which Id is 2 for the items which Code="b"
Appreciate your help in advance.
Thanks
See DB Fiddle for an example.
declare #id bigint = 2
, #code nvarchar(8) = 'B'
, #value nvarchar(8) = '$2.00'
update a
set json = JSON_MODIFY(json, '$.HData[' + HData.[key] + '].Value', #value)
from so75416277 a
CROSS APPLY OPENJSON (json, '$.HData') HData
CROSS APPLY OPENJSON (HData.Value, '$')
WITH (
ID1 bigint
, Value nvarchar(8)
, Code nvarchar(8)
, [Desc] nvarchar(8)
) as HDataItem
WHERE id = #id
AND HDataItem.Code = #Code
The update / set statement says we want to replace the value of json with a new generated value / functions exactly the same as it would in any other context; e.g. update a set json = 'something' from so75416277 a where a.column = 'some condition'
The JSON_MODIFY does the manipulation of our json.
The first input is the original json field's value
The second is the path to the value to be updated.
The third is the new value
'$.HData[' + HData.[key] + '].Value' says we go from our JSON's root ($), find the HData field, filter the array of values for the one we're after (i.e. key here is the array item's index), then use the Value field of this item.
key is a special term; where we don't have a WITH block accompanying our OPENJSON statement we get back 3 items: key, value and type; key being the identifier, value being the content, and type saying what sort of content that is.
CROSS APPLY allows us to perform logic on a value from a single DB rowto return potentially multiple rows; e.g. like a join but against its own contents.
OPENJSON (json, '$.HData') HData says to extract the HData field from our json column, and return this with the table alias HData; as we've not included a WITH, this HData column has 3 fields; key, value, and type, as mentioned above (this is the same key we used in our JSONMODIFY).
The next OPENJSON works on HData.Value; i.e. the contents of the array item under HData. Here we take the object from this array (i.e. that's the root from the current context; hence $), and use WITH to parse it into a specific structure; i.e. ID1, Value, Code, and Desc (brackets around Desc as it's a keyword). We give this the alias HDataItem.
Finally we filter for the bit of the data we're interested in; i.e. on id to get the row we want to update, then on HDataItem.Code so we only update those array items with code 'B'.
Try the below SP.
CREATE PROC usp_update_75416277
(
#id Int = null,
#code Varchar(15),
#value Varchar(15)
)
AS
BEGIN
SET NOCOUNT ON;
DECLARE #SQLStr Varchar(MAX)=''
;WITH CTE
AS
( SELECT ROW_NUMBER()OVER(PARTITION BY YourTable.Json ORDER BY (SELECT NULL))RowNo,*
FROM YourTable
CROSS APPLY OPENJSON(YourTable.Json,'$.HData')
WITH (
ID1 Int '$.ID1',
Value Varchar(20) '$.Value',
Code Varchar(20) '$.Code',
[Desc] Varchar(20) '$.Desc'
) HData
WHERE (#id IS NULL OR ID =#id)
)
SELECT #SQLStr=#SQLStr+' UPDATE YourTable
SET [JSON]=JSON_MODIFY(YourTable.Json,
''$.HData['+CONVERT(VARCHAR(15),RowNo-1)+'].Value'',
'''+CONVERT(VARCHAR(MAX),#value)+''') '+
'WHERE ID ='+CONVERT(Varchar(15),CTE.ID) +' '
FROM CTE
WHERE Code=#code
AND (#id IS NULL OR ID =#id)
EXEC( #SQLStr)
END

Format output as JSON object instead of array

Table:
sqlite> select * from t;
id name date
-- ---- ----
1 ... ...
2 ... ...
3 ... ...
Schema:
sqlite> .schema t
CREATE TABLE t (id integer primary key, name text, date text);
The output can be formatted in JSON as an array of objects:
sqlite> .mode json
sqlite> select * from t;
[
{"id": 1, "name": "...", "date": "..."},
{"id": 2, "name": "...", "date": "..."},
{"id": 3, "name": "...", "date": "..."}
]
However, I would like an object where the keys are integers and the values are objects:
{
1: {"name": "...", "date": "..."},
2: {"name": "...", "date": "..."},
3: {"name": "...", "date": "..."}
}
Is this possible with SQLite?
Use function json_object() to get a json object for the name and date of each row and json_group_object() to aggregate all rows:
SELECT json_group_object(
id,
json_object('name', name, 'date', date)
) AS result
FROM t;
See the demo.

Creating JSON Nested Level Arrays with Query

I'm trying to recreate a JSON output that is about 5 levels deep. I can recreate 2 levels deep, but after that, I'm stumped. It has worked using FOR JSON AUTO, but I need control over the naming and how they appear, so I need to be able to use FOR JSON PATH.
Below is sample code and would allow for a 3 level deep output. I'm hoping that whatever is needed to make it 3 levels, I can learn from to go to 5 levels.
CREATE TABLE [FamilyTree](
[ID] INT NOT NULL ,
[Name] VARCHAR(250) NOT NULL,
[ParentID] INT NOT NULL,
) ON [PRIMARY]
GO
INSERT [FamilyTree]([ID],[Name],[ParentID])
VALUES(1,N'Person1',0)
INSERT [FamilyTree]([ID],[Name],[ParentID])
VALUES(2,N'Person2',0)
INSERT [FamilyTree]([ID],[Name],[ParentID])
VALUES(3,N'Person3',1)
INSERT [FamilyTree]([ID],[Name],[ParentID])
VALUES(4,N'Person4',2)
INSERT [FamilyTree]([ID],[Name],[ParentID])
VALUES(5,N'Person5',3)
INSERT [FamilyTree]([ID],[Name],[ParentID])
VALUES(6,N'Person6',3)
INSERT [FamilyTree]([ID],[Name],[ParentID])
VALUES(7,N'Person7',4)
INSERT [FamilyTree]([ID],[Name],[ParentID])
VALUES(8,N'Person8',4)
INSERT [FamilyTree]([ID],[Name],[ParentID])
VALUES(9,N'Person9',4)
Using this query:
SELECT
FT1.Name AS [name],
(SELECT Name FROM FamilyTree WHERE ParentID = FT1.ID FOR JSON PATH) children
FROM FamilyTree FT1
WHERE FT1.ParentID = 0
FOR JSON PATH
I can get the following output:
[
{
"name": "Person1",
"children": [
{
"Name": "Person3"
}
]
},
{
"name": "Person2",
"children": [
{
"Name": "Person4"
}
]
}
]
but I want to be able to get it to go even further to look like this:
[
{
"name": "Person1",
"children": [
{
"Name": "Person3",
"children": [
{
"name": "Person 5",
"name": "Person 6",
}
]
}
]
},
{
"name": "Person2",
"children": [
{
"name": "Person4",
"children": [
{
"name": "Person 7",
"name": "Person 8",
"name": "Person 9"
}
]
}
]
}
]
You can just keep nesting subqueries if you want
SELECT
ft1.ID,
ft1.Name,
ft2.children
FROM FamilyTree ft1
CROSS APPLY (
SELECT
ft2.ID,
ft2.Name,
ft3.children
FROM FamilyTree ft2
CROSS APPLY (
SELECT
ft3.ID,
ft3.Name,
ft4.children
FROM FamilyTree ft3
CROSS APPLY (
SELECT
ft4.ID,
ft4.Name,
ft5.children
FROM FamilyTree ft4
CROSS APPLY (
SELECT
ft5.ID,
ft5.Name
FROM FamilyTree ft5
WHERE ft5.ParentID = ft4.ID
FOR JSON PATH
) ft5(children)
WHERE ft4.ParentID = ft3.ID
FOR JSON PATH
) ft4(children)
WHERE ft3.ParentID = ft2.ID
FOR JSON PATH
) ft3(children)
WHERE ft2.ParentID = ft1.ID
FOR JSON PATH
) ft2(children)
WHERE ft1.ParentID IS NULL
FOR JSON PATH;
db<>fiddle
To do this recursively, or for that matter to prevent duplication of code, you cannot use a Table Valued Function. You can only do this with a scalar UDF (oh the horror!).
CREATE OR ALTER FUNCTION dbo.GetJson (#ParentID int)
RETURNS nvarchar(max)
AS
BEGIN
RETURN (
SELECT
ft.ID,
ft.Name,
children = dbo.GetJson(ft.ID)
FROM FamilyTree ft
WHERE EXISTS (SELECT ft.ParentID INTERSECT SELECT #ParentID) -- null compare
FOR JSON PATH
);
END;
SELECT dbo.GetJson(NULL);
db<>fiddle
Note that in both of these examples, root nodes have a ParentID of NULL not 0. This is the correct way to do it, as there is no 0 row.
Try this, Mabe its usful:
DECLARE #FamilyTree TABLE (
[ID] INT NOT NULL ,
[Name] VARCHAR(250) NOT NULL,
[ParentID] INT NOT NULL
)
INSERT #FamilyTree([ID],[Name],[ParentID])
VALUES(1,N'Person1',0)
INSERT #FamilyTree([ID],[Name],[ParentID])
VALUES(2,N'Person2',0)
INSERT #FamilyTree([ID],[Name],[ParentID])
VALUES(3,N'Person3',1)
INSERT #FamilyTree([ID],[Name],[ParentID])
VALUES(4,N'Person4',2)
INSERT #FamilyTree([ID],[Name],[ParentID])
VALUES(5,N'Person5',3)
INSERT #FamilyTree([ID],[Name],[ParentID])
VALUES(6,N'Person6',3)
INSERT #FamilyTree([ID],[Name],[ParentID])
VALUES(7,N'Person7',4)
INSERT #FamilyTree([ID],[Name],[ParentID])
VALUES(8,N'Person8',4)
INSERT #FamilyTree([ID],[Name],[ParentID])
VALUES(9,N'Person9',4)
SELECT
FT1.Name AS [name],children.Name,grandchild.Name AS grandchild
FROM #FamilyTree FT1
INNER JOIN (SELECT * FROM #FamilyTree ) children ON children.ParentID = FT1.ID
INNER JOIN (SELECT * FROM #FamilyTree ) grandchild ON grandchild.ParentID = children.ID
WHERE FT1.ParentID = 0
FOR JSON AUTO
For each level, you can set another "INNER JOIN"
It's the result:
[
{
"name": "Person1",
"children": [
{
"Name": "Person3",
"grandchild": [
{
"grandchild": "Person5"
},
{
"grandchild": "Person6"
}
]
}
]
},
{
"name": "Person2",
"children": [
{
"Name": "Person4",
"grandchild": [
{
"grandchild": "Person7"
},
{
"grandchild": "Person8"
},
{
"grandchild": "Person9"
}
]
}
]
}
]

Parse JSON document into table

I'm working on parsing JSON document with an object array into a SQL Server table. I'm stuck with how to parse the complex object array.
I tried subproject.id and that did not work. I usually use the below code to parse a JSON array which works just fine but in this case it does not.
JSON:
{
"Data": [
{
"name": "ABC",
"date": "2020-01-20",
"subproject": [
{
"id": "123",
"projectname": "new1",
"refnum": "123:new1"
},
{
"id": "456",
"projectname": "new2",
"refnum": "456:new2"
}
],
"projectid": "1234",
"projectdate": "2020-01-27"
},
{
"name": "DEF",
"date": "2020-01-30",
"subproject": [
{
"id": "789",
"projectname": "new3",
"refnum": "789:new3"
},
{
"id": "901",
"projectname": "new4",
"refnum": "901:new4"
}
],
"projectid": "4567",
"projectdate": "2020-02-07"
}
]
}
SQL:
DECLARE #JSON VARCHAR(MAX)
SELECT #JSON = BulkColumn
FROM OPENROWSET (BULK 'c:/data/project.json', SINGLE_CLOB) X
SELECT *
FROM OPENJSON (#JSON)
WITH (
[name] NVARCHAR(256),
[date] DATE,
[projectid] INT,
[projectdate] DATE
)
You need to use AS JSON clause in the first OPENJSON() call (to specify that the $.subproject property contains an inner JSON array) and a combination of another OPENJSON() call and APPLY operator:
SELECT *
FROM OPENJSON (#JSON, '$.Data') WITH (
[name] NVARCHAR(256),
[date] DATE,
[projectid] INT,
[projectdate] DATE,
[subproject] NVARCHAR(MAX) '$.subproject' AS JSON
) j1
OUTER APPLY OPENJSON (j1.[subproject]) WITH (
[subprojectid] INT '$.id',
[subprojectname] NVARCHAR(256) '$.projectname',
[subprojectrefnum] NVARCHAR(256) '$.refnum'
) j2

how to add key-value pair in json root node and convert it into table using SQL server

I have table people and it's maintain Four column which is Name ,TagName ,Value , Location.
I want to convert the tagname and value in json with name and location column as rootnode (Name & location same for multiple records)
Need output as :
{
"{"Name":"EMP1","Location":"mumbai"}": [
{
"TagName": "1",
"Value": "844.17769999999996"
},
{
"TagName": "abc",
"Value": "837.43679999999995"
},
{
"TagName": "pqr",
"Value": "0"
},
{
"TagName": "XYZ",
"Value": "1049.2429999999999"
}
]
}
please check the below query, In which I am trying to create json string using json path but stuck in root node.
SELECT TagName
,Value
FROM dbo.people
FOR JSON PATH, ROOT('')---
when I convert the above json into tabular format, required output as :
Name | Location |TagName| Value
EMP1 | Mumbai |1 | 844.17769999999996|
EMP1 | Mumbai |abc | 837.43679999999995|
.....
Your expected output is not a valid JSON, but you are probably looking for something like this:
Table:
CREATE TABLE People (
[Name] varchar(10),
[Location] varchar(50),
[TagName] varchar(3),
[Value] numeric(20, 14)
)
INSERT INTO People ([Name], [Location], [TagName], [Value])
VALUES
('EMP1', 'Mumbai', '1', 844.17769999999996),
('EMP1', 'Mumbai', 'abc', 837.43679999999995),
('EMP2', 'Mumbai', 'abc', 837.43679999999995)
Statement:
SELECT DISTINCT p.[Name], p.[Location], c.Items
FROM People p
CROSS APPLY (
SELECT [TagName], [Value]
FROM People
WHERE [Name] = p.[Name] AND [Location] = p.[Location]
FOR JSON AUTO
) c (Items)
FOR JSON PATH
Result:
[
{
"Name":"EMP1",
"Location":"Mumbai",
"Items":[
{
"TagName":"1",
"Value":844.17769999999996
},
{
"TagName":"abc",
"Value":837.43679999999995
}
]
},
{
"Name":"EMP2",
"Location":"Mumbai",
"Items":[
{
"TagName":"abc",
"Value":837.43679999999995
}
]
}
]
If you want to parse the generated JSON, you need to use OPENJSON() twice:
Generated JSON:
DECLARE #json varchar(max) = N'[
{
"Name":"EMP1",
"Location":"Mumbai",
"Items":[
{
"TagName":"1",
"Value":844.17769999999996
},
{
"TagName":"abc",
"Value":837.43679999999995
}
]
},
{
"Name":"EMP2",
"Location":"Mumbai",
"Items":[
{
"TagName":"abc",
"Value":837.43679999999995
}
]
}
]'
Statement:
SELECT j1.Name, j1.Location, j2.TagName, j2.Value
FROM OPENJSON(#json) WITH (
[Name] varchar(10) '$.Name',
[Location] varchar(50) '$.Location',
[Items] nvarchar(max) '$.Items' AS JSON
) j1
OUTER APPLY OPENJSON(j1.Items) WITH (
[TagName] varchar(3) '$.TagName',
[Value] numeric(20, 14) '$.Value'
) j2