How to parse JSON string recursively with openjson - sql-server-2016

I have the following JSON data :
set #json = N'{
"Book":{
"IssueDate":"02-15-2019"
, "Detail":{
"Type":"Any Type"
, "Author":{
"Name":"Annie"
, "Sex":"Female"
}
}
, "Chapter":[
{
"Section":"1.1"
, "Title":"Hello world."
}
,
{
"Section":"1.2"
, "Title":"Be happy."
}
]
, "Sponsor":["A","B","C"]
}
}'
The expected result is
topKey Key Value
Book IssueDate 02-15-2019
Book Detail { "Type":"Any Type", "Author":{ "Name":"Annie" , "Sex":"Female"}
Book Chapter [{ "Section":"1.1", "Title":"Hello world." }, { "Section":"1.2", "Title":"Be happy." }]
Book Sponsor ["A","B","C"]
Detail Type Any Type
Detail Author { "Name":"Annie" ,"Sex":"Female"}
Author Name Annie
Author Sex Female
Chapter Section 1.1
Chapter Title Hello world
Chapter Section 1.2
Chapter Title Be happy.
I found that when the field "Value" is JSON, I need to keep parsing it.
So I created a function to do the parsing work but it returns '' which does not meet the requirement.
create function ParseJson(#json nvarchar(max))
returns #tempTable table ([key] nvarchar(max), [value] nvarchar(max))
as
begin
insert #tempTable
select
x.[key]
, x.[value]
from
openjson(#json) x
cross apply ParseJson(x.[value]) y
where ISJSON(x.[value])=1
end
A string may be passed to the function.
select * from ParseJson(#json)

I'm not sure if your expectation of the results is reasonable but clearly the returning table of your function doesn't match what you stated -- it lacks topKey column. For this reason, I'd rather aggregate the path of the hierarchy. Here we go:
create function ParseJson(
#parent nvarchar(max), #json nvarchar(max))
returns #tempTable table (
[key] nvarchar(max), [value] nvarchar(max))
as
begin
; with cte as (
select
iif(#parent is null, [key]
, concat(#parent, '.', [key])) [key]
, [value]
from
openjson(#json)
)
insert
#tempTable
select
x.*
from
cte x
union all
select
x.*
from
cte y
cross apply ParseJson(y.[key], y.[value]) x
where isjson(y.[value])=1
return
end
And the results:

Related

How to extract nested json?

I have a table name ‘my doc’. And there is a column which is a nested json called ‘element’. The structure is as below.
I wanna extract the element_id as a column. How to make it with sql? Thanks
{
“element”: {
“1”: {
“element_id”:”3jakd4ks”,
“type”: “square”,
“name”: “eggplant”
},
“2”: {
“element_id”:” ieh3iusk”;
“type”: “circle”,
“name”: “orange”
},
“3”: {
“element_id”:”766wjdhh”;
“type”: “circle”,
“name”: “apple”
}
}
}
If you are working with SQL Server, and your data is stored in a table, the syntax would be something like this. Your schema may be different, but refer to Microsoft documentation for more details.
https://learn.microsoft.com/en-us/sql/relational-databases/json/json-data-sql-server?view=sql-server-ver15
SELECT v.*
FROM Table1 AS t
CROSS APPLY
OPENJSON(t.JSONColumn)
WITH (
ResultID NVARCHAR(MAX) '$.element' AS JSON
) AS Returned
CROSS APPLY (
SELECT element_id
,type
,name
FROM
OPENJSON(Returned.ResultID)
WITH (
element_id VARCHAR(MAX) '$.element_id'
,type VARCHAR(MAX) '$.type'
,name VARCHAR(MAX) '$.name'
)
) AS v;

How to extract specific text from column

I am using SQL Server 2016 where I am loading JSON Response in one of the nvarchar(max) type column.
Now I want to create a derived column or maybe even a view that will find the particular value in that complete JSON Response and display only that in the new derived column.
Example:
**Complete_JSON_Repsone** --> this is SQL column
{"result":{"banner_image_light":"","country":"USA","parent":"","notes":"","stock_symbol":"","u_op_dev_version":"","u_restriciton":"No","discount":"","sys_id":"7a2c008c1b07ac50a62cea0ce54bcbe8","market_cap":"0","customer":"false"}}
I tried the below query, but it's not giving the expected results it prints out everything after sys_id:
SELECT
Substring (
a.Complete_JSON_Repsone,
Charindex( '"sys_id":', Complete_JSON_Repsone) + 1,
Len(Complete_JSON_Repsone)
) AS [Sys_Idd]
FROM <table-name> a
Current output (actual result):
Sys_Idd
sys_id":"7a2c008c1b07ac50a62cea0ce54bcbe8","market_cap":"0","customer":"false"}}
Expected output:
Sys_Idd
7a2c008c1b07ac50a62cea0ce54bcbe8
UPDATE
Sample Input :
Create table dbo.log1
(
Id varchar(50),
Complete_JSON_Response nvarchar(max),
Sys_Id varchar(50)
)
insert into dbo.log1 (Id,Complete_JSON_Response)
values ('S1','{"result":{"banner_image_light":"","country":"USA","parent":"","notes":"","stock_symbol":"","u_op_dev_version":"","u_restriciton":"No","discount":"","sys_id":"7a2c008c1b07ac50a62cea0ce54bcbe8","market_cap":"0","customer":"false"}}')
,('S2','{"result":{"banner_image_light":"","country":"Aus","parent":"","notes":"","stock_symbol":"","u_op_prod_version":"","u_restriciton":"No","discount":"","sys_id":"5b2c008c1b07ac50a62cea0ce54bcbe8","market_cap":"1","customer":"TRUE"}}')
select * from dbo.log1
Above select query prints, NULL value for Sys_id column as value for that column in not inserted initially. what I want in expected output is that instead of NULL it should populate(derive) only sys_id value from Complete_JSON_Response column to Sys_id column
Expected output:
Id Sys_Id
S1 7a2c008c1b07ac50a62cea0ce54bcbe8
S2 5b2c008c1b07ac50a62cea0ce54bcbe8
SQL Server 2016 supports JSON, so you may try to use JSON_VALUE():
SELECT JSON_VALUE(Complete_JSON_Response, '$.result.sys_id') AS sys_id
FROM (VALUES
(N'{
"result":{
"banner_image_light":"",
"country":"USA",
"parent":"",
"notes":"",
"stock_symbol":"",
"u_op_dev_version":"",
"u_restriciton":"No",
"discount":"",
"sys_id":"7a2c008c1b07ac50a62cea0ce54bcbe8",
"market_cap":"0",
"customer":"false"
}
}')
) a (Complete_JSON_Response)
As an additional option, you may create a simplified UDF with a recursive search:
CREATE FUNCTION dbo.ParseJson (
#json nvarchar(max),
#key nvarchar(max)
)
RETURNS #ResultTable TABLE (
[value] nvarchar(max)
)
AS
BEGIN
;WITH rCTE AS (
SELECT
CONVERT(nvarchar(max), N'$') COLLATE DATABASE_DEFAULT AS [path],
CONVERT(nvarchar(max), JSON_QUERY(#json, '$')) COLLATE DATABASE_DEFAULT AS [value]
UNION ALL
SELECT
CONVERT(nvarchar(max), c.[key]) COLLATE DATABASE_DEFAULT,
CONVERT(nvarchar(max), c.[value]) COLLATE DATABASE_DEFAULT
FROM rCTE r
CROSS APPLY OPENJSON(r.[value]) c
WHERE ISJSON(r.[value]) = 1
)
INSERT INTO #ResultTable ([value])
SELECT [value]
FROM rCTE
WHERE (ISJSON([value]) = 0) AND (path = #key)
RETURN
END
Statement:
DECLARE #json nvarchar(max) = N'
{
"result":{
"banner_image_light":"",
"country":"USA",
"parent":"",
"notes":"",
"stock_symbol":"",
"u_op_dev_version":"",
"u_restriciton":"No",
"discount":"",
"sys_id":"7a2c008c1b07ac50a62cea0ce54bcbe8",
"market_cap":"0",
"customer":"false"
},
"result2":{
"sys_id":"xxxx008c1b07ac50a62cea0ce54bcbe8"
}
}
'
SELECT j.[value] AS sys_id
FROM (VALUES (#json)) a (Complete_JSON_Response)
OUTER APPLY dbo.ParseJson(a.Complete_JSON_Response, 'sys_id') j
Result:
sys_id
--------------------------------
xxxx008c1b07ac50a62cea0ce54bcbe8
7a2c008c1b07ac50a62cea0ce54bcbe8
You can use a function. It may lead some performance issues but works fine.
select
'{"result":{"banner_image_light":"","country":"USA","parent":"","notes":"","stock_symbol":"","u_op_dev_version":"","u_restriciton":"No","discount":"","sys_id":"7a2c008c1b07ac50a62cea0ce54bcbe8","market_cap":"0","customer":"false"}}' json
into tmp_json
CREATE FUNCTION dbo.trialFnc(
#json nvarchar(max),
#key nvarchar(255)
)
RETURNS nvarchar(255)
AS
BEGIN
declare #txt1 nvarchar(max) = right(#json, len(#json) - (charindex(#key, #json)) + 1 - len(#key) - 3)
declare #txt2 nvarchar(max) = left(#txt1, charindex('"', #txt1) - 1)
RETURN #txt2
END;
select
dbo.trialFnc(json, 'country') country
, dbo.trialFnc(json, 'sys_id') sys_id
from tmp_json
It will return you this:
country
sys_id
USA
7a2c008c1b07ac50a62cea0ce54bcbe8

Decode Base64 using a column as a variable

Objective:
I would like to Decode a Base64 column from a table using the OPENJSON function and extract the 'a' key from that decoded string as such:
DECLARE #JSON nvarchar(MAX) = 'column name' -- Here i am puting 'column name' because im not sure how to insert a column into a declared variable.
SELECT [Value]
FROM OPENJSON(#JSON)
WHERE [key] = 'a';
Situation:
I have a column ('ProductsBase64') that is Base64 encoded that I would like to decode.
Example of a unique string:
I would like to decode an entire column however
DECLARE #JSON nvarchar(MAX) = N'{"a":1,"b":2,"c":3,"d":4}';
SELECT *
FROM OPENJSON(#JSON)
where type = 2
Results:
key value type
a 1 2
b 2 2
c 3 2
d 4 2
Assuming the original text encoding is compatible with varchar you can use the below (uses XML xs:base64Binary method to do the base 64 decoding, that is then cast to VARCHAR and finally passed as input to OPENJSON)
CREATE TABLE #t
(
ProductsBase64 VARCHAR(max)
)
INSERT INTO #t
VALUES ('eyJhIjoxLCJiIjoyLCJjIjozLCJkIjo0fQ=='); /*{"a":1,"b":2,"c":3,"d":4} */
SELECT ca.*
FROM #t
CROSS APPLY (SELECT *
FROM OPENJSON(
CONVERT (
VARCHAR(MAX),
CAST('' AS XML).value('xs:base64Binary(sql:column("ProductsBase64"))', 'VARBINARY(MAX)')
)
)
WHERE type = 2) ca

Replace columns separated by string with id from another table - SQL Server

I have following 2 tables in SQL Server
Category table:
Category
--------------------------
Delivery;Gauges;Book;Table
Category id:
id name
-----------------
13183 Delivery
88781 Gauges
88782 Book
12512 Table
Intended result is to have category table replaced with category id, as:
Category
-----------------------
13183;88781;88782;12512
I approached this by first separating category columns into separate columns using :
ltrim(rtrim(xDim.value('/x[1]','varchar(max)')))
ltrim(rtrim(xDim.value('/x[2]','varchar(max)')))
and so on. Then used left join and replace on each new column. Isn't there an easier way to do this? I searched on the net and stackoverflow but can't seem to find anything similar.
You can try to make a function to split your string value by a character.
CREATE FUNCTION Split_fun
( #Words nvarchar(MAX)
, #splitStr varchar(50)
)
RETURNS #Result_Table TABLE
(
[word] nvarchar(max) NULL
)
BEGIN
Declare #TempStr nvarchar(MAX)
WHILE (CHARINDEX(#splitStr,#Words)>0)
BEGIN
Set #TempStr=SUBSTRING(#Words,1,CHARINDEX(#splitStr,#Words)-1)
Insert into #Result_Table (word) Values (#TempStr)
Set #Words = REPLACE(#Words,#TempStr+#splitStr,'')
END/*End While*/
IF(LEN(RTRIM(LTRIM(#Words)))>0 And CHARINDEX(#splitStr,RTRIM(LTRIM(#Words)))=0)
Begin
Set #TempStr=#Words
Insert into #Result_Table (word) Values (#TempStr)
End
RETURN
END
you can use this function to make a result set by ';'.
do self-join with Category id table.
final you can use FOR XML connect all string by ; to get your expectation result.
;with cte as (
SELECT id
FROM T CROSS APPLY Split_fun(Category,';') v
JOIN T1 on v.word = t1.Category
)
select STUFF((
select distinct ';'+ cast(id as varchar(10))
FROM cte
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 1, '')
sqlfiddle

Convert select query results into Json

I want to execute a select query on a table and I need this result in a JSON format or in an excel sheet. I want to do this using query only and I'm using SQL Server 2014.
Here is the table schema
CREATE TABLE TestTable
(
Id int primary key identity(1,1),
Name nvarchar(200),
About nvarchar(2000),
Age int,
AddressId int
)
I need to get values from Id, Name, About and Age into a JSON List
JSON AUTO would make quick work of this in but JSON support is available only in SQL Server 2016 and later, including Azure SQL Database. For a T-SQL solution in SQL 2014 and earlier, you'll need to build the JSON string yourself.
Below is an example that uses a FOR XML subquery to concatenate the result into JSON format and adds the outermost [ and ] for the array. Note that this does not handle characters that must be escaped in JSON (\ and ") so you'll need to use REPLACE to escape those if contained in your data.
SELECT '[' + STUFF((
SELECT
',{'
+ '"Id":'+CAST(Id AS varchar(10)) + ','
+ COALESCE('"Name":"' + Name + '",','')
+ COALESCE('"About":"' + About + '",','')
+ COALESCE('"Age":'+CAST(Age AS varchar(10)) + ',','')
+ COALESCE('"AddressId":'+CAST(AddressId AS varchar(10)), '')
+ '}'
FROM TestTable
FOR XML PATH(''), TYPE).value('.', 'varchar(MAX)'),1,1,'')
+ ']';
For SQL server 2017
CREATE TABLE mytable(
ID int PRIMARY KEY,
Name varchar(50),
teamName varchar(255),
Create_Date DATETIME
);
INSERT INTO mytable VALUES (1,NULL,'TEST1','2017-01-02');
INSERT INTO mytable VALUES (2,NULL,'TEST2',NULL);
INSERT INTO mytable VALUES (3,'KK','TEST3','2017-01-02');
INSERT INTO mytable VALUES (4,NULL,NULL,NULL);
Try below way here i provide an example
SELECT
ID,
Name,
teamName,
Create_Date
FROM mytable
FOR JSON AUTO
http://www.sqlfiddle.com/#!18/81350/1
JSON_F52E2B61-18A1-11d1-B105-00805F49916B
[{"ID":1,"teamName":"TEST1","Create_Date":"2017-01-02T00:00:00"},{"ID":2,"teamName":"TEST2"},{"ID":3,"Name":"KK","teamName":"TEST3","Create_Date":"2017-01-02T00:00:00"},{"ID":4}]
For below vesion of sql 2017 server:
1st create a scaler function
create FUNCTION [dbo].[udf-Str-JSON] (#IncludeHead int,#ToLowerCase int,#XML xml)
Returns varchar(max)
AS
Begin
Declare #Head varchar(max) = '',#JSON varchar(max) = ''
; with cteEAV as (Select RowNr=Row_Number() over (Order By (Select NULL))
,Entity = xRow.value('#*[1]','varchar(100)')
,Attribute = xAtt.value('local-name(.)','varchar(100)')
,Value = xAtt.value('.','varchar(max)')
From #XML.nodes('/row') As R(xRow)
Cross Apply R.xRow.nodes('./#*') As A(xAtt) )
,cteSum as (Select Records=count(Distinct Entity)
,Head = IIF(#IncludeHead=0,IIF(count(Distinct Entity)<=1,'[getResults]','[[getResults]]'),Concat('{"status":{"successful":"true","timestamp":"',Format(GetUTCDate(),'yyyy-MM-dd hh:mm:ss '),'GMT','","rows":"',count(Distinct Entity),'"},"results":[[getResults]]}') )
From cteEAV)
,cteBld as (Select *
,NewRow=IIF(Lag(Entity,1) over (Partition By Entity Order By (Select NULL))=Entity,'',',{')
,EndRow=IIF(Lead(Entity,1) over (Partition By Entity Order By (Select NULL))=Entity,',','}')
,JSON=Concat('"',IIF(#ToLowerCase=1,Lower(Attribute),Attribute),'":','"',Value,'"')
From cteEAV )
Select #JSON = #JSON+NewRow+JSON+EndRow,#Head = Head From cteBld, cteSum
Return Replace(#Head,'[getResults]',Stuff(#JSON,1,1,''))
End
-- Parameter 1: #IncludeHead 1/0
-- Parameter 2: #ToLowerCase 1/0 (converts field name to lowercase
-- Parameter 3: (Select * From ... for XML RAW)
Then use this function json conversion below query is an example
Declare #Table table (ID int,Active bit,First_Name varchar(50),Last_Name varchar(50),EMail varchar(50))
Insert into #Table values
(1,1,'John','Smith','john.smith#email.com'),
(2,0,'Jane','Doe' ,'jane.doe#email.com')
Select A.ID
,A.Last_Name
,A.First_Name
,B.JSON
From #Table A
Cross Apply (Select JSON=[dbo].[udf-Str-JSON](0,1,(Select A.* For XML Raw)) ) B
From SQL-Server 2016+, you can use JSON AUTO to return Json:
Select *
From Customers
FOR JSON AUTO;
If you expect to return just a single row, then you can add Without_Array_Wrapper after FOR JSON AUTO with a comma after the AUTO keyword, and you will get an object instead of an array:
Select *
From Customers
FOR JSON AUTO, Without_Array_Wrapper;
Note: If you add the Without_Array_Wrapper and return multiple rows, you will get a newly populated object type but won't get any error - which requires carefulness as you won't know if anything is wrong and might hit you later when exercising the data.
From SQL-Server 2016+, you can use JSON AUTO to return Json:
I know two way to return query in JSON format.
If your query is from one table or only return a single table (as usual) you can use JSON PATH or JSON AUTO
CREATE TABLE TestTable
(
Id int primary key identity(1,1),
Name nvarchar(200),
About nvarchar(2000),
Age int,
AddressId int
)
INSERT INTO dbo.TestTable(Name,About,Age,AddressId)
VALUES (N'John' , NULL ,21, 16),(N'Mehdi' , 'Developer' ,32, 15)
query is
select * from [TestTable] for JSON PATH
output :
[{
"Id": 1,
"Name": "Mehdi",
"About": "Developer",
"Age": 32,
"AddressId": 15
}, {
"Id": 3,
"Name": "John",
"Age": 21,
"AddressId": 16
}
]
Notice that NULL values do not show on result. For showing the null values, [INCLUDE_NULL_VALUES] must be added at the end of the query.
SELECT * FROM [TestTable] WHERE id=2 FOR JSON PATH , INCLUDE_NULL_VALUES
output:
[{
"Id": 3,
"Name": "John",
"About": null,
"Age": 21,
"AddressId": 16
}
]
For the query that only return an object, WITHOUT_ARRAY_WRAPPER must be added at the end of query the brackets are remove from the result.
SELECT * FROM [TestTable] WHERE id=2 FOR JSON PATH , INCLUDE_NULL_VALUES , WITHOUT_ARRAY_WRAPPER
output :
{
"Id": 3,
"Name": "John",
"About": null,
"Age": 21,
"AddressId": 16
}
For adding sub query for example the user's contracts or user's family names.
there two way :
1-
SELECT
* ,
ContractList = (SELECT* FROM contract where UserId = 2 FOR JSON PATH)
FROM TestTable FOR JSON PATH
2-
SELECT
* ,
(JSON_QUERY((SELECT* FROM contract WHERE UserId = 2 FOR JSON PATH))) AS ContractList
FROM TestTable FOR JSON PATH
output:
[{
"Id": 3,
"Name": "John",
"About": null,
"Age": 21,
"AddressId": 16 ,
"ContractList" : [{...}, {...}]
}
]
Thank you so much, your answer came to my rescue after a week-long search.
Had to make a few changes to suit my scenario and add quotes around the code
SELECT '[' + STUFF((
SELECT
',['
+ '"'+ CAST(s.Code AS varchar(10)) + '"' + ','
+ COALESCE(''+CAST(COUNT(s.ControlNumber) AS varchar(10)), '')
+ ']'
FROM school s
INNER JOIN [dbo].[Class] cc ON cc.SchoolID = s.SchoolID
INNER JOIN [dbo].[Subject] s ON s.subjectID = cc.subjectID
INNER JOIN [dbo].Marks ms ON s.MarkID = c.CaseStatusID
WHERE ms.Created BETWEEN '2021-04-01 00:00:00' AND '2021-04-07 23:59:59'
AND s.subjectID IN (1, 2, 3, 4, 5, 6, 7, 8, 9)
Group By s.Code
FOR XML PATH(''), TYPE).value('.', 'varchar(MAX)'),1,1,'')
+ ']';
Output
[["za-ec",14],["za-fs",5],["za-gt",3],["za-mp",14],["za-nc",2],["za-nl",8],["za-np",5],["za-nw",6],["za-wc",15]]