SQL Server result as Json Array - arrays

I need to perform a query that outputs a Json array.
SQL Server has the function FOR JSON PATH; but the output would be:
[{"key_1":"value_1","key_2":"value_2"},{"key_1":"value_3","key_2":"value_4"}]
But the output I need is:
{ "key_1": ["value_1","value_3"],
"key_2": ["value_2","value_4"]
}
Can this be done?

Add , WITHOUT_ARRAY_WRAPPER after FOR JSON PATH
https://learn.microsoft.com/en-us/sql/relational-databases/json/remove-square-brackets-from-json-without-array-wrapper-option?view=sql-server-ver15
UPDATE:
This isn't the prettiest solution, but it works. I tried to recreate some data based on the original format of your output. So the answer is based on that assumption.
-- Test data (based on what I could figure out from your question)
CREATE TABLE dbo.jsonvalues
(
key_1 NVARCHAR(MAX)
,key_2 NVARCHAR(MAX)
);
INSERT INTO dbo.jsonvalues
(
key_1
,key_2
)
VALUES
(
N'value_1'
,N'value_2'
)
,(
N'value_3'
,N'value_4'
);
To get the data in the right format you would first have to unpivot the data (or store it differently). Then you can create an array in the output by using a sub-query in the SELECT.
SELECT
*
INTO
#newformat
FROM
dbo.jsonvalues AS J
UNPIVOT
(
vals FOR keys IN (key_1, key_2)
) AS u;
SELECT
N.keys
,(SELECT N2.vals FROM #newformat AS N2 WHERE N2.keys = N.keys FOR JSON AUTO) AS vals
FROM
(SELECT DISTINCT
keys
FROM
#newformat
) AS N
FOR JSON AUTO, WITHOUT_ARRAY_WRAPPER;
OUTPUT:
{
"keys":"key_1","vals":[{"vals":"value_1"},{"vals":"value_3"}]
},
{
"keys":"key_2","vals":[{"vals":"value_2"},{"vals":"value_4"}]
}
This won't give you exactly what you want but is the closest I could get without starting to do some manual tweaking. I included the tweaks I did below to get to your requested output format. Just be warned that it is brittle and might need additional tweaks for your data.
SELECT
REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE((
SELECT
N.keys
,(SELECT N2.vals FROM #newformat AS N2 WHERE N2.keys = N.keys FOR JSON AUTO) AS vals
FROM
(SELECT DISTINCT
keys
FROM
#newformat
) AS N
FOR JSON AUTO, WITHOUT_ARRAY_WRAPPER
),'"keys":',''),'"vals"',''),',:',':'),'},{:',','),'{:',''),'}]}',']');
OUTPUT:
{
"key_1":["value_1","value_3"]
,"key_2":["value_2","value_4"]
}

Related

distinct result by entire table when using cross apply on json and order by with case

I need to order my result by value from dictionary that stored as JSON in my table that equals a parameter.
In order to get it I'm using case on my order by to check if the value from the dictionary match the parameter.
After ordering the table I need to distinct the result however I'm getting an error and I couldn't figure it out.
here is my query:
declare #FilteredItemIDs -> temp table that filtered my items
declare #CurrentGroupID as int
select distinct item.*
from Items as items
outer apply openjson(json_query(Data, '$.itemOrderPerGroup'), '$') as X
where items.ItemID in (select ItemID from #FilteredItemIDs )
order by case
when #CurrentGroupID!= 0 and (JSON_VALUE(X.[Value], '$.Key') = #CurrentGroupID) then 1
else 2 end,
CONVERT(int, JSON_VALUE(X.[Value], '$.Value'))
When you DISTINCT over a resultset, you are effectively using GROUP BY over all columns. So the X.Value doesn't exist anymore when you get to the ORDER BY.
Using DISTINCT is usually a code smell, it indicates that joins have not been thought through. In this case, you should probably place the OPENJSON inside a subquery, with SELECT TOP (1), although it's hard to say without sample data and expected results.
select
item.*
from Items as items
outer apply (
select top (1)
X.[Key],
X.Value
from openjson(Data, '$.itemOrderPerGroup')
with (
[Key] int,
Value int
) as X
) X
where items.ItemID in (select ItemID from #FilteredItemIDs )
order by case
when #CurrentGroupID != 0 and X.Key = #CurrentGroupID then 1
else 2 end,
X.[Value];
Note the correct use of OPENJSON with a JSON path and property names.
If what you actually want is to filter the OPENJSON results, rather than ordering by them, then you can do that in an EXISTS
select
item.*
from Items as items
outer apply X
where items.ItemID in (select ItemID from #FilteredItemIDs )
and exists (select 1
from openjson(Data, '$.itemOrderPerGroup')
with (
[Key] int,
Value int
) as X
where #CurrentGroupID = 0 or X.Key = #CurrentGroupID
);

Is it possible to use STRING_SPLIT in an ORDER BY clause?

I am trying to order values that are going to be inserted into another table based on their order value from a secondary table. We are migrating old data to a new table with a slightly different structure.
I thought I would be able to accomplish this by using the string_split function but I'm not very skilled with SQL and so I am running into some issues.
Here is what I have:
UPDATE lse
SET Options = a.Options
FROM
dbo.LessonStepElement as lse
CROSS JOIN
(
SELECT
tbl1.*
tbl2.Options,
tbl2.QuestionId
FROM
dbo.TrainingQuestionAnswer as tbl1
JOIN (
SELECT
string_agg((CASE
WHEN tqa.CorrectAnswer = 1 THEN REPLACE(tqa.AnswerText, tqa.AnswerText, '*' + tqa.AnswerText)
ELSE tqa.AnswerText
END),
char(10)) as Options,
tq.Id as QuestionId
FROM
dbo.TrainingQuestionAnswer as tqa
INNER JOIN
dbo.TrainingQuestion as tq
on tq.Id = tqa.TrainingQuestionId
INNER JOIN
dbo.Training as t
on t.Id = tq.TrainingId
WHERE
t.IsDeleted = 0
and tq.IsDeleted = 0
and tqa.IsDeleted = 0
GROUP BY
tq.Id,
tqa.AnswerDisplayOrder
ORDER BY
(SELECT [Value] FROM STRING_SPLIT((SELECT AnswerDisplayOrder FROM dbo.TrainingQuestion WHERE Id = tmq.Id), ','))
) as tbl2
on tbl1.TrainingQuestionId = tbl2.QuestionId
) a
WHERE
a.TrainingQuestionId = lse.TrainingQuestionId
The AnswerDisplayOrder that I am using is just a nvarchar comma separated list of the ids for the answers to the question.
Here is an example:
I have 3 rows in the TrainingQuestionAnswer table that look like the following.
ID TrainingQuestionId AnswerText
-------------------------------------------
215 100 No
218 100 Yes
220 100 I'm not sure
I have 1 row in the TrainingQuestion table that looks like the following.
ID AnswerDisplayOrder
--------------------------
100 "218,215,220"
Now what I am trying to do is when I update the row in the new table with all of the answers combined, the answers will need to be in the correct order which is dependent on the AnswerDisplayOrder in the TrainingQuestion table. So in essence, the new table would have a row that would look similar to the following.
ID Options
--------------
193 "Yes No I'm not sure"
I'm aware that the way I'm trying to do it might not be even possible at all. I am still learning and would just love some advice or guidance on how to make this work. I also know that string_split does not guarantee order. I'm open to other suggestions that do guarantee the order as well.
I simplified the issue in the question to the following approach, that is a possible solution to your problem. If you want to get the results from the question, you need a splitter, that returns the substrings and the positions of the substrings. STRING_SPLIT() is available from SQL Server 2016, but is not an option here, because (as is mentioned in the documentation) the output rows might be in any order and the order is not guaranteed to match the order of the substrings in the input string.
But you may try to use a JSON based approach, with a small string manipulation, that transforms answers IDs into a valid JSON array (218,215,220 into [218,215,220]). After that you can easily parse this JSON array with OPENJSON() and default schema. The result is a table, with columns key, value and type and the key column (again from the documentation) holds the index of the element in the specified array.
Tables:
CREATE TABLE TrainingQuestionId (
ID int,
TrainingQuestionId int,
AnswerText varchar(1000)
)
INSERT INTO TrainingQuestionId
(ID, TrainingQuestionId, AnswerText)
VALUES
(215, 100, 'No'),
(218, 100, 'Yes'),
(220, 100, 'I''m not sure')
CREATE TABLE TrainingQuestion (
ID int,
AnswerDisplayOrder varchar(1000)
)
INSERT INTO TrainingQuestion
(ID, AnswerDisplayOrder)
VALUES
(100, '218,215,220')
Statement:
SELECT tq.ID, oa.Options
FROM TrainingQuestion tq
OUTER APPLY (
SELECT STRING_AGG(tqi.AnswerText, ' ') WITHIN GROUP (ORDER BY CONVERT(int, j.[key])) AS Options
FROM OPENJSON(CONCAT('[', tq.AnswerDisplayOrder, ']')) j
LEFT JOIN TrainingQuestionId tqi ON TRY_CONVERT(int, j.[value]) = tqi.ID
) oa
Result:
ID Options
100 Yes No I'm not sure
Notes: You need SQL Server 2017+ to use STRING_AGG(). For SQL Server 2016 you need a FOR XML to aggregate strings.
declare #TrainingQuestionAnswer table
(
ID int,
TrainingQuestionId int,
AnswerText varchar(20)
);
insert into #TrainingQuestionAnswer(ID, TrainingQuestionId, AnswerText)
values(215, 100, 'No'), (218, 100, 'Yes'), (220, 100, 'I''m not sure');
declare #TrainingQuestiontest table
(
testid int identity,
QuestionId int,
AnswerDisplayOrder varchar(200)
);
insert into #TrainingQuestiontest(QuestionId, AnswerDisplayOrder)
values(100, '218,215,220'), (100, '220,218,215'), (100, '215,218');
select *,
(
select string_agg(pci.AnswerText, '==') WITHIN GROUP ( ORDER BY pci.pos)
from
(
select a.AnswerText,
pos = charindex(concat(',', a.ID, ','), concat(',', q.AnswerDisplayOrder,','))
from #TrainingQuestionAnswer as a
where a.TrainingQuestionId = q.QuestionId
and charindex(concat(',', a.ID, ','), concat(',', q.AnswerDisplayOrder,',')) >= 1
) as pci
) as TestAnswerText
from #TrainingQuestiontest as q;

Extraction all values between special characters SQL

I have the following values in the SQL Server table:
But I need to build query from which output look like this:
I know that I should probably use combination of substring and charindex but I have no idea how to do it.
Could you please help me how the query should like?
Thank you!
Try the following, it may work.
SELECT
offerId,
cTypes
FROM yourTable AS mt
CROSS APPLY
EXPLODE(mt.contractTypes) AS dp(cTypes);
You can use string_split function :
select t.offerid, trim(translate(tt.value, '[]"', ' ')) as contractTypes
from table t cross apply
string_split(t.contractTypes, ',') tt(value);
The data in each row in the contractTypes column is a valid JSON array, so you may use OPENJSON() with explicit schema (result is a table with columns defined in the WITH clause) to parse this array and get the expected results:
Table:
CREATE TABLE Data (
offerId int,
contractTypes varchar(1000)
)
INSERT INTO Data
(offerId, contractTypes)
VALUES
(1, '[ "Hlavni pracovni pomer" ]'),
(2, '[ "ÖCVS", "Staz", "Prahovne" ]')
Table:
SELECT d.offerId, j.contractTypes
FROM Data d
OUTER APPLY OPENJSON(d.contractTypes) WITH (contractTypes varchar(100) '$') j
Result:
offerId contractTypes
1 Hlavni pracovni pomer
2 ÖCVS
2 Staz
2 Prahovne
As an additional option, if you want to return the position of the contract type in the contractTypes array, you may use OPENJSON() with default schema (result is a table with columns key, value and type and the value in the key column is the 0-based index of the element in the array):
SELECT
d.offerId,
CONVERT(int, j.[key]) + 1 AS contractId,
j.[value] AS contractType
FROM Data d
OUTER APPLY OPENJSON(d.contractTypes) j
ORDER BY CONVERT(int, j.[key])
Result:
offerId contractId contractType
1 1 Hlavni pracovni pomer
2 1 ÖCVS
2 2 Staz
2 3 Prahovne

T-SQL split string containing alpha and numeric characters by variable delimiter

I am trying to split a string that has 3 set Alpha Characters that can appear in any order followed by a numeric value. The issue I am having is that the order of the alpha characters isn't fixed. And neither is the number of numeric values after the alpha character it may contain any of the following examples:
X1Y45Z1
Y25Z1
X1Y9Z1
X2Z6
With a a lot of help from our local IT ( I am still learning SQL) I have managed to separate out X Y and Z into separate columns with the numbers after them, but they don't always appear in order
Col1 may contain X or Y
Col2 may contain Y or Z
Col3 may contain Z or nothing
I am trying to get a result like the following:
If X is in Col1, Show number(s) after X, in new column "X", if Y is in col1, Show number(s) after Y in new column "Y", etc.
At present we are using 2 cte's to break up the string. and I am trying to simplify it so that I can search the string, have 3 columns after created 'X','Y','Z' and put the correct number(s) after each Alpha delimiter into it. I should note I Do Not have full admin access so I cannot create new tables or update/insert data or clean it.
Also apologies if this is slightly formatted incorrectly. It is my first post on StackOverflow
declare #tbl table
(
Col1 varchar(100), <-------This Column contains the values I want
)
insert into #tbl
select Col1,
from table1,
where xyz
;with cte as
(
select
Col1,
replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(**Col1**,'P', '</x><x>P'),'C', '</x><x>C'),'I', '</x><x>I'),'M', '</x><x>M'),'S', '</x><x>S'),'Q', '</x><x>Q'),'L', '</x><x>L'),'T', '</x><x>T'),'E', '</x><x>E'),'R', '</x><x>R'),'U', '</x><x>U'),'W', '</x><x>W')
**Col1NODES**
from
#tbl
)
, cte2 (Col1, Col1Nodes) as
(
select
Col1,
convert(xml,'<z><x>' + Col1nodes + '</x></z>') **Col1NODES**
from
cte
)
select
Col1,
isnull(Col1Nodes.value('/z[1]/x[2]','varchar(100)'),'-') F1,
isnull(Col1Nodes.value('/z[1]/x[3]','varchar(100)'),'-') F2,
isnull(Col1Nodes.value('/z[1]/x[4]','varchar(100)'),'-') F3
from
cte2
Current output is below:
If you have SQL Server 2016+ you may try to use the following solution, based on JSON. The important part is to transform the input data into a valid JSON object (X1Y45Z1 is transformed into {"X":1,"Y":45,"Z":1} for example). After that you need to parse this object with OPENJSON() function using the appropriate WITH clause to define the columns in the output.
Table:
CREATE TABLE Data (
TextData nvarchar(100)
)
INSERT INTO Data
(TextData)
VALUES
('X1Y45Z1'),
('Y25Z1'),
('X1Y9Z1'),
('X2Z6'),
('Z1X6')
Statement:
SELECT d.TextData, j.*
FROM Data d
CROSS APPLY OPENJSON(
CONCAT(
N'{',
STUFF(REPLACE(REPLACE(REPLACE(d.TextData, N'X', N',"X":'), N'Y', N',"Y":'), N'Z', N',"Z":'), 1, 1, N''),
N'}'
)
) WITH (
X int '$.X',
Y int '$.Y',
Z int '$.Z'
) j
Output:
---------------------
TextData X Y Z
---------------------
X1Y45Z1 1 45 1
Y25Z1 25 1
X1Y9Z1 1 9 1
X2Z6 2 6
Z1X6 6 1
For versions before SQL Server 2016, you may use an XML based approach. You need to transform text data into an appropriate XML (X1Y45Z1 is transformed into <row><name>X</name><value>1</value></row><row><name>Y</name><value>45</value></row><row><name>Z</name><value>1</value></row> for example):
SELECT
TextData,
XmlData.value('(/row[name = "X"]/value/text())[1]', 'nvarchar(4)') AS X,
XmlData.value('(/row[name = "Y"]/value/text())[1]', 'nvarchar(4)') AS Y,
XmlData.value('(/row[name = "Z"]/value/text())[1]', 'nvarchar(4)') AS Z
FROM (
SELECT
TextData,
CONVERT(
xml,
CONCAT(
STUFF(REPLACE(REPLACE(REPLACE(d.TextData, N'X', N'</value></row><row><name>X</name><value>'), N'Y', N'</value></row><row><name>Y</name><value>'), N'Z', N'</value></row><row><name>Z</name><value>'), 1, 14, N''),
N'</value></row>'
)
) AS XmlData
FROM Data d
) x

Get records which are not ended with specific word

I have field with values for instance:
323.12.444.1
55.1231
4543.432.431
6.1
456.3234.54353.1124.1
321.3.425
2.3.1
5345.43.1
432.5646.2
So for records ended by .1 has to be gathered. What should be the query?
This should be faster than LIKE
SELECT * FROM table WHERE RIGHT(fieldname,2)='.1'
The LIKE with a % at the beginning is something one should avoid if possible...
select * from table where fieldname like '%.1'
I would suggest to use this:
SELECT *
FROM YourTable
WHERE REVERSE(SUBSTRING(REVERSE(col1),1,CHARINDEX('.',REVERSE(col1))-1)) = '1'
You can find any string you need without changing parameters inside query:
;WITH YourTable AS (
SELECT *
FROM (VALUES
('323.12.444.1'),
('55.1231'),
('4543.432.431'),
('6.1'),
('456.3234.54353.1124.1'),
('321.3.425'),
('2.3.1'),
('5345.43.1'),
('432.5646.2')
) as t(col1)
)
SELECT *
FROM YourTable
WHERE REVERSE(SUBSTRING(REVERSE(col1),1,CHARINDEX('.',REVERSE(col1))-1)) = '431'
Output:
4543.432.431

Resources