How to extract value from a JSON string with no Key? - sql

I have a JSON column in one of the tables, and the JSON column has no key or property, only the value.
I tried to parse the column with JSON_Query and JSON_Value, but both of these functions only work if the JSON string has a key, but in my situation, the JSON string has no key.
So how can I parse the column from the top table to the bottom table in SQL Server like the image below?

Please try this:
DECLARE #Table TABLE (ID INT, [JSONColumn] NVARCHAR(MAX));
INSERT INTO #Table(ID,[JSONColumn])VALUES
(151616,'["B0107C57WO","B066EYU4IY"]')
,(151617,'["B0088MD64S"]')
;
SELECT t.ID,j.[value]
FROM #Table t
CROSS APPLY OPENJSON(t.JSONColumn) j
;

Related

T-SQL: Count Numbers of semicolons before expression

I got a table with strings that look like that:
'9;1;test;A;11002'
How would I count how many semicolons are there before the 'A'?
Cheers!
Using string functions
select len(left(str,charindex(str,'A')) - len(replace(left(str,charindex(str,'A'), ';', '')) n
from tbl
Hint1: The whole issue has some smell... You should not store your data as CSV string. But sometimes we have to work with what we have...
Hint2: The following needs SQL-Server v2016. With an older version we'd need to do something similar based on XML.
Try this:
--A declared table to mockup your issue
DECLARE #tbl TABLE(ID INT IDENTITY, YourCSVstring VARCHAR(100));
INSERT INTO #tbl(YourCSVstring)
VALUES('9;1;test;A;11002');
--the query
SELECT t.ID
,A.*
FROM #tbl t
CROSS APPLY OPENJSON(CONCAT(N'["',REPLACE(t.YourCSVstring,';','","'),N'"]')) A;
The idea in short:
We use some replacements to translate your CSV-string to a JSON array.
Now we can use use OPENJSON() to read it.
The value is the array item, the key its zero-based index.
Proceed with this however you need it.
Just to give you some fun: You can easily read the CSV type-safe into columns by doubling the [[ and using WITH to specify your columns:
SELECT t.ID
,A.*
FROM #tbl t
CROSS APPLY OPENJSON(CONCAT(N'[["',REPLACE(t.YourCSVstring,';','","'),N'"]]'))
WITH(FirstNumber INT '$[0]'
,SecondNumber INT '$[1]'
,SomeText NVARCHAR(100) '$[2]'
,YourLetterA NVARCHAR(100) '$[3]'
,FinalNumber INT '$[4]')A
returns:
ID FirstNumber SecondNumber SomeText YourLetterA FinalNumber
1 9 1 test A 11002

String_Split inserts only the first value

I'm trying to insert comma separated Guids into a temp table, to later check for a value using IN in these Guids. The following query is inserting only the first value in the table twice.
DECLARE #campaignids nvarchar(max) = '1DEBD122-FF1B-4E87-8812-D427ABA5D54E,FBD06A2E-24D1-4C06-B71D-B4306D8EA3BD'
DECLARE #TempCampaignIds TABLE (CampaignId uniqueidentifier)
INSERT INTO #TempCampaignIds
SELECT CAST(#campaignids AS uniqueidentifier)
FROM STRING_SPLIT(#campaignids, ',')
SELECT CampaignId FROM #TempCampaignIds
--result
CampaignId
1DEBD122-FF1B-4E87-8812-D427ABA5D54E
1DEBD122-FF1B-4E87-8812-D427ABA5D54E
You need to use the value from the string:
INSERT INTO #TempCampaignIds (CampaignId)
SELECT CAST(s.value AS uniqueidentifier)
FROM STRING_SPLIT(#campaignids, ',') s;
Here is a db<>fiddle.
I'm actually surprised that your code works, but SQL Server converts the first value of such a string without an error. That doesn't seem to happen for other data types. In fact, SQL Server appears to look at only the first 36 characters for a unique identifier.

Prevent double-escaped JSON in FOR JSON output in SQL

I have a small problem in my case because in my case a column can contain text 'John' directly or text as array '["John","Smith"]' both. So how can I prevent double-escaped JSON in FOR JSON output? I think I am doing something wrong here. Please check my example:
Create table #jsonTest(NameList varchar(max))
insert into #jsonTest(NameList)
select '["John","Smith"]'
Now if I want its output it will give correct output from this (without escape character):
select JSON_QUERY(NameList) NameList from #jsonTest for json auto
Output:
[{"NameList":["John","Smith"]}]
Simple text example:
truncate table #jsonTest
insert into #jsonTest(NameList)
Select 'John'
Now for this I have to change my select query for the correct output because JSON_QUERY, as mentioned, it only returns objects and arrays. So i've changed it to this:
select case when ISJSON(NameList) = 1 then JSON_QUERY(NameList) else NameList end NameList from #jsonTest for json auto
Output:
[{"NameList":"John"}]
Now It will give correct output for now but if I insert previous data again and try upper select query
truncate table #jsonTest
insert into #jsonTest(NameList)
select '["John","Smith"]'
select case when ISJSON(NameList) = 1 then JSON_QUERY(NameList) else NameList end NameList from #jsonTest for json auto
Output:
[{"NameList":"[\"John\",\"Smith\"]"}]
then it is giving escape characters in output. What is wrong in the code?
This behaviour is explained in the documentation - If the source data contains special characters, the FOR JSON clause escapes them in the JSON output with '\'. Of course, as you already know, when JSON_QUERY() is used with FOR JSON AUTO, FOR JSON doesn't escape special characters in the JSON_QUERY return value.
Your problem is the fact, that your data is not always a JSON. So, one possible approach is to generate a statement with duplicate column names (NameList). By default FOR JSON AUTO does not include NULL values in the output, so the result is the expected JSON. Just note, that you must not use INCLUDE_NULL_VALUES in the statement or the final JSON will contain duplicate keys.
Table:
CREATE TABLE #jsonTest(NameList varchar(max))
insert into #jsonTest(NameList)
select '["John","Smith"]'
insert into #jsonTest(NameList)
Select 'John'
Statement:
SELECT
JSON_QUERY(CASE WHEN ISJSON(NameList) = 1 THEN JSON_QUERY(NameList) END) AS NameList,
CASE WHEN ISJSON(NameList) = 0 THEN NameList END AS NameList
FROM #jsonTest
FOR JSON AUTO
Result:
[{"NameList":["John","Smith"]},{"NameList":"John"}]

How to update xml column node value with another column new value at same update query?

I want to change the value of 2 columns in one table. One column is varchar and the other is XML. First of all, I want to replace the value of the RECIPIENT column with the new value and replace the node value named as RecipientNo in the XML column with the new value of RecipientNo. How can I do these two operations in the same update function? The query below works. Secondly, DATARECORD table includes too many records. Does modify function take too much time to update the records? If so, how can I increase the performance of modify function or can you suggest another alternative solution? By the way, I cannot add index to DATARECORD table. Thanks.
Here is the sample row;
ID RECIPIENT RECORDDETAILS
1 1 <?xml version="1.0"?>
<MetaTag xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/XMLSchema">
<Code>123</Code>
<RecipientNo>123</RecipientNo>
<Name>xyz</Name>
</MetaTag>'
CREATE TABLE #TEMPTABLE(
ID bigint,
RECIPIENT nvarchar(max),
RECORDDETAILS xml
)
INSERT INTO #TEMPTABLE
SELECT ID,RECIPIENT,RECORDDETAILS
FROM DATARECORD WITH (NOLOCK)
WHERE cast(RECORDDETAILS as varchar(max)) LIKE '%<Code>123</Code>%' and cast(RECORDDETAILS as varchar(max)) LIKE '%MetaTag%'
UPDATE #TEMPTABLE SET RECIPIENT = CONCAT('["queryType|1","recipientNoIDENTIFICATION|',RECIPIENT,']')
UPDATE #TEMPTABLE SET RECORDDETAILS.modify('replace value of (MetaTag/RecipientNo/text())[1] with sql:column("RECIPIENT")')
UPDATE d
SET d.RECORDDETAILS =Concat('<?xml version="1.0"?>', CAST(t.RECORDDETAILS AS VARCHAR(max))),
d.RECIPIENT = t.RECIPIENT
FROM dbo.DATARECORD as d
Join #TEMPTABLE as t
ON t.ID = d.ID
It's certainly possible to update an SQL column and an XML node in the same update statement, e.g.:
create table DataRecord (
ID bigint not null primary key,
Recipient nvarchar(max) not null,
RecordDetails xml not null
);
insert DataRecord values
(1, N'1', N'<?xml version="1.0"?>
<MetaTag xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/XMLSchema">
<Code>123</Code>
<RecipientNo>123</RecipientNo>
<Name>xyz</Name>
</MetaTag>');
create table #TempTable (
ID bigint not null primary key,
Recipient nvarchar(max) not null,
RecordDetails xml not null
);
insert #TempTable
select ID, Recipient, RecordDetails
from DataRecord with (nolock)
where cast(RecordDetails as varchar(max)) like '%<Code>123</Code>%' and cast(RecordDetails as varchar(max)) like '%MetaTag%'
-- Change an SQL value and an XML node in the one update statement...
update tt set
Recipient = NewRecipient,
RecordDetails.modify('replace value of (/MetaTag/RecipientNo/text())[1] with sql:column("NewRecipient")')
from #TempTable tt
outer apply (
select NewRecipient = concat('["queryType|1","recipientNoIDENTIFICATION|', Recipient, '"]')
) Calc
select * from #TempTable
Which yields:
ID Recipient RecordDetails
1 ["queryType|1","recipientNoIDENTIFICATION|1"] <MetaTag
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/XMLSchema">
<Code>123</Code>
<RecipientNo>["queryType|1","recipientNoIDENTIFICATION|1"]</RecipientNo>
<Name>xyz</Name>
</MetaTag>
There are a couple of things contributing to your performance problem:
Converting XML, which SQL Server essentially stores in UTF-16 encoding, to varchar (twice) is expensive. It will also trash any Unicode characters outside your database's collation.
Performing like matches on the XML (converted to varchar) will be causing TABLE SCAN operations, converting and testing every row in your table.
Some things to consider:
Add XML Index(es) to the RecordDetails column and use something like WHERE RecordDetails.exists('/MetaTag/Code[.="123"]) to short list the rows to be updated.
Alternatively, pre-shred your RecordDetails, persist the value of /MetaTag/Code/text() in a table column (e.g.: MetaTagCode), and use something like WHERE MetaTagCode='123' in your query. Adding an index to that column will allow SQL to do a much cheaper INDEX SCAN when searching for the desired value instead of a TABLE SCAN.
Since you say you cannot add indexes you're basically going to have to tolerate TABLE SCANs and just wait it out.

Xml file node reading and inserting as seperate rows in a table

I am new bee in learning microsoft technologies.
I struck with an issue in sql server where I need your help.
Well, I have a XML file with below format,
please see it for your reference
<Permissions><Denied><User/><Roles/><Groups/></Denied><Allowed><Users/><Roles>admin,user,reader,writer,</Roles><Groups/></Allowed></Permissions>
In which I need to read Roles node values and insert those comma separated values as single row where I I will pass permissionid as a parameter in stored procedure.
here is the table columns (I need to insert single role for single row in test table based on transitionid)
create table test
(
empid int identity(1,1),
roles varchar(40),
transitionid int
)
You have two problems here: getting the data from the XML and splitting it.
If you're using SQL 2016 you're in luck - there's a new STRING_SPLIT function. You could use that like so:
declare #xml xml = '<Permissions><Denied><User/><Roles/><Groups/></Denied><Allowed><Users/><Roles>admin,user,reader,writer,</Roles><Groups/></Allowed></Permissions>';
declare #test table
(
empid int identity(1,1),
roles varchar(40),
transitionid int
)
INSERT #test (roles)
select b.value
FROM #xml.nodes('//Roles/text()')x(csv)
CROSS APPLY STRING_SPLIT(CAST(x.csv.query('.') AS VARCHAR(MAX)), ',')b
where b.value <> ''
select * from #test
Otherwise, you'll have to do something similar using a custom string splitting method, which you can find more about How do I split a string so I can access item x? or https://sqlperformance.com/2012/07/t-sql-queries/split-strings - basically, both require either writing a custom T-SQL function or CLR code that is imported into SQL Server. You could then use the same method as above (replacing STRING_SPLIT with the name of your custom string splitting function).