Making json from sql query - sql-server

I have the following scenario:
class question {
idQuestion: string;
question: string;
type: string;
}
class options {
idOption: string;
option: string;
}
My SQL returns:
idquestion question type idoption option
i.e:
question1 foo? textbox null null
question2 bar? select option1 aaa
question2 bar? select option2 bbb
question3 foobar? radio option1 aaa
question3 foobar? radio option2 bbb
question3 foobar? radio option3 ccc
I want to map the SQL response to the following interface:
questionOptions{
question: Question;
options: Option[];
}
How can I make it possible? So in the end, I could have a list of question, each one containing its options.
P.S: Would it be a better option to make the association between question and options from the sql database?
EDIT:
From the sample data I want to obtain the following json:
[
{
idQuestion: "question1",
question: "foo?",
options: []
},
{
idQuestion: "question2",
question: "bar?"
options: [
{
idOption: "option1",
option: "aaa"
},
{
idOption: "option2",
option: "bbb"
},
]
}
]

I have a helper function which will transform virtually any row/data set into a JSON String/Array.
Assuming 2012+
Declare #YourData table (idQuestion varchar(50),question varchar(50), type varchar(50),idOption varchar(50),[option] varchar(50))
Insert Into #YourData values
('question1','foo?','textbox', null, null),
('question2','bar?','select','option1','aaa'),
('question2','bar?','select','option2','bbb'),
('question3','foobar?','radio','option1','aaa'),
('question3','foobar?','radio','option2','bbb'),
('question3','foobar?','radio','option3','ccc')
Declare #JSON varchar(max) = ''
Select #JSON=#JSON+','+String
From (
Select String=Replace(B.JSON,'}',',"options":'+IsNull(C.JSON,'[]')+'}')
From (Select Distinct idquestion,question From #YourData) A
Cross Apply (Select JSON=[dbo].[udf-Str-JSON](0,0,(Select A.idQuestion,A.question for XML RAW))) B
Cross Apply (Select JSON=[dbo].[udf-Str-JSON](0,0,(Select idOption,[option] from #YourData Where idquestion=A.idquestion for XML RAW))) C
) A
Select '['+Stuff(#JSON,1,1,'')+']'
Returns
The UDF
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),'"},"retults":[[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)

Related

Parsing a JSON based on a condition in SQL Server

This is the JSON definition that is going to be provided (just a short example) and the code that I have implemented to get the expected result:
declare #json nvarchar(max)
set #json = '{
"testJson":{
"testID":"Test1",
"Value":[
{
"Value1":"",
"Value2":"",
"Value3":"",
"Type": "1A"
},
{
"Value1":"123",
"Value2":"456",
"Value3":"Automatic",
"Type": "2A"
},
{
"Value1":"789",
"Value2":"159",
"Value3":"Manual",
"Value4":"Success" ,
"Type": "3A"
}
]
}
}'
select
'ValueFields' as groupDef,
-- b.[key],
-- c.[key],
STRING_AGG( c.value , ' | ') as val
from
openjson(#json, '$.testJson.Value') as b
cross apply
openjson(b.value) as c
where
b.[key] not in (select b.[key]
from openjson(#json, '$.testJson.Value') as b
where b.value like ('%1A%'))
As you can see each element in the array can have different quantity of attributes (value1,.., value4..), and I only need to consider those elements where the type attribute is not equal to "1A". The query gives me the result requested, however, I am wondering how can I improve the performance of the code given that I'm using the like operator in the sub select, and obviously the original JSON file could a considerable number of elements in the array.
…
select b.Value --,c.value
from
openjson(#json, '$.testJson.Value')
with
(
Value nvarchar(max) '$' as json,
Type varchar(100) '$.Type'
) as b
--cross apply openjson(b.Value) as c
where b.Type <> '1A'
SELECT
'ValueFields' as groupDef,
J.value as val
FROM
OPENJSON(#json,'$.testJson.Value') J
WHERE
JSON_VALUE([value],'$.Type') <> '1A'

How to execute two function according to a table result with a condition by using mssql?

ParseValuesJson and insertToTable are user defined function.
The following is the result of the execution ( ParseValuesJson ) :
declare
#json nvarchar(max)=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"]
}
}'
declare
#tempTable table (
topKey nvarchar(4000)
, [key] nvarchar(4000)
, IsType bit
, IsList bit
, [value] nvarchar(4000))
--execute
insert #tempTable
select * from GetValuesJson(#json,default)
topKey Key isType isList Value
--
Book Type 0 0 Any Type
Book Author 1 0 {"Name":"Annie", "Sex":"Female"}
Book IssueDate 0 0 02-15-2019
Book Chapter 1 1 [{"Section":"1.1", "Title":"Hello world."}, {"Section":"1.2", "Title":"Be happy."}]
Book Sponsor 1 1 ["A","B","C"]
As title, If ignore what the function doing, how can I achieve the following purpose?
If IsType=1 , I want to call function ParseValuesJson;
If IsType=0 , I want to call function insertToTable.
But I found that sql case can not use like that.
This sql query may execute recursively and call different functions accordingly at the same level.
It means that I can't parse all string (ParseValuesJson) first and then insert the result (insertToTable) to the table.
Is there any other way to achieve?
select
case IsType when 1 then
ParseValuesJson('{"' + [key] + '":' + [value] + '}',IsList)
else
insertToTable(topKey,[key])
end
from ParseValuesJson(#json,default)
Well, the easiest thing to do is to split it into two separate SELECTs.
select ParseValuesJson('{"' + [key] + '":' + [value] + '}',IsList)
from ParseValuesJson(#json,default)
where IsType = 1
select insertToTable(topKey,[key])
from ParseValuesJson(#json,default)
where IsType = 0
But I guess this approach won't help you since inside user defined functions you cannot use INSERT, UPDATE, DELETE statements -> i.e modify table data
So I guess that to parse the JSON you'd need to user recursive CTE to parse all values first and then to insert them into temp table at once.
Something like this:
;WITH ParsedJSON(topKey, [key], IsType, IsList, [value])
AS
(
SELECT topKey, [key], IsType, IsList, [value]
FROM ParseValuesJson('{"' + [key] + '":' + [value] + '}',IsList)
UNION ALL
SELECT topKey, [key], IsType, IsList, [value]
FROM ParsedJSON
WHERE IsType = 1
)
insert #tempTable
select * from ParsedJSON

How to add attribute value to root element when using for XML path?

I have this SQL and it works:
set nocount on
DECLARE
#OrderID VARCHAR(200) = '13095314100';
declare #eOrder Table
(
colOrderId varchar(20),
colDeliveryCountryCode varchar(20)
)
insert into #eOrder values ('13095314100', 'SE')
SELECT
'name' AS 'Val/#n', Sender.name AS Val
FROM #eOrder E
inner join
(
select '12345' SenderID, 'KappAhl Sverige AB' "name", 'Idrottsvägen 14' "address1", null address2, '431 24' ZipCode, 'MÖLNDAL' City, 'SE' Country, '010-138 87 11' phone
union
select '12345', 'KappAhl Shop Online', 'c/o KappAhl OY', 'Unikkotie 3 C', '01300' , 'VANTAA', 'PL', '0753 267 881'
) Sender
on E.colDeliveryCountryCode = Sender.Country
WHERE colOrderId = #OrderID
FOR XML PATH('Sender');
I get this output as expected:
<Sender><Val n="name">KappAhl Sverige AB</Val></Sender>
But how do I add SenderID as an attribute to my Sender tag?
Wanted XML output:
<Sender SenderID="12345" ><Val n="name">KappAhl Sverige AB</Val></Sender>
Add Sender.SenderID as '#SenderID' to your select:
SELECT Sender.SenderID as '#SenderID',
'name' AS 'Val/#n', Sender.name AS Val
Result:

How to initialize a variable in stored procedure with select statement

I wonder how I can directly give this variable (#p_txtValue) in the stored procedure (EXEC dbo.bisting_sp_common_Event_SetParameter) a value with a select statement without using SET statement above.
This is my code where I initialize #l_txtTemp with a SET statement then the give #p_txtValue the value from it.
SELECT
ssh.No_
,ssh.[Bill-to Customer No_]
,RANK () over (ORDER BY ssh.No_) Rank_Id
INTO #shipments --DROP TABLE #shipments
FROM dbo.[Production$Sales Shipment Header] SSH
WHERE SSH.[Sell-to Customer No_] IN ('3004', '3003', '3002')
AND ssh.[Posting Date]>'2015-10-01'
AND NOT EXISTS (SELECT *
FROM dbo.[bisting System Monitor] tSub1
WHERE tSub1.[Row Type] = 'Log'
AND tSub1.[Type] ='Checkpoint Init'
AND tSub1.[Master Event Type] = #l_txtCurrMasterEventType
AND tSub1.[Document No_] = ssh.No_)
SELECT #l_intRankIDCurr = 1, #l_intRankIDMax = (SELECT MAX(Rank_Id) FROM #shipments)
WHILE (#l_intRankIDCurr <= #l_intRankIDMax)
BEGIN
SELECT #l_txtShipmentNo = No_
FROM #shipments WHERE Rank_Id = #l_intRankIDCurr
SET #l_txtTemp = CASE (SELECT t1.[Bill-to Customer No_]
FROM #shipments t1
WHERE t1.Rank_Id = #l_intRankIDCurr)
WHEN '3002' THEN 'Uppsala.csv'
WHEN '3003' THEN 'Gränby.csv'
WHEN '3004' THEN 'Örebro.csv'
END
IF #p_intLogLevel >= 4 PRINT CONVERT(VARCHAR(19),CURRENT_TIMESTAMP,121)+': Create set document no parameter'
EXEC dbo.bisting_sp_common_Event_SetParameter #p_bigintEventID=#l_bigintChildEventID
,#p_txtAttributeLvl1='Action SP Filename'
,#p_txtValue= #l_txtTemp
SELECT #l_intRankIDCurr += 1
END
Is it possible to make something like this? this example below didn't work for me neither the other ones i tried:
IF #p_intLogLevel >= 4
PRINT CONVERT(VARCHAR(19),CURRENT_TIMESTAMP,121)+': Create set document no parameter'
EXEC dbo.bisting_sp_common_Event_SetParameter
#p_bigintEventID = #l_bigintChildEventID
,#p_txtAttributeLvl1 = 'Action SP Filename'
,#p_txtValue = (SELECT [Attribute]
FROM [TABLE] t1
WHERE t1.Rank_Id = #l_intRankIDCurr)
Only way is:
declare #p_txtValue nvarchar(max) =
(SELECT [Attribute] FROM [TABLE] t1 WHERE t1.Rank_Id = #l_intRankIDCurr)
and:
EXEC dbo.bisting_sp_common_Event_SetParameter ....#p_txtValue = #p_txtValue
because of:
[ [ #parameter = ] { value
| #variable [ OUTPUT ]
| [ DEFAULT ]
}
that means that you can pass directly string 'somevalue' or with a variable.

SQL Server : Select without some word

I have a procedure like this;
SET #Oldname = (SELECT name
FROM [Marketing].[dbo].[_Users]
WHERE ID = #MID);
Everything is okay but some members got xxx, xxx_A1,xxx_A2,xxx_A3, while selecting these members I want to select without _A1 , _A2 , A_30
How can I select it?
Try reversing string, find first index of _ and apply substring:
DECLARE #s NVARCHAR(MAX) = 'John_Smith_234'
DECLARE #t TABLE ( Name NVARCHAR(50) )
INSERT INTO #t
VALUES ( 'John_Smith_234' ),
( 'Michael_Jordan' ),
( 'Honore_De_Balzak_234' )
SELECT SUBSTRING(Name, 1, LEN(Name) - CHARINDEX('_', REVERSE(Name))) AS Name
FROM #t
Output:
Name
John_Smith
Michael
Honore_De_Balzak
try this
select left(name, len(name)-charindex('_', reverse(name))) name
from whatever
Assure that you add a '%' sign to your #MID Variable, looking like 'xxx%'
Than SELECT with LIKE instead of = :
SET #Oldname = (SELECT name From [Marketing].[dbo].[_Users] Where ID LIKE #MID);
I think you may also add the '%' sign in qour query (haven't tried):
SET #Oldname = (SELECT name From [Marketing].[dbo].[_Users] Where ID LIKE #MID+'%');

Resources