Return json list of values in SQL Server 2016 using data from 2 columns - sql

I have a database with 2 columns
A B
-- --
X 1995
Y 2005
C 1962
D 2003
I'm trying to create a SQL statement that will take a string of comma delimited values and return a json list of values in B where any value in the string is in A
so if the comma delimited string was 'X,C' the json list would be [1995,1962]
I've been using json path to try this, but I can't get it exactly like I want it and I've been spinning my wheels for too long
This is what I've tried:
Select mt.B as json_list_b_values
From [dbo].[myTable] mt
Where mt.A in (Select value From String_Split('X,C', ',')) for json path
This is the ouput:
[ {"json_list_b_values":"1995"}, {"json_list_b_values":"1962"} ]

As you're on 2016 you can't use STRING_AGG, but you can use the old tried and tested FOR XML PATH and STUFF method:
DECLARE #list varchar(8000) = 'X,C';
WITH VTE AS(
SELECT *
FROM(VALUES('X',1995),
('Y',2005),
('C',1962),
('D',2003)) V(A,B))
SELECT '[' + STUFF((SELECT CONCAT(',',V.B)
FROM VTE V
CROSS APPLY STRING_SPLIT(#list,',') SS
WHERE SS.[value] = V.A
FOR XML PATH('')),1,1,'') + ']';

I am no SQL expert. So please bear with me.
Step 1 - Handle the CSV input
You are already doing this by using the IN clause. I would store these results in a table variable
Step 2 - Convert the query results to a simple JSON array
I can think of the function STRING_AGG(). This will concatenate the rows into a flat string.
E.g. Join the FirstName column into a comma delimited string
SELECT STRING_AGG ( ISNULL(FirstName,'N/A'), ',') AS csv
FROM Person.Person;
Will produce the following
John,N/A,Mike,Peter,N/A,N/A,Alice,Bob
Code snippet based on your example
I am using a table variable #result1 to hold the result from Step 1.
SELECT '[ "' + string_agg( json_list_b_values, '", "') + '" ]' FROM #result1
MSDN reference for STRING_AGG()
https://learn.microsoft.com/en-us/sql/t-sql/functions/string-agg-transact-sql?view=sql-server-2017
CORRECTION
The function STRING_AGG() is available in SQL 2017.

Related

How to fetch only a part of string

I have a column which has inconsistent data. The column named ID and it can have values such as
0897546321
ABC,0876455321
ABC,XYZ,0873647773
ABC,
99756
test only
The SQL query should fetch only Ids which are of 10 digit in length, should begin with a 08 , should be not null and should not contain all characters. And for those values, which have both digits and characters such as ABC,XYZ,0873647773, it should only fetch the 0873647773 . In these kind of values, nothing is fixed, in place of ABC, XYZ , it can be anything and can be of any length.
The column Id is of varchar type.
My try: I tried the following query
select id
from table
where id is not null
and id not like '%[^0-9]%'
and id like '[08]%[0-9]'
and len(id)=10
I am still not sure how should I deal with values like ABC,XYZ,0873647773
P.S - I have no control over the database. I can't change its values.
SQL Server generally has poor support regular expressions, but in this case a judicious use of PATINDEX is viable:
SELECT SUBSTRING(id, PATINDEX('%,08[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9],%', ',' + id + ','), 10) AS number
FROM yourTable
WHERE ',' + id + ',' LIKE '%,08[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9],%';
Demo
If you normalise your data, and split the delimited data into parts, you can achieve this some what more easily:
SELECT SS.value
FROM dbo.YourTable YT
CROSS APPLY STRING_SPLIT(YT.YourColumn,',') SS
WHERE LEN(SS.value) = 10
AND SS.value NOT LIKE '%[^0-9]%';
If you're on an older version of SQL Server, you'll have to use an alternative String Splitter method (such as a XML splitter or user defined inline table-value function); there are plenty of examples on these already on Stack Overflow.
db<>fiddle

String aggregation using JSON in SQL Server 2016

I would like to format a json string '[{"_":7},{"_":13},{"_":17}]' as '[7,13,17]'
Tried with REPLACE Method in TSQL. I have to use REPLACE method three times to get the desire result.
SELECT REPLACE(REPLACE(REPLACE('[{"_":7},{"_":13},{"_":17}]','},{"_":',', '),'{"_":',''),'}','')
is there a better way to do that?
I am using SQL Server 2016.
After some comments for this post, This my actual issue.
I have some customer data. Customer Table
CustomerId | Name
1 ABC
2 XYZ
3 EFG
each customer has some area of interest. Customer Area of Interest
CustomerAreaInterestId | FK_CustomerId | FK_AreaOfInterestId
1 1 2
2 1 3
3 1 5
4 2 1
5 2 2
6 3 3
7 3 4
Area of interest table
AreaOfInterestId | Description
1 Interest1
2 Interest2
3 Interest3
4 Interest4
5 Interest5
In the final result set, I have to include area of interest id's as an array of value
[
{
"CustomerName": "ABC",
"AreaofInterest": "[2,3,5]"
},
{
"CustomerName": "XYZ",
"AreaofInterest": "[1,2]"
},
{
"CustomerName": "EFG",
"AreaofInterest": "[3,4]"
}
]
The result consists with some other data’s as well. I have omitted for the code brevity.
Short Version
Cast the numeric field to text before trying to aggregate it
From the comments, it looks like the real question is how to use JSON to aggregate strings in SQL Server 2016, as shown in this answer.
SELECT
JSON_VALUE(
REPLACE(
(SELECT _ = someField FROM someTable FOR JSON PATH)
,'"},{"_":"',', '),'$[0]._'
)
or, rewritten for clarity :
SELECT
JSON_VALUE( REPLACE(
(SELECT _ = someField
FROM someTable
FOR JSON PATH)
,'"},{"_":"',', ')
,'$[0]._')
That query works only with string fields. One needs to understand what it does before it can be adopted to other types.
The inner query generates a JSON string from a field's values, eg '[{"_":"value1"},{"_":"value2"}]'.
REPLACE replaces the quotes and separators between objects, changing that array of objects to '[{"_":"value1,value2"}]'. That's a single object in an array, whose single attribute is a comma-separated string.
JSON_VALUE(...,,'$[0]._') extracts the _ attribute of that single array item.
That trick can't be used with numeric values because they don't have quotes. The solution is to cast them to text first:
SELECT
JSON_VALUE( REPLACE(
(SELECT _ = CAST(someNumber as nvarchar(20))
FROM someTable
FOR JSON PATH)
,'"},{"_":"',', ')
,'$[0]._')
Eg :
declare #t table (id int)
insert into #t
values
(7),
(13),
(17)
SELECT
JSON_VALUE( REPLACE(
(SELECT _ = cast(ID as nvarchar(20))
FROM #t
FOR JSON PATH)
,'"},{"_":"',', '),'$[0]._')
The only change from the original query is the cast clause.
This produces :
7, 13, 17
This conversion is localized so care must be taken with decimals and dates, to avoid producing unexpected results, eg 38,5, 40,1 instead of 38.5, 40.1.
PS: That's no different than the XML technique, except STUFF is used there to cut off the leading separator. That technique also needs casting numbers to text, eg :
SELECT STUFF(
( SELECT N', ' + cast(ID as nvarchar(20))
FROM #t FOR XML PATH(''),TYPE)
.value('text()[1]','nvarchar(max)'),
1,2,N'')
If you want to use only JSON functions (not string-based approach), the next example may help:
DECLARE #json nvarchar(max) = N'[{"_":7},{"_":13},{"_":17}]'
DECLARE #output nvarchar(max) = N'[]'
SELECT #output = JSON_MODIFY(#output, 'append $', j.item)
FROM OPENJSON(#json) WITH (item int '$."_"') j
SELECT #output AS [Result]
Result:
Result
[7,13,17]
Of course, the approach based on string aggregation is also a possible solution:
DECLARE #json nvarchar(max) = N'[{"_":7},{"_":13},{"_":17}]'
SELECT CONCAT(
N'[',
STUFF(
(
SELECT CONCAT(N',', j.item)
FROM OPENJSON(#json) WITH (item int '$."_"') j
FOR XML PATH('')
), 1, 1, N''
),
N']'
)
Yes you could do it with only 2 replace :
SELECT REPLACE(REPLACE('[{"_":7},{"_":13},{"_":17}]','{"_":',''),'}','')
DEMO HERE
Except if you really need a space after coma which is not what you asked to be honest.

JSON Array in SQL - Extracting multiple values from JSON array

I have a JSON column [gca] that looks like this
[{
"i":"https://some.image.URL 1",
"u":"https://some.product.url",
"n":"Product 1 Name",
"q":"1",
"sk":"sku number 1",
"st":"$499.99"
},
{
"i":"https://some.image.URL 2",
"u":"https://some.product.url",
"n":"Product 2 Name",
"q":"1",
"sk":"sku number 2",
"st":"$499.99"
}]
I want to extract values specific to position. For example:
JSON_VALUE ([gca], '$[0].i') + ', ' + JSON_VALUE ([gca], '$[1].i')
So the result would be a string
image url 1, image url 2
I tried the cross apply solution from this answer, but I get this error:
JSON text is not properly formatted. Unexpected character 'h' is found at position 0
Expected results
-- https://i.stack.imgur.com/UaRjQ.png
Your statement is correct and based on the JSON data in the question, the reason for this "... JSON text is not properly formatted. Unexpected character 'h' is found at position 0' error ... " error is somewhere else.
Your JSON text is a valid JSON array, so you have two possible approaches to get your expected results:
if this JSON array has always two items, you should use JSON_VALUE() to access each item
if the count of the items is not known, you should use OPENJSON() with additional CROSS APPLY operator and string aggregation function.
Table:
CREATE TABLE #Data (
[gca] nvarchar(max)
)
INSERT INTO #Data
([gca])
VALUES
(N'[{"i":"https://some.image.URL 1","u":"https://some.product.url","n":"Product 1 Name","q":"1","sk":"sku number 1","st":"$499.99"},{"i":"https://some.image.URL 2","u":"https://some.product.url","n":"Product 2 Name","q":"1","sk":"sku number 2","st":"$499.99"}]'),
(N'[{"i":"https://some.image.URL 1","u":"https://some.product.url","n":"Product 1 Name","q":"1","sk":"sku number 1","st":"$499.99"},{"i":"https://some.image.URL 2","u":"https://some.product.url","n":"Product 2 Name","q":"1","sk":"sku number 2","st":"$499.99"}]')
Statements:
-- For fixed structure with two items
SELECT JSON_VALUE ([gca], '$[0].i') + ', ' + JSON_VALUE ([gca], '$[1].i') AS [http]
FROM #Data
-- For JSON array with multiple items and SQL Server 2017+
SELECT j.*
FROM #Data d
CROSS APPLY (
SELECT STRING_AGG([http], ',') AS [http]
FROM OPENJSON(d.[gca]) WITH ([http] varchar(max) '$.i')
) j
-- For JSON array with multiple items
SELECT STUFF(j.[http], 1, 1, N'') AS [http]
FROM #Data d
CROSS APPLY (
SELECT CONCAT(',', [http])
FROM OPENJSON(d.[gca]) WITH ([http] varchar(max) '$.i')
FOR XML PATH('')
) j([http])
Output:
-------------------------------------------------
http
-------------------------------------------------
https://some.image.URL 1,https://some.image.URL 2
https://some.image.URL 1,https://some.image.URL 2
Note, that JSON support was intoduced in SQL Server 2016 and STRING_AGG() was introduced in SQL Server 2017. For string aggregation in earlier versions use FOR XML PATH.

find the end point of a pattern in SQL server

There is a comma separated string in a column which looks like
test=1,value=2.2,system=321
I want to extract value out from the string. I can use select PatIndex('%value=%',columnName) then use left, but this only find the beginning of the patindex.
How to identify the end of pattern value=%, so we can extract the value out?
Chain a few SUBSTRING with CHARINDEX and your PATHINDEX.
DECLARE #text VARCHAR(100) = 'test=1,value=2.21954,system=321'
SELECT
Original = #text,
Parsed = SUBSTRING( -- Get a portion of the original value
#text,
PATINDEX('%value=%',#text) + 6, -- ... starting from the 'value=' (without the 'value=')
-1 + CHARINDEX( -- ... and get as many characters until the first comma
',',
SUBSTRING( -- ... (find the comma starting from the 'value=' onwards)
#text,
PATINDEX('%value=%',#text) + 6,
100)))
Result:
Original Parsed
test=1,value=2.2,system=321 2.2
Note that the CHARINDEX will fail if there is no comma after your value=. You can filter this with a WHERE.
I strongly suggest to store your values already split on a proper table and you wont have to deal with string nightmares like this.
You can use CHARINDEX with starting position to find the first comma after the pattern. CROSS APPLY is used to keep the query easier to read:
WITH tests(str) AS (
SELECT 'test=1,value=2.2,system=321'
)
SELECT str, substring(str, pos1, pos2 - pos1) AS match
FROM tests
CROSS APPLY (SELECT PATINDEX('%value=%', str) + 6) AS ca1(pos1)
CROSS APPLY (SELECT CHARINDEX(',', str, pos1 + 1)) AS ca2(pos2)
-- 2.2
First of all, don't store denormalized data in this way, if you want to query them. SQL, the language, isn't good at string manipulation. Parsing and splitting strings can't take advantage of indexes either, which means any query that tried to find eg all records that refer to system 321 would have to scan and parse all rows.
SQL Server 2016 and JSON
SQL Server 2016 added suppor for JSON and the STRING_SPLIT function. Earlier versions already provided the XML type. It's better to store complex values as JSON or XML instead of trying to parse the string.
One option is to convert the string into a JSON object and retrieve the value contents, eg :
DECLARE #text VARCHAR(100) = 'test=1,value=2.2,system=321'
select json_value('{"' + replace(replace(#text,',','","'),'=','":"') + '"}','$.value')
This returns 2.2.
The replacements converted the original string into
{"test":"1","value":"2.2","system":"321"}
JSON_VALUE(#json,'$.') will return the value property of that object
Earlier SQL Server versions
In earlier SQL Server version, you can convert that string into an XML element the same way and use XQuery :
DECLARE #text VARCHAR(100) = 'test=1,value=2.2,system=321';
declare #xml varchar(100)='<r ' + replace(replace(#text,',','" '),'=',' ="') + '" />';
select #xml
select cast(#xml as xml).value('(/r[1]/#value)','varchar(20)')
In this case #xml contains :
<r test ="1" value ="2.2" system ="321" />
The query result is 2.2
You can try like following.
DECLARE #xml AS XML
SELECT #xml = Cast(( '<X>' + Replace(txt, ',', '</X><X>') + '</X>' ) AS XML)
FROM (VALUES ('test=1,value=2.2,system=321')) v(txt)
SELECT LEFT(value, Charindex('=', value) - 1) AS LeftPart,
RIGHT(value, Charindex('=', Reverse(value)) - 1) AS RightPart
FROM (SELECT n.value('.', 'varchar(100)') AS value
FROM #xml.nodes('X') AS T(n))T
Online Demo
Output
+----------+-----------+
| LeftPart | RightPart |
+----------+-----------+
| test | 1 |
+----------+-----------+
| value | 2.2 |
+----------+-----------+
| system | 321 |
+----------+-----------+
You can try the below query if you are using SQL Server (2016 or above)
SELECT RIGHT(Value,CHARINDEX('=',REVERSE(Value))-1) FROM YourTableName
CROSS APPLY STRING_SPLIT ( ColumnName , ',' )
WHERE Value Like 'Value=%'

Return string of description given string of IDs (separated by commas)

I have one table A that has a column C and a lookup table (lookup) that provides a description given an ID.
Here the setup:
table A with column C values:
1,2,3
2,3,4
table lookup:
1, 'This'
2, 'is'
3, 'tricky'
4, 'SQL'
Provide a SQL (SQL Server 2005) statement that returns the following strings:
Input: 1,2,3 Output: 'This','Is','tricky'
Input: 2,3,4 Output: 'Is','tricky','SQL'
basically turning the string of IDs (from an input table A) into a string of descriptions
The Samples that come with SQL Server 2005 include a CLR function called Split(). It's the best way of splitting comma-separated lists like this by far.
Suppose you have a table called inputs, with a column called input.
I forget what the particular outputs of dbo.Split() are... so work with me here. Let's call the fields id and val, where id tells us which entry it is in the list.
WITH
separated AS (
SELECT i.input, s.id, s.val
FROM
dbo.inputs AS i
CROSS APPLY
dbo.Split(i.input) AS s
)
, converted AS (
SELECT s.input, s.id, m.string
FROM
separated AS s
JOIN
dbo.mapping AS m
ON m.number = CAST(s.val AS varchar(5))
)
SELECT c.input, (SELECT string + ' ' FROM converted AS c2 WHERE c2.input = c.input ORDER BY id FOR XML PATH('')) AS converted_string
FROM converted AS c
GROUP BY c.input;