Retrieving the only value from XML string - sql

Here is a my XML string of datatype nvarchar(MAX):
declare #string Nvarchar(MAX)
set #string='<ROOT><Data C="1" /><Data C="2" /><Data C="3" /></ROOT>'
Expected result:
#c='1,2,3'
i want to check that the value of "C" is exists in table or not

You can use the following code to get the values in tabular format:
declare #string Nvarchar(MAX)
set #string='<ROOT><Data C="1" /><Data C="2" /><Data C="3" /></ROOT>'
DECLARE #StringXML XML = CAST(#string as XML);
SELECT T.c.value('(./#C)[1]', 'INT')
FROM #StringXML.nodes('ROOT/Data') T(c);
Then, you can apply the EXIST clause.
SELECT STUFF
(
(
SELECT ',' + T.c.value('(./#C)[1]', 'VARCHAR(12)')
FROM #StringXML.nodes('ROOT/Data') T(c)
FOR XML PATH(''), TYPE
).value('.', 'VARCHAR(MAX)')
,1
,1
,''
);

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

Pivot TSQL table with dynamic columns

I am passing up JSON as a parameter to a SQL stored proc. I use a function that takes a JSON dictionary and creates a table with key value pairs (two columns) that I then use with a COALESCE to create dynamic sql for an INSERT statement. This works fine for a single dictionary, but I need to also be able to send up JSON string that contains an array of dictionaries. Right now, my parse function gives me this table variable:
What I need is a table variable like this:
I can get the column names from the first table with this:
SELECT DISTINCT element name from #JSONTable
I should mention that these elementname's can and will change. I will not know the number of distinct elementname values.
UPDATE - Using Umair's answer, I am getting close:
DECLARE #JSONString AS VARCHAR(MAX)
SET #JSONString = '[{"ravid":3,"ravversion":2,"taskid":3},{"ravid":4,"ravversion":7,"taskid":99}]'
IF OBJECT_ID('tempdb..#JSONTable') IS NOT NULL
DROP TABLE #JSONTable
CREATE TABLE #JSONTable
(
elementname VARCHAR(500) ,
elementvalue VARCHAR(500)
)
INSERT INTO #JSONTable
( elementname ,
elementvalue
)
SELECT NAME ,
StringValue
FROM dbo.parseJSON(#JSONString)
WHERE LEN(StringValue) > 0 AND NAME IS NOT NULL
--declare a csv variable with all the distinct elements
DECLARE #csv NVARCHAR(max) = STUFF(
(
SELECT ',' + elementname
FROM (
SELECT DISTINCT elementname
FROM #JSONTable
) AS e
FOR XML PATH(''), TYPE
).value('.', 'nvarchar(max)'),
1,
1,
''
);
DECLARE #sql NVARCHAR(MAX) = '
SELECT *
FROM (
SELECT *, Row = ROW_NUMBER() OVER (PARTITION BY elementname ORDER BY elementname)
FROM #JSONTable
) AS t
PIVOT (
MAX(elementvalue)
FOR elementname IN (' + #csv + ')
) AS p
'
EXEC sp_executesql #sql
But the dictionary values don't correspond to the key. Here is the results of Umair's current answer:
How about
--declare a csv variable with all the distinct elements
DECLARE #csv NVARCHAR(max) = STUFF(
(
SELECT ',' + elementname
FROM (
SELECT DISTINCT elementname
FROM #JSONTable
) AS e
FOR XML PATH(''), TYPE
).value('.', 'nvarchar(max)'),
1,
1,
''
);
DECLARE #sql NVARCHAR(MAX) = '
SELECT *
FROM (
SELECT *, Row = ROW_NUMBER() OVER (PARTITION BY elementname ORDER BY elementvalue)
FROM #JSONTable
) AS t
PIVOT (
MAX(elementvalue)
FOR elementname IN (' + #csv + ')
) AS p
'
EXEC sp_executesql #sql
i.e dynamic pivoting :)
Edit:
Since you do not want to aggregate by anything in the pivot, I added a row number function to assign each distinct elementname a sequentially increasing id (based on element value). This will essentially group the pivot by this row column, producing all the required rows.

how to split and concatenate in sql server?

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

SQL- fetch value from XML parent node (separately)

In my below Query it's return all the recode set as a XML into a single variable.But i need all the parent node values into separate while loop.Just run the below query:
---------Just Declare the temp Table -------------------------------------------------
IF OBJECT_ID('tempdb.dbo.##TestTable','U')IS NOT NULL DROP TABLE ##TestTable
CREATE TABLE ##TestTable(id int,Uname nvarchar(max),Uaddress nvarchar(max))
INSERT INTO ##TestTable values (1,'abc','NY')
INSERT INTO ##TestTable values (2,'def','WD')
INSERT INTO ##TestTable values (3,'','KL')
DECLARE #XML XML
DECLARE #WhereClause nvarchar(max)
DECLARE #CountVal int
SELECT #CountVal=count(*) from ##TestTable
SET #XML= (SELECT * FROM ##TestTable FOR XML PATH('ParentNode'), ELEMENTS XSINIL)
SELECT #XML
;with cte as
( select xr.value('fn:local-name(.)','nvarchar(max)') name, xr.value('.','nvarchar(max)') val from #xml.nodes('//.') xq(xr)
where xr.value('fn:local-name(.)','nvarchar(max)')<>''
)
SELECT #WhereClause= STUFF((select ' and ' + name + '='''+val+'''' from cte for xml path('')),1,4,'')
SELECT #WhereClause
WHILE (#CountVal>0)
BEGIN
SELECT #WhereClause
SET #CountVal=#CountVal-1
END
IF OBJECT_ID('tempdb.dbo.##TestTable','U')IS NOT NULL DROP TABLE ##TestTable
Current sample XML(in #XML):
<ParentNode xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><id>1</id><Uname>abc</Uname><Uaddress>NY</Uaddress></ParentNode><ParentNode xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><id>2</id><Uname>def</Uname><Uaddress>WD</Uaddress></ParentNode><ParentNode xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><id>3</id><Uname /><Uaddress>KL</Uaddress></ParentNode>
Current the output of #WhereClause is (all into in a single #WhereClause variable):
ParentNode='1abcNY' and id='1' and Uname='abc' and Uaddress='NY' and ParentNode='2defWD' and id='2' and Uname='def' and Uaddress='WD' and ParentNode='3KL' and id='3' and Uname='' and Uaddress='KL'
But my Expected output is:
Firstly(in #WhereClause): id='1' and Uname='abc' and Uaddress='NY'
Secondly(in #WhereClause):id='2' and Uname='def' and Uaddress='WD'
Thirdly(in #WhereClause):id='3' and Uname='' and Uaddress='KL'
.
.
How i get it. Thanks in advance.
Try this:
declare #WhereClause nvarchar(max)
declare #CountVal int
select #CountVal=count(*)
from ##TestTable
while #CountVal>0
begin
select #WhereClause =
(
select ' and '+T.N.value('local-name(.)', 'nvarchar(max)')+'='+T.N.value('.', 'nvarchar(max)')
from (
select *
from ##TestTable
where id = #CountVal
for xml path(''), type
) as C(X)
cross apply C.X.nodes('/*') as T(N)
for xml path(''), type
).value('substring((./text())[1], 6)', 'nvarchar(max)')
select #WhereClause
set #CountVal=#CountVal-1
end
seem to be late and having missunderstood, that woul habe been my approach
DECLARE #XML XML
DECLARE #WhereClause nvarchar(max)
DECLARE #CountVal int
SELECT #CountVal=count(*) from ##TestTable
SET #XML= (SELECT * FROM ##TestTable FOR XML PATH('ParentNode'), ELEMENTS XSINIL)
SELECT #XML
;with cte as
( select xr.value('fn:local-name(.)','nvarchar(max)') name, xr.value('.','nvarchar(max)') val from #xml.nodes('//.') xq(xr)
where xr.value('fn:local-name(.)','nvarchar(max)')<>'' and xr.value('fn:local-name(.)','nvarchar(max)')<>'ParentNode'
)
SELECT #WhereClause= SubString((select Case when Name ='id' then CHAR(10) +''+ name + '='''+val+'''' else ' and ' + name + '='''+val+'''' end from cte for xml path('')),2,100000000)
Print #WhereClause