json array to table - sql

I am using SQL Server 2014. I can't use openjson to read the following array and convert it to table structure. My array looks like below. It can have more than 2 objects. I have a few other column as well in the table (tableA) along with array column:
[{"Id":1725,"Number":"12345","qty":1,"Block":true},
{"Id":125,"Number":"1234544","qty":1,"Block":true}]
Created function below.
but it returns blank when I run it for my table as-
SELECT
dbo.fn_parse_json2xml(X.jsoncolumn) AS XML_Column
FROM
tableA AS X;
I could not get it to working.I apologize in advance if I missed something.
CREATE FUNCTION dbo.fn_parse_json2xml(
#json varchar(max)
)
RETURNS xml
AS
BEGIN;
DECLARE #output varchar(max), #key varchar(max), #value varchar(max),
#recursion_counter int, #offset int, #nested bit, #array bit,
#tab char(1)=CHAR(9), #cr char(1)=CHAR(13), #lf char(1)=CHAR(10);
--- Clean up the JSON syntax by removing line breaks and tabs and
--- trimming the results of leading and trailing spaces:
SET #json=LTRIM(RTRIM(
REPLACE(REPLACE(REPLACE(#json, #cr, ''), #lf, ''), #tab, '')));
--- Sanity check: If this is not valid JSON syntax, exit here.
IF (LEFT(#json, 1)!='{' OR RIGHT(#json, 1)!='}')
RETURN '';
--- Because the first and last characters will, by definition, be
--- curly brackets, we can remove them here, and trim the result.
SET #json=LTRIM(RTRIM(SUBSTRING(#json, 2, LEN(#json)-2)));
SELECT #output='';
WHILE (#json!='') BEGIN;
--- Look for the first key which should start with a quote.
IF (LEFT(#json, 1)!='"')
RETURN 'Expected quote (start of key name). Found "'+
LEFT(#json, 1)+'"';
--- .. and end with the next quote (that isn't escaped with
--- and backslash).
SET #key=SUBSTRING(#json, 2,
PATINDEX('%[^\\]"%', SUBSTRING(#json, 2, LEN(#json))+' "'));
--- Truncate #json with the length of the key.
SET #json=LTRIM(SUBSTRING(#json, LEN(#key)+3, LEN(#json)));
--- The next character should be a colon.
IF (LEFT(#json, 1)!=':')
RETURN 'Expected ":" after key name, found "'+
LEFT(#json, 1)+'"!';
--- Truncate #json to skip past the colon:
SET #json=LTRIM(SUBSTRING(#json, 2, LEN(#json)));
--- If the next character is an angle bracket, this is an array.
IF (LEFT(#json, 1)='[')
SELECT #array=1, #json=LTRIM(SUBSTRING(#json, 2, LEN(#json)));
IF (#array IS NULL) SET #array=0;
WHILE (#array IS NOT NULL) BEGIN;
SELECT #value=NULL, #nested=0;
--- The first character of the remainder of #json indicates
--- what type of value this is.
--- Set #value, depending on what type of value we're looking at:
---
--- 1. A new JSON object:
--- To be sent recursively back into the parser:
IF (#value IS NULL AND LEFT(#json, 1)='{') BEGIN;
SELECT #recursion_counter=1, #offset=1;
WHILE (#recursion_counter!=0 AND #offset<LEN(#json)) BEGIN;
SET #offset=#offset+
PATINDEX('%[{}]%', SUBSTRING(#json, #offset+1,
LEN(#json)));
SET #recursion_counter=#recursion_counter+
(CASE SUBSTRING(#json, #offset, 1)
WHEN '{' THEN 1
WHEN '}' THEN -1 END);
END;
SET #value=CAST(
dbo.fn_parse_json2xml(LEFT(#json, #offset))
AS varchar(max));
SET #json=SUBSTRING(#json, #offset+1, LEN(#json));
SET #nested=1;
END
--- 2a. Blank text (quoted)
IF (#value IS NULL AND LEFT(#json, 2)='""')
SELECT #value='', #json=LTRIM(SUBSTRING(#json, 3,
LEN(#json)));
--- 2b. Other text (quoted, but not blank)
IF (#value IS NULL AND LEFT(#json, 1)='"') BEGIN;
SET #value=SUBSTRING(#json, 2,
PATINDEX('%[^\\]"%',
SUBSTRING(#json, 2, LEN(#json))+' "'));
SET #json=LTRIM(
SUBSTRING(#json, LEN(#value)+3, LEN(#json)));
END;
--- 3. Blank (not quoted)
IF (#value IS NULL AND LEFT(#json, 1)=',')
SET #value='';
--- 4. Or unescaped numbers or text.
IF (#value IS NULL) BEGIN;
SET #value=LEFT(#json,
PATINDEX('%[,}]%', REPLACE(#json, ']', '}')+'}')-1);
SET #json=SUBSTRING(#json, LEN(#value)+1, LEN(#json));
END;
--- Append #key and #value to #output:
SET #output=#output+#lf+#cr+
REPLICATE(#tab, ##NESTLEVEL-1)+
'<'+#key+'>'+
ISNULL(REPLACE(
REPLACE(#value, '\"', '"'), '\\', '\'), '')+
(CASE WHEN #nested=1
THEN #lf+#cr+REPLICATE(#tab, ##NESTLEVEL-1)
ELSE ''
END)+
'</'+#key+'>';
--- And again, error checks:
---
--- 1. If these are multiple values, the next character
--- should be a comma:
IF (#array=0 AND #json!='' AND LEFT(#json, 1)!=',')
RETURN #output+'Expected "," after value, found "'+
LEFT(#json, 1)+'"!';
--- 2. .. or, if this is an array, the next character
--- should be a comma or a closing angle bracket:
IF (#array=1 AND LEFT(#json, 1) NOT IN (',', ']'))
RETURN #output+'In array, expected "]" or "," after '+
'value, found "'+LEFT(#json, 1)+'"!';
--- If this is where the array is closed (i.e. if it's a
--- closing angle bracket)..
IF (#array=1 AND LEFT(#json, 1)=']') BEGIN;
SET #array=NULL;
SET #json=LTRIM(SUBSTRING(#json, 2, LEN(#json)));
--- After a closed array, there should be a comma:
IF (LEFT(#json, 1) NOT IN ('', ',')) BEGIN
RETURN 'Closed array, expected ","!';
END;
END;
SET #json=LTRIM(SUBSTRING(#json, 2, LEN(#json)+1));
IF (#array=0) SET #array=NULL;
END;
END;
--- Return the output:
RETURN CAST(#output AS xml);
END;
DECLARE #json varchar(max);
SET #json='{
"Person": {
"firstName": "John",
"lastName": "Smith",
"age": [25, 26, 27],
"Address": {
"streetAddress":"21, 2nd Street",
"city" :"New York",
"state":"NY",
"postalCode":"10021"
},
"PhoneNumbers": {
"home":"212 555-1234",
"fax":"646 555-4567"
}
}
}';
SELECT dbo.fn_parse_json2xml(#json);

That answer is really late, probably to late for you, but it might help someone else:
Assuming the structure is always the same you can transform your string to an XML rather easily:
DECLARE #jsonString NVARCHAR(MAX)=
N'[{"Id":1725,"Number":"12345","qty":1,"Block":true},
{"Id":125,"Number":"1234544","qty":1,"Block":true}]';
DECLARE #Converted XML=
CAST(REPLACE
(REPLACE
(REPLACE
(REPLACE
(REPLACE
(REPLACE
(REPLACE
(#jsonString,'[','')
,'{"Id":','<row><Id>')
,',"Number":','</Id><Number>')
,',"qty":','</Number><qty>')
,',"Block":','</qty><Block>')
,'},','</Block></row>')
,'}]','</Block></row>') AS XML);
--See the result
SELECT #Converted;
<row>
<Id>1725</Id>
<Number>"12345"</Number>
<qty>1</qty>
<Block>true</Block>
</row>
<row>
<Id>125</Id>
<Number>"1234544"</Number>
<qty>1</qty>
<Block>true</Block>
</row>
--This XML can be queries like here:
SELECT a.value('(Id/text())[1]','int') AS Id
,a.value('(Number/text())[1]','nvarchar(max)') AS Number
,a.value('(qty/text())[1]','int') AS qty
,a.value('(Block/text())[1]','bit') AS Block
FROM #Converted.nodes('/row') A(a);
The final result
Id Number qty Block
1725 "12345" 1 1
125 "1234544" 1 1

Related

Json input of a stored Procedure

I have a Stored Procedure with a JSON in input ,Is it possible to use the JSON as input for a stored procedure? how could i do that ?
CREATE PROCEDURE [warm].[stored _table]
(
#Json NVARCHAR(MAX)
)
AS
BEGIN
SET NOCOUNT ON;
BEGIN TRY
BEGIN
WITH JsonToTable AS
(
SELECT * FROM OPENJSON (#Json) WITH (
[type] [nvarchar](100),
[source] [nvarchar](38),
[time] [nvarchar](28),
[ID] [varchar](50) '$.data.ID',
[RegionCode] [varchar](10)'$.data.RegionCode'
[DueDate] [datetime2](7)'$.data.DueDate',
[SchedulStartDate] [datetime2](7)'$.data.SchedulStartDate',
)
)
MERGE [warm].[table] AS TARGET
USING JsonToTable AS SOURCE
ON (TARGET.ID = SOURCE.ID)
WHEN MATCHED THEN
UPDATE SET
TARGET.[RegionCode] = (SOURCE.[RegionCode]
TARGET.[DueDate] = [dbo].[ufn_cast_string_to_date](SOURCE.[DueDate])
,TARGET.[SchedulStartDate] = [dbo].[ufn_cast_string_to_date](SOURCE.[SchedulStartDate])
WHEN NOT MATCHED THEN
INSERT
(
[SourceID]
,[ID]
,[RegionCode]
,[DueDate]
,[SchedulStartDate])
VALUES
(
1
,[ID]
,[RegionCode]
,[dbo].[ufn_cast_string_to_date](SOURCE.[DueDate])
,[dbo].[ufn_cast_string_to_date](SOURCE.[SchedulStartDate])
);
END
END TRY
END
I Want to execute it with the request below :
DECLARE #return_value int
EXEC #return_value = [warm].[usp_upsert_warm_table]
#Json = N'{
"type" : "table",
"source" : "informations",
"time" : "2018-04-05T17:31:00Z",
"id" : "A11-111-111",
"data" : {"
"ID":"123-56",
"RegionCode":"2",
"DueDate":"2020-13-14T10:54:00",
"SchedulStartDate":"2020-12-14T10:54:00"
}'}
I get this Message Error :
JSON text is not properly formatted. Unexpected character '.' is found at position 480.**
This is the Valid input :
DECLARE #return_value int
EXEC #return_value = [warm].[usp_upsert_warm_table]
#Json = N'
{
"type" : "table1",
"source" : "",
"id" : "A111-111-11",
"time" : "2020-12-14 10:54:00",
"data" :{
"ApplicationID":"INFORMATIONS",
"ID":"157faf1657c-100",
"RegionCode":"2",
"DueDate":"2020-12-14 10:54:00",
"SchedulStartDate":"2020-12-14 10:54:00"}'}
For my input date : 2020-12-14 10:54:00 i deleteed the T in the middle
for my S.P : I change the type of My variables from datetime To varchar :
CREATE PROCEDURE [warm].[usp_upsert_warm_table]
...
[ID] [varchar](50) '$.data.ID',
[RegionCode] [varchar](10)'$.data.RegionCode'
[DueDate] [varchar](40)'$.data.DueDate',
[SchedulStartDate] [varchar](10)'$.data.SchedulStartDate',
....

Extract Certain String after Some Expression

I have table customer_info in which a column PrintData which contains many information. I would like to get Transaction id from that column.
The column data look like this:
<Line28>.TVR: 4000008000</Line28>
<Line29>.IAD: 06020103649D00</Line29>
<Line30>.TSI: E800</Line30>
<Line31>.ARC: 00</Line31>
<Line32>.CVM: PIN VERIFIED</Line32>
<Line33>.TRAN ID: 000000000075169</Line33>
I would like to get only 000000000075169 i.e. TRAN ID:
I have tried this as:
SUBSTRING(PrintData,CHARINDEX('TRAN ID: ',PrintData),CHARINDEX('</Li',PrintData))
but it is not giving write answer.
DECLARE #z NVARCHAR(MAX) = '
<Line28>.TVR: 4000008000</Line28>
<Line29>.IAD: 06020103649D00</Line29>
<Line30>.TSI: E800</Line30>
<Line31>.ARC: 00</Line31>
<Line32>.CVM: PIN VERIFIED</Line32>
<Line33>.TRAN ID: 000000000075169</Line33>
'
SELECT SUBSTRING(#z, CHARINDEX('TRAN ID: ', #z) + 9 -- offset charindex by 9 characters to omit the 'TRAN ID: '
, CHARINDEX('</Li', #z, CHARINDEX('TRAN ID: ', #z))-CHARINDEX('TRAN ID: ', #z) - 9) -- find the </Li AFTER the occurence of TRAN ID, and subract 9 to account for the offset
Yields 000000000075169.
Can you please with the following query.
DECLARE #PrintData AS VARCHAR (200) = '<Line33>.TRAN ID: 000000000075169</Line33>';
SELECT SUBSTRING(#PrintData,
CHARINDEX('TRAN ID: ', #PrintData) + LEN('TRAN ID: '),
CHARINDEX('</Li',#PrintData) - (CHARINDEX('TRAN ID: ', #PrintData) + LEN('TRAN ID: '))
);
The syntax is SUBSTRING (expression, start_position, length)
UPDATE:
As per the comment by MarcinJ, for the multiple instance of </Line, the folllowing query will work.
DECLARE #PrintData VARCHAR(2000) = '
<Line28>.TVR: 4000008000</Line28>
<Line29>.IAD: 06020103649D00</Line29>
<Line30>.TSI: E800</Line30>
<Line31>.ARC: 00</Line31>
<Line32>.CVM: PIN VERIFIED</Line32>
<Line33>.TRAN ID: 000000000075169</Line33>
';
DECLARE #FindString AS VARCHAR (20) = 'TRAN ID: ';
DECLARE #LenFindString AS INT = LEN(#FindString);
SELECT SUBSTRING(#PrintData,
CHARINDEX(#FindString, #PrintData) + #LenFindString,
CHARINDEX('</Line', #PrintData, CHARINDEX(#FindString, #PrintData)) - (CHARINDEX(#FindString, #PrintData) + #LenFindString)
);
I would simply use APPLY :
select #PrintData AS Original_string,
substring(tran_id, 1, charindex('</', tran_id) -1) as Tran_ID
from ( values ( substring(PrintData, charindex('TRAN ID:', PrintData) + 8, len(PrintData)) )
) t(tran_id);

JSON_MODIFY Remove Array Objects

How do you remove an object from a VARCHAR(MAX) JSON array?
Setup
DECLARE #Json VARCHAR(MAX) =
JSON_QUERY('[{"u":"user1","i":"item1"},{"u":"user2","i":"item2"}]')
SELECT #Json
DECLARE #NewJson VARCHAR(MAX) =
JSON_QUERY('{"u":"user3","i":"item3"}')
SELECT #NewJson
Result
[{"u":"user1","i":"item1"},{"u":"user2","i":"item2"}]
{"u":"user3","i":"item3"}
Append #NewJson to #Json
SELECT JSON_MODIFY(#Json, 'append $', JSON_QUERY(#NewJson))
Result
[{"u":"user1","i":"item1"},{"u":"user2","i":"item2"},{"u":"user3","i":"item3"}]
All good.
Remove Index 0 from #Json
SELECT JSON_MODIFY(#Json, '$[0]', NULL)
Result
[null,{"u":"user2","i":"item2"}]
I don't want index 0 to be null. I want it removed.
I can't find anything in the docs that state how to remove an object from an array.
I haven't worked with json but, since its a string you could do this;
SET #Json = JSON_MODIFY(#Json, '$[0]', NULL);
SET #Json = STUFF(#Json, CHARINDEX ('null,', 5, '');
Note That I can't test this code at the moment.

Comparing two nvarchar in a function

In a function I want to check whether passed value is equal to a predefined value (#ValX)
I have wrote following function:
ALTER Function IsNotInReview(#Val NVARCHAR(MAX))
RETURNS BIT
AS
BEGIN
DECLARE #ValX NVARCHAR(MAX)
SET #ValX = '
{
"httpCode" : 200,
"message" : "OK",
"result" : {
"items" : {
"items" : [ ]
}
}
}
'
IF LTRIM(RTRIM(#Val)) = LTRIM(RTRIM(#ValX))
BEGIN
RETURN 1
END
ELSE
BEGIN
RETURN 0
END
RETURN 0
END
When I test call the function, I am always getting false value
SELECT dbo.IsNotInReview('
{
"httpCode" : 200,
"message" : "OK",
"result" : {
"items" : {
"items" : [ ]
}
}
}
')--should return true
SELECT dbo.IsNotInReview('test')
UPDATE
I have updated my SP but still getting the same 'false' return value
ALTER Function IsNotInReview(#Val NVARCHAR(MAX))
RETURNS BIT
AS
BEGIN
DECLARE #ValX NVARCHAR(MAX)
SET #ValX = '
{
"httpCode" : 200,
"message" : "OK",
"result" : {
"items" : {
"items" : [ ]
}
}
}
'
DECLARE #ReturnVal BIT
IF LTRIM(RTRIM(#Val)) = LTRIM(RTRIM(#ValX))
BEGIN
SET #ReturnVal = 1
END
ELSE
BEGIN
SET #ReturnVal = 0
END
RETURN #ReturnVal
END
SELECT dbo.IsNotInReview('{
"httpCode" : 200,
"message" : "OK",
"result" : {
"items" : {
"items" : [ ]
}
}
}') --Return false which is unexpected
You are not comparing identical strings. Look at the indentation. I cannot imagine that this is what your intention is, i.e. comparing not only relevant content but also indentation.
A possible solution is to remove all white spaces, line breaks etc. in the string, while skipping over sections enclosed in double quotes.
Edit: Here is a little function which might be useful for you. It removes white spaces from your string while skipping quoted regions. Please test thoroughly, I only wrote it and testet it a bit (I have to go to work now)
create function RemoveWhiteSpaces (#String nvarchar(max))
returns nvarchar(max)
as
begin
declare #result nvarchar(max), #i int, #n int, #inQuotes bit,
declar #c0 nchar(1), #c nchar(1)
set #i=1
set #n=len(#string)
set #result=''
set #inQuotes=0
set #c='x'
while #i <= #n begin
set #c0=#c
set #c=substring(#string,#i,1)
if #c='"' and #c0 != '\'
set #inQuotes= 1 - #inQuotes
if #inQuotes = 1 or
(#inQuotes = 0 and #c not in (' ',char(13), char(10),char(8)))
set #result = #result + #c
set #i=#i+1
end
return #result
end

How to get a distinct list of words used in all Field Records using MS SQL?

If I have a table field named 'description', what would be the SQL (using MS SQL) to get a list of records of all distinct words used in this field.
For example:
If the table contains the following for the 'description' field:
Record1 "The dog jumped over the fence."
Record2 "The giant tripped on the fence."
...
The SQL record output would be:
"The","giant","dog","jumped","tripped","on","over","fence"
I do not think you can do this with a SELECT. The best chance is to write a user defined function that returns a table with all the words and then do SELECT DISTINCT on it.
Disclaimer: Function dbo.Split is from http://www.sqlteam.com/forums/topic.asp?TOPIC_ID=50648
CREATE TABLE test
(
id int identity(1, 1) not null,
description varchar(50) not null
)
INSERT INTO test VALUES('The dog jumped over the fence')
INSERT INTO test VALUES('The giant tripped on the fence')
CREATE FUNCTION dbo.Split
(
#RowData nvarchar(2000),
#SplitOn nvarchar(5)
)
RETURNS #RtnValue table
(
Id int identity(1,1),
Data nvarchar(100)
)
AS
BEGIN
Declare #Cnt int
Set #Cnt = 1
While (Charindex(#SplitOn,#RowData)>0)
Begin
Insert Into #RtnValue (data)
Select
Data = ltrim(rtrim(Substring(#RowData,1,Charindex(#SplitOn,#RowData)-1)))
Set #RowData = Substring(#RowData,Charindex(#SplitOn,#RowData)+1,len(#RowData))
Set #Cnt = #Cnt + 1
End
Insert Into #RtnValue (data)
Select Data = ltrim(rtrim(#RowData))
Return
END
CREATE FUNCTION dbo.SplitAll(#SplitOn nvarchar(5))
RETURNS #RtnValue table
(
Id int identity(1,1),
Data nvarchar(100)
)
AS
BEGIN
DECLARE My_Cursor CURSOR FOR SELECT Description FROM dbo.test
DECLARE #description varchar(50)
OPEN My_Cursor
FETCH NEXT FROM My_Cursor INTO #description
WHILE ##FETCH_STATUS = 0
BEGIN
INSERT INTO #RtnValue
SELECT Data FROM dbo.Split(#description, #SplitOn)
FETCH NEXT FROM My_Cursor INTO #description
END
CLOSE My_Cursor
DEALLOCATE My_Cursor
RETURN
END
SELECT DISTINCT Data FROM dbo.SplitAll(N' ')
I just had a similar problem and tried using SQL CLR to solve it. Might be handy to someone
using System;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;
using System.Collections;
using System.Collections.Generic;
public partial class UserDefinedFunctions
{
private class SplitStrings : IEnumerable
{
private List<string> splits;
public SplitStrings(string toSplit, string splitOn)
{
splits = new List<string>();
// nothing, return empty list
if (string.IsNullOrEmpty(toSplit))
{
return;
}
// return one word
if (string.IsNullOrEmpty(splitOn))
{
splits.Add(toSplit);
return;
}
splits.AddRange(
toSplit.Split(new string[] { splitOn }, StringSplitOptions.RemoveEmptyEntries)
);
}
#region IEnumerable Members
public IEnumerator GetEnumerator()
{
return splits.GetEnumerator();
}
#endregion
}
[Microsoft.SqlServer.Server.SqlFunction(FillRowMethodName = "readRow", TableDefinition = "word nvarchar(255)")]
public static IEnumerable fnc_clr_split_string(string toSplit, string splitOn)
{
return new SplitStrings(toSplit, splitOn);
}
public static void readRow(object inWord, out SqlString word)
{
string w = (string)inWord;
if (string.IsNullOrEmpty(w))
{
word = string.Empty;
return;
}
if (w.Length > 255)
{
w = w.Substring(0, 254);
}
word = w;
}
};
It is not the fastest approach but might be used by somebody for a small amount of data:
declare #tmp table(descr varchar(400))
insert into #tmp
select 'The dog jumped over the fence.'
union select 'The giant tripped on the fence.'
/* the actual doing starts here */
update #tmp
set descr = replace(descr, '.', '') --get rid of dots in the ends of sentences.
declare #xml xml
set #xml = '<c>' + replace(
(select ' ' + descr
from #tmp
for xml path('')
), ' ', '</c><c>') + '</c>'
;with
allWords as (
select section.Cols.value('.', 'varchar(250)') words
from #xml.nodes('/c') section(Cols)
)
select words
from allWords
where ltrim(rtrim(words)) <> ''
group by words
In SQL on it's own it would probably need to be a big stored procedure, but if you read all the records out to the scripting language of your choice, you can easily loop over them and split each out into arrays/hashes.
it'd be a messy stored procedure with a temp table and a SELECT DISTINCT at the end.
if you had the words already as records, you would use SELECT DISTINCT [WordsField] from [owner].[tablename]