how to split and concatenate in sql server? - sql

I am passing in a string into a stored procedure to be used in a select statement using dynamic sql:
#groups as nvarchar(1000) = 'group1,group10,group8'
I might just pass in string of numbers, eg, '1,2,3,4'
I want to split these values and then concatenate them so that they end up in the following manner :
'rmc.group1,rmc.group10,rmc.group8'

declare #groups nvarchar(1000) ='group1,group10,group8'
set #groups = 'rmc.' + replace(#groups, ',', ',rmc.')
select #groups
Result:
rmc.group1,rmc.group10,rmc.group8

Sql Fiddle Demo
Select Replace('group1,group10,group8','group','rmc.group')

Try this one -
DECLARE #groups nvarchar(1000) = 'group1,group10,group8'
SELECT STUFF((
SELECT ',rmc.' + t
FROM (
SELECT t = t.c.value('.', 'VARCHAR(50)')
FROM (
SELECT ID = CAST ('<t>' + REPLACE(#groups, ',', '</t><t>') + '</t>' AS XML)
) r
CROSS APPLY ID.nodes ('/t') t(c)
) t
FOR XML PATH(''), TYPE).value('.', 'VARCHAR(MAX)'), 1,1, '')
Output -
------------------------------------
rmc.group1,rmc.group10,rmc.group8

Related

Extract all records from a JSON column, using JSON type

I have a couple tables (see reproducible code at the bottom):
tbl1_have
id json_col
1 {"a_i":"a","a_j":1}
1 {"a_i":"b","a_j":2}
2 {"a_i":"c","a_j":3}
2 {"a_i":"d","a_j":4}
tbl2_have
id json_col
1 [{"a_i":"a","a_j":1},{"a_i":"b","a_j":2}]
2 [{"a_i":"c","a_j":3},{"a_i":"d","a_j":4}]
I wish to extract all json columns without providing explicit data type conversion for each columns since in my use case the names and amounts of nested attributes vary.
The expected output is the same for both cases:
tbl_want
id a_i a_j
1 a 1
1 b 2
2 c 3
2 d 4
with a_i and a_j correctly stored as a character and numeric column, which mean I'd like to map json types to SQL types (say INT and VARCHAR() here) automatically.
The following gets me half way for both tables:
SELECT id, a_i, a_j FROM tbl2_have CROSS APPLY OPENJSON(json_col)
WITH(a_i VARCHAR(100), a_j INT)
id a_i a_j
1 1 a 1
2 1 b 2
3 2 c 3
4 2 d 4
How can I work around mentioning the types explicitly in with() ?
reproducible code :
CREATE TABLE tbl1_have (id INT, json_col VARCHAR(100))
INSERT INTO tbl1_have VALUES
(1, '{"a_i":"a","a_j":1}'),
(1, '{"a_i":"b","a_j":2}'),
(2, '{"a_i":"c","a_j":3}'),
(2, '{"a_i":"d","a_j":4}')
CREATE TABLE tbl2_have (id INT, json_col VARCHAR(100))
INSERT INTO tbl2_have VALUES
(1, '[{"a_i":"a","a_j":1},{"a_i":"b","a_j":2}]'),
(2, '[{"a_i":"c","a_j":3},{"a_i":"d","a_j":4}]')
SELECT id, a_i, a_j FROM tbl1_have CROSS APPLY OPENJSON(json_col)
WITH(a_i VARCHAR(100), a_j INT)
SELECT id, a_i, a_j FROM tbl2_have CROSS APPLY OPENJSON(json_col)
WITH(a_i VARCHAR(100), a_j INT)
I am assuming that you don't know the name and type of keys in advance. You need to use dynamic SQL.
You first need to use OPENJSON without the WITH clause on the {objects} like so:
select string_agg(quotename(k) + case t
when 0 then ' nchar(1)' -- javascript null
when 1 then ' nvarchar(max)' -- javascript string
when 2 then ' float' -- javascript number
when 3 then ' bit' -- javascript boolean
else ' nvarchar(max) as json' -- javascript array or object
end, ', ') within group (order by k)
from (
select j2.[key], max(j2.[type])
from test
cross apply openjson(case when json_col like '{%}' then '[' + json_col + ']' else json_col end) as j1
cross apply openjson(j1.value) as j2
group by j2.[key]
) as kt(k, t)
The inner query gives you the name and type of all the keys across all json values in the table. The outer query builds the WITH clause for dynamic SQL.
The rest is relatively straight forward, use the generated clause in your dynamic SQL. Here is the complete example:
declare #table_name nvarchar(100) = 'test';
declare #with_clause nvarchar(100);
declare #query1 nvarchar(999) = N'select #with_clause_temp = string_agg(quotename(k) + case t
when 0 then '' nchar(1)''
when 1 then '' nvarchar(max)''
when 2 then '' float''
when 3 then '' bit''
else '' nvarchar(max) as json''
end, '', '') within group (order by k)
from (
select j2.[key], max(j2.[type])
from ' + quotename(#table_name) + '
cross apply openjson(case when json_col like ''{%}'' then ''['' + json_col + '']'' else json_col end) as j1
cross apply openjson(j1.value) as j2
group by j2.[key]
) as kt(k, t)';
exec sp_executesql #query1, N'#with_clause_temp nvarchar(100) out', #with_clause out;
declare #query2 nvarchar(999) = N'select id, j.*
from ' + quotename(#table_name) + '
cross apply openjson(json_col)
with (' + #with_clause + ') as j';
exec sp_executesql #query2;
Demo on db<>fiddle
I have found a solution that maybe works for your use case. I am no SQL-expert by any means, and i did not manage to automatically detect the datatypes of the dynamic columns. But i found a solution for your two examples.
First i tried to get all column names dynamically from the json_col. I found an answer on stackoverflow and got this piece of code:
STUFF(
(
SELECT DISTINCT ', '+QUOTENAME(columnname) FROM #tmpTbl FOR XML PATH(''), TYPE
).value('.', 'nvarchar(max)'), 1, 1, '');
This will output all column names as a string separated by commas, in your example: ' [a_i], [a_j]'. This can then be used to dynamically SELECT columns.
As already mentioned above, i was not able to write a datatype detection algorithm. I just hardcoded the columns to have nvarchar(100) as datatype.
To dynamically get the column-names with the corresponding datatype (hardcoded as nvarchar(100)) i used a slightly modified version of above query:
STUFF(
(
SELECT DISTINCT ', '+QUOTENAME(columnname)+' nvarchar(100)' FROM #tmpTbl FOR XML PATH(''), TYPE
).value('.', 'nvarchar(max)'), 1, 1, '');
Then i just used them in the WITH-CLAUSE.
Full version for the table tbl1_have
DECLARE #cols NVARCHAR(MAX), #colsWithType NVARCHAR(MAX), #query NVARCHAR(MAX);
DROP TABLE IF EXISTS #tmpTbl
SELECT outerTable.[id] AS columnid, innerTable.[key] AS columnname, innerTable.[value] AS columnvalue
INTO #tmpTbl
FROM tbl1_have outerTable CROSS APPLY OPENJSON(json_col) AS innerTable
SELECT * FROM #tmpTbl
SET #cols = STUFF(
(
SELECT DISTINCT ', '+QUOTENAME(columnname) FROM #tmpTbl FOR XML PATH(''), TYPE
).value('.', 'nvarchar(max)'), 1, 1, '');
SET #colsWithType = STUFF(
(
SELECT DISTINCT ', '+QUOTENAME(columnname)+' nvarchar(100)' FROM #tmpTbl FOR XML PATH(''), TYPE
).value('.', 'nvarchar(max)'), 1, 1, '');
SET #query = N'SELECT id, '+#cols+' FROM tbl1_have CROSS APPLY OPENJSON(json_col)
WITH('+#colsWithType+')';
exec sp_executesql #query
Full Version for the table tbl2_have:
DECLARE #cols NVARCHAR(MAX), #colsWithType NVARCHAR(MAX), #query NVARCHAR(MAX);
DROP TABLE IF EXISTS #tmpTbl
DROP TABLE IF EXISTS #tmpTbl2
SELECT *
INTO #tmpTbl
FROM tbl2_have CROSS APPLY OPENJSON(json_col)
SELECT outerTable.[id] AS columnid, innerTable.[key] AS columnname, innerTable.[value] AS columnvalue
INTO #tmpTbl2
FROM #tmpTbl outerTable CROSS APPLY OPENJSON([value]) AS innerTable
SELECT * FROM #tmpTbl
SELECT * FROM #tmpTbl2
SET #cols = STUFF(
(
SELECT DISTINCT ', '+QUOTENAME(columnname) FROM #tmpTbl2 FOR XML PATH(''), TYPE
).value('.', 'nvarchar(max)'), 1, 1, '');
SET #colsWithType = STUFF(
(
SELECT DISTINCT ', '+QUOTENAME(columnname)+' nvarchar(100)' FROM #tmpTbl2 FOR XML PATH(''), TYPE
).value('.', 'nvarchar(max)'), 1, 1, '');
SET #query = N'SELECT id, '+#cols+' FROM tbl2_have CROSS APPLY OPENJSON(json_col)
WITH('+#colsWithType+')';
exec sp_executesql #query
Would using the Value returned from OPENJSON work? It probably maps to a string data type, however, you do not have to know the type upfront. The official doc of the OPENJSON rowset function indicates that it returns a Key:Value pair as well as a Type for each parse. The Type value may be useful, however, it determines the datatype while parsing. I bet that Value is always a string type, as it would have to be.
;WITH X AS
(
SELECT id, a_i=J.[Key], a_j=J.[Value] FROM #tbl2_have CROSS APPLY OPENJSON(json_col) J
)
SELECT
id,
a_i=MAX(CASE WHEN J.[Key]='a_i' THEN J.[Value] ELSE NULL END),
a_j=MAX(CASE WHEN J.[Key]='a_j' THEN J.[Value] ELSE NULL END)
FROM X CROSS APPLY OPENJSON(X.a_j) J
GROUP BY
id,a_i,a_j

Returning the results of a select statement that uses a FOR XML PATH('') to condense to one row in a Stored Procedure

I need to pull several values out of some XML that I have and return it as one string from a stored procedure. I have created the select statement that will pull all of the rows into one.
This is the code that I currently have.
CREATE FUNCTION openrpt.getSearchTermNames (#searchID NVARCHAR(MAX))
RETURNS VARCHAR(MAX) AS
BEGIN
DECLARE #xmlData XML
DECLARE #Names XML
SET #xmlData =
(
SELECT QueryXML
FROM ConflictsSearchTerms
WHERE ConflictsSearchTerms.[ID] = #searchID);
WITH xmlnamespaces ( default 'http://schemas.datacontract.org/2004/07/IntApp.Wilco.Model.Conflicts.Searches', 'http://schemas.microsoft.com/2003/10/Serialization/Arrays' AS d2p1, 'http://www.w3.org/2001/XMLSchema-instance' AS i )
RETURN
(
SELECT '; ' + temp.value('.', 'NVARCHAR(MAX)')
FROM #xmlData.nodes('/ConflictsSearchTermQuery/TermItems/d2p1:string') AS XMLDATA(temp)
UNION
SELECT '; ' + temp.value('d2p1:Value[1]', 'NVARCHAR(MAX)') AS [Test]
FROM #xmlData.nodes('/ConflictsSearchTermQuery/CorporateTreeCompaniesById/d2p1:KeyValueOfstringstring') AS XMLDATA(temp) FOR XML PATH('')
)
The select statemt will correctly return a list of the items from those two fields in the XML seperated by semi-colons but I am getting a syntax error at the return . I also get an error that says
The FOR XML clause is invalid in views, inline functions, derived tables, and subqueries when they contain a set operator. To work around, wrap the SELECT containing a set operator using derived table syntax and apply FOR XML on top of it.
Try this:
CREATE FUNCTION openrpt.getSearchTermNames
(
#searchID NVARCHAR(MAX)
)
RETURNS NVARCHAR(MAX)
AS
BEGIN
DECLARE #xmlData XML
DECLARE #Names XML
DECLARE #x XML
SET #xmlData = ( SELECT QueryXML
FROM ConflictsSearchTerms
WHERE ConflictsSearchTerms.[ID] = #searchID
);
WITH XMLNAMESPACES ( DEFAULT 'http://schemas.datacontract.org/2004/07/IntApp.Wilco.Model.Conflicts.Searches', 'http://schemas.microsoft.com/2003/10/Serialization/Arrays' AS d2p1, 'http://www.w3.org/2001/XMLSchema-instance' AS i )
SELECT #x =
( SELECT * FROM (
SELECT '; ' + temp.value('.', 'NVARCHAR(MAX)') AS [Test]
FROM #xmlData.nodes('/ConflictsSearchTermQuery/TermItems/d2p1:string') AS XMLDATA(temp)
UNION
SELECT '; ' + temp.value('d2p1:Value[1]', 'NVARCHAR(MAX)')
FROM #xmlData.nodes('/ConflictsSearchTermQuery/CorporateTreeCompaniesById/d2p1:KeyValueOfstringstring') AS XMLDATA(temp))t
FOR XML PATH('')
)
RETURN CAST(#x AS NVARCHAR(MAX))
END

Split a string with 2 delimiters in SQL Server

I have some wrong data in my SQL Server database.
It's look like this:
abc.qwerty#yahoo.com|fubar#cc.uk|helloworld#gmail.com
or
abc.qwerty#yahoo.com;fubar#cc.uk
How can I split them to separate email if the delimiter can be anyone of these '|', ';', ','.
After spliting, the result will be add to 3 column in the same table:
Email1: abc.qwerty#yahoo.com, Email2: fubar#cc.uk, Email3: helloworld#gmail.com
DECLARE #STR NVARCHAR(MAX)='abc.es#yahoo.com|shudo#cc.uk|maria#gmail.com, abc.es#yahoo.com;shudo#cc.uk'
-- Converts values to rows
SELECT Split.a.value('.', 'VARCHAR(100)') 'Ids'
FROM
(
-- Use 3 REPLACE for '|', ';', ','
SELECT CAST ('<M>' + REPLACE(REPLACE(REPLACE(#STR, '|', '</M><M>'),',','</M><M>'),';','</M><M>') + '</M>' AS XML) AS Data
) AS A
CROSS APPLY Data.nodes ('/M') AS Split(a)
See the working FIDDLE here
UPDATE
If you want Comma Separated Value as final string you can do the below
DECLARE #STR NVARCHAR(MAX)='abc.qwerty#yahoo.com|fubar#cc.uk|helloworld#gmail.com,abc.qwerty#yahoo.com;fubar#cc.uk '
DECLARE #Final VARCHAR(MAX)='';
-- Converts values to rows
;WITH CTE AS
(
SELECT DISTINCT
Split.a.value('.', 'VARCHAR(100)') 'Ids'
FROM
(
-- Use 3 REPLACE for '|', ';', ','
SELECT CAST ('<M>' + REPLACE(REPLACE(REPLACE(#STR, '|', '</M><M>'),',','</M><M>'),';','</M><M>') + '</M>' AS XML) AS Data
) AS A
CROSS APPLY Data.nodes ('/M') AS Split(a)
)
-- Convert to Comma Seperated Values
SELECT #Final +=
Isnull('Email' + CAST(ROW_NUMBER() OVER(ORDER BY IDS)AS VARCHAR(10)) + ': ' + Ids, '') + ', '
FROM CTE
SELECT LEFT(#Final,len(#Final)-1)
PRINT #Final
UPDATE 2 : Split delimiter separated values to rows and convert to columns dynamically
I have written logic inside query
DECLARE #STR NVARCHAR(MAX)='abc.es#yahoo.com|shudo#cc.uk|maria#gmail.com,abc.es#yahoo.com;shudo#cc.uk'
;WITH CTE AS
(
-- Converts values to rows
SELECT DISTINCT Split.a.value('.', 'VARCHAR(100)') 'Ids'
FROM
(
-- Use 3 REPLACE for '|', ';', ','
SELECT CAST ('<M>' + REPLACE(REPLACE(REPLACE(#STR, '|', '</M><M>'),',','</M><M>'),';','</M><M>') + '</M>' AS XML) AS Data
) AS A
CROSS APPLY Data.nodes ('/M') AS Split(a)
)
-- Create a column to order Emails like Email1,Email2.... and insert to a temp table
-- It can be done without temp table, but its for sake of readability
SELECT 'Email' + CAST(ROW_NUMBER() OVER(ORDER BY Ids)AS VARCHAR(10)) EMails,Ids
INTO #TEMP
FROM CTE
-- Get columns to pivot
DECLARE #cols NVARCHAR (MAX)
SELECT #cols = COALESCE (#cols + ',[' + EMails + ']','[' + EMails + ']')
FROM (SELECT DISTINCT EMails FROM #TEMP) PV
ORDER BY EMails
-- Pivot the result(convert to columns from rows)
DECLARE #query NVARCHAR(MAX)
SET #query = 'SELECT * FROM
(
SELECT EMails,Ids
FROM #TEMP
) x
PIVOT
(
MIN(Ids)
FOR EMails IN (' + #cols + ')
) p;'
EXEC SP_EXECUTESQL #query
SQL FIDDLE
Now if you want to insert emails into your table you have created, you can use the below code
DECLARE #query NVARCHAR(MAX)
SET #query = 'INSERT INTO YOURTABLE(' + #cols + ')
SELECT * FROM
(
SELECT EMails,Ids
FROM #TEMP
) x
PIVOT
(
MIN(Ids)
FOR EMails IN (' + #cols + ')
) p;'
EXEC SP_EXECUTESQL #query
Step one I would clean your data. SQL Server has a Replace function you can use.
REPLACE ( string_expression , string_pattern , string_replacement )
Example you can run in SQL Server Management Studio:
select Replace(Replace('ab|de,ghi;de', ',', '|'), ';', '|')
Would first replace the commas, and then the semicolons. Resulting in ab|de|ghi|de
Alternatively, you could just split up the data multiple times by running a cursor on the resulting split (with whatever mechanism you are using to split.

How to get all records on the basis of Search String In sql

I want to fetch all the records on the basis of Search string
E.G.
Column name: FileName
MasterRoomTwo.jpg
BedRoom.png
MasterbedRoom.gif
and in simple scenario I can use
Declare #FileName nvarchar(60) = NULL
set #FileName = '.jpg'
SELECT *
FROM JobAttachment
WHERE AND Tags LIKE '%' + ISNULL(#FileName ,FileName ) + '%'
ORDER BY updated DESC
but in my case I will get like
set #FileName = '.jpg,.Png,gif'
So how to make query like this?
Any help will be appreciated.
Thanks
Try this. Split the input string and use charindex
SELECT 'MasterRoomTwo.jpg' a INTO #temp UNION
SELECT 'BedRoom.png' UNION
SELECT 'MasterbedRoom.gif'
DECLARE #FileName NVARCHAR(60)
SET #FileName = '.jpg,.Png,gif'
SELECT *
FROM #temp
JOIN (SELECT Rtrim(Ltrim(Split.a.value('.', 'VARCHAR(100)'))) fs
FROM (SELECT Cast ('<M>' + Replace(#FileName, ',', '</M><M>')
+ '</M>' AS XML) AS Data) AS A
CROSS APPLY Data.nodes ('/M') AS Split(a)) ad
on Charindex(fs, a) > 0
Try this :
SELECT *
FROM JobAttachment a
JOIN (SELECT t1.nod.value('.', 'varchar(50)') tags
FROM (SELECT Cast('<N>.'
+ Replace(Replace(#FileName, '.', ''), ',', '</N><N>.')
+ '</N>' AS XML) AS format) t
CROSS APPLY format.nodes('/N') AS t1(nod)
WHERE t1.nod.value('.', 'varchar(50)') <> '.') fileformat
ON a.tag LIKE ( '%' + fileformat.tags + '%' )
You can create dynamic condition as
Declare #FileName nvarchar(60) = NULL
set #FileName = '.jpg,.Png,gif'
--append a comma to the string to get correct results with empty strings
--or strings with a single value (no commas)
SET #FileName = #FileName + ',';
declare #x XML
declare #FileSearch nvarchar(max)
select #x = cast( '<F>' + replace ( #FileName,',','</F><F>') + '</F>' as xml)
select #FileSearch = stuff( isnull(#FileSearch , '') +
' OR FileName Like ''%'+ isnull(t.value('.','nvarchar(60)'),'')
+'%''' ,1,3,'')
from #x.nodes('/F') as x(t)
And then create dynamic query to get desired results:
set #sql = 'select * from test where ' -- entire query goes here
+ #FileSearch
exec sp_executesql #sql
DEMO

Comma separated column value from Temporary Table

I am inserting select statement result to Temporary table. I want its one column value comma separated, so I tried following but it is not working.
SELECT #IdList = COALESCE(#IdList+',' ,'') + s.Id
FROM (SELECT Id FROM #_TempStudentTable) as s
I have checked data in #_TempStudentTable, it is having data. But when i try to display IdList, it returns NULL.
Help me. I don't know what's the problem.
Try this :
DECLARE #IdList VARCHAR(MAX)
SELECT #IdList = COALESCE(#IdList + ',','') + CAST(Id AS VARCHAR(100))
FROM #_TempStudentTable
SELECT #IdList
You can get it without variable also using XML like below
SELECT STUFF(( SELECT ',' + CAST(Id AS VARCHAR(10))
FROM #_TempStudentTable
FOR XML PATH (''))
, 1, 1, '')
In SQL Server, you may use the below query to get a list of comma separated column names/headers:
SELECT STUFF((
SELECT ',' + CAST(name AS VARCHAR(50))
FROM (
SELECT name
FROM tempdb.sys.columns
WHERE [object_id] = OBJECT_ID(N'tempdb..#temptablename')
) k
FOR XML PATH('')
), 1, 1, '')