Extract value from double escaped JSON column in SQL - sql

I have ports_list table with ports column below:
ports
intended_output
"[{\"id\":193,\"name\":\"PORT C\"}]"
PORT C
"[{\"id\":204,\"name\":\"PORT D\"}]"
PORT D
"[{\"id\":45,\"name\":\"PORT I\"},{\"id\":193,\"name\":\"PORT C\"},{\"id\":204,\"name\":\"PORT D\"},{\"id\":271,\"name\":\"PORT G\"}]"
PORT I, PORT C, PORT D, PORT G
I use this method and somehow managed to get result when there is only single port value in a row unfortunately it is unable to cater for multiple port values in a row:
select ports,
REPLACE( substring(ports, CHARINDEX('"name\":\', ports, 1)+10, LEN(ports)-10),
'\"}]"',
''
)
from ports_list
ports
output
"[{\"id\":193,\"name\":\"PORT C\"}]"
PORT C
"[{\"id\":204,\"name\":\"PORT D\"}]"
PORT D
"[{\"id\":45,\"name\":\"PORT I\"},{\"id\":193,\"name\":\"PORT C\"},{\"id\":204,\"name\":\"PORT D\"},{\"id\":271,\"name\":\"PORT G\"}]"
PORT I"},{"id":193,"name":"PORT C"},{"id":204,"name":"PORT D"},{"id":271,"name":"PORT G"}]"
Can someone help me to get the intended output as per the first table?

Your ports column is a double-escaped JSON, in other words, it contains a JSON-escaped string that represents a JSON object.
So we just need to get it back into regular JSON to query it. Let's stuff it into a [] JSON array, then pass it to JSON_VALUE:
select ports,
STRING_AGG(j.name, ', ')
from ports_list
outer apply OPENJSON(JSON_VALUE('[' + ports + ']', '$[0]'))
with (name nvarchar(100) '$.name') as j
group by ports;

You created a soluntion to resolve only when exists one port.
The function CharIndex is returning the first ocorrency of '"name":' always and the function SUBSTRING always return the string minus the ten last characters.
A form to resolve the problem is creating the user function to return all ocorrency of port.
try create the function below
CREATE FUNCTION uf_findPorts
(
#string nvarchar(max)
)
RETURNS nvarchar(MAX)
AS
BEGIN
DECLARE
#length INT ,
#aux INT ,
#return NVARCHAR(MAX)
SET #length = LEN(#string)
SET #aux = 1
SET #return = ''
WHILE(CHARINDEX('"name\":\', #string,#aux) != 0)
BEGIN
SET #return = #return + ' ' + REPLACE( substring(#string,
CHARINDEX('"name\":\', #string,#aux)+10,
6),
'\"}]"',
''
)
SET #string = SUBSTRING(#string,CHARINDEX('"name\":\', #string,#aux) + 6,LEN(#string) )
END
RETURN #return
END
GO
To Test:
select ports,dbo.uf_findPorts(ports)
from ports_list
SELECT dbo.uf_findPorts('[{\"id\":45,\"name\":\"PORT I\"},{\"id\":193,\"name\":\"PORT C\"},{\"id\":204,\"name\":\"PORT D\"},{\"id\":271,\"name\":\"PORT G\"}]')
SELECT dbo.uf_findPorts('"[{\"id\":193,\"name\":\"PORT C\"}]"')
OBS: If exists any error of write you can corret me

Thanks to those who tried to help.
Posting my solution here if anyone stumbled into similar problem in the future:
select ports = replace(replace(replace(dbo.fnRemovePatternFromString(dbo.fnRemovePatternFromString(dbo.fnRemovePatternFromString(dbo.fnRemovePatternFromString(ports,'%[\"{}]%',1),'%name:%',5),'%id:%',3),'%[0-9]%',1),'[,',''),',,',','),']','')
from ports_list

Related

Error Handling for numbers of delimiters when extracting substrings

Situation: I have a column where each cell can have up to 5 delimiters. However, it's possible that there are none.
Objective: How do i handle errors such as :
Invalid length parameter passed to the LEFT or SUBSTRING function.
in the case that it cannot find the specified delimiter.
Query:
declare #text VARCHAR(111) = 'abc-def-geeee-ifjf-zzz'
declare #start1 as int
declare #start2 as int
declare #start3 as int
declare #start4 as int
declare #start_index_reverse as int
set #start1 = CHARINDEX('-',#text,1)
set #start2 = CHARINDEX('-',#text,charindex('-',#text,1)+1)
set #start3 = CHARINDEX('-',#text,charindex('-',#text,CHARINDEX('-',#text,1)+1)+1)
set #start4 = CHARINDEX('-',#text,charindex('-',#text,CHARINDEX('-',#text,CHARINDEX('-',#text,1)+1)+1)+1)
set #start_index_reverse = CHARINDEX('-',REVERSE(#text),1)
select
LEFT(#text,#start1-1) AS Frst,
SUBSTRING(#text,#start1+1,#start2-#start1-1) AS Scnd,
SUBSTRING(#text,#start2+1,#start3-#start2-1) AS Third,
SUBSTRING(#text,#start3+1,#start4-#start3-1)AS Third,
RIGHT(#text,#start_index_reverse-1) AS Lst
In this case my variable includes 5 delimiters and so my query works but if i removed one '-' it would break.
XML support in SQL Server brings about some unintentional but useful tricks. Converting this string to XML allows for some parsing that is far less messy than native string handling, which is very far from awesome.
DECLARE #test varchar(111) = 'abc-def-ghi-jkl-mnop'; -- try also with 'abc-def'
;WITH n(x) AS
(
SELECT CONVERT(xml, '<x>' + REPLACE(#test, '-', '</x><x>') + '</x>')
)
SELECT
Frst = x.value('/x[1]','varchar(111)'),
Scnd = x.value('/x[2]','varchar(111)'),
Thrd = x.value('/x[3]','varchar(111)'),
Frth = x.value('/x[4]','varchar(111)'),
Ffth = x.value('/x[5]','varchar(111)')
FROM n;
For a table it's almost identical:
DECLARE #foo TABLE ( col varchar(111) );
INSERT #foo(col) VALUES('abc-def-ghi-jkl-mnop'),('abc'),('def-ghi');
;WITH n(x) AS
(
SELECT CONVERT(xml, '<x>' + REPLACE(col, '-', '</x><x>') + '</x>')
FROM #foo
)
SELECT
Frst = x.value('/x[1]','varchar(111)'),
Scnd = x.value('/x[2]','varchar(111)'),
Thrd = x.value('/x[3]','varchar(111)'),
Frth = x.value('/x[4]','varchar(111)'),
Ffth = x.value('/x[5]','varchar(111)')
FROM n;
Results (sorry about the massive size, seems this doesn't handle 144dpi well):
add a test before your last select
then you should decide how to handle the other case (when one of start is 0)
You can also refer to this link about splitting a string in sql server
which is uses a loop and can handle any number of delimiters
if #start1>0 and #start2>0 and #start3>0 and #start4>0
select LEFT(#text,#start1-1) AS Frst,
SUBSTRING(#text,#start1+1,#start2-#start1-1) AS Scnd,
SUBSTRING(#text,#start2+1,#start3-#start2-1) AS Third,
SUBSTRING(#text,#start3+1,#start4-#start3-1)AS Third,
RIGHT(#text,#start_index_reverse-1) AS Lst

SQL HTML email showing blank when one table has no results

The following code sends 2 diferent tables based on an sql query through the function sp_send_dbmail , the catch is , if both tables return results , the email shows up without any problem , perfectly. If one of the tables has NO results, the email comes up completly blank.
How can i fix this?
Thanks
declare #tableHTML NVARCHAR(MAX);
set #tableHTML = N'Este foi o resultado de Faturas Emitidas: <br><br><table border ="1">' +
N'<tr><th>Documento</th></tr>' +
cast (( select td = cc.tx
from cc
for xml path ('tr'),type) as nvarchar(max)) +
N' </table><table border ="1"><tr><th>Valor Total Vencido</th></tr>'
+
cast (( select td = fx.tc
from fx
for xml path ('tr'),type) as nvarchar(max)) +
N'</table>';
EXEC sp_send_dbmail
#profile_name ='xx_SqlMail',
#recipients ='ccccc#hotmail.com',
#subject ='Resumo',
#body =#tableHTML,
#body_format='HTML';
I would suspect that part of your query is returning a NULL value. Concatenating any value with a NULL will always result in NULL.
SELECT 'A' + NULL + 'B' will return NULL.
As you are doing multiple concatenations it would mean that if any value is NULL then #tableHTML will be NULL. Try wrapping your selects in an ISNULL().
select ISNULL(td, '') = cc.tx ...
Any table in your concatenation that returns a NULL will make the entire concatenation NULL.
To resolve this, just wrap each section that could potentially be NULL with an ISNULL().
I had a similar issue where I was running two separate queries and building two tables that I wanted to include in the body of the email. One would occasionally return no values and the email would come back blank. Using ISNULL fixed it for me.
See the code below for an example of what I did:
set #tablesHTML = **ISNULL**(#tableOneHTML,'NO RESULTS')
+ **ISNULL**(#tableTwoHTML,'NO RESULTS')
exec XXXXXX.[XXX].[sp_send_dbmail]
#profile_name='Mail'
,#recipients = #EmailRecipients
,#copy_recipients= #EmailCopyRecipients
,#subject = 'Email Subject Here'
,#body = #tablesHTML
,#body_format = 'HTML'

String operation in SQL to reverse a string

In DB2 9.7 I am looking for a way to reverse a string in a SQL query.
I am familiar with SQL Server where the query would be like
SELECT
REVERSE(LEFT_TO_REIGHT) AS RIGHT_TO_LEFT
FROM
TABLE1;
I couldn't find a similar function in DB2. is there a simple way to reverse a string?
Creating a REVERSE(..) function is unnecessary.
DB2 has something called RIGHT(string-expression, length):
The RIGHT function returns the rightmost string of string-expression
of length length, expressed in the specified string unit. If
string-expression is a character string, the result is a character
string. If string-expression is a graphic string, the result is a
graphic string
So if you're interested in the last 8 characters, you can pretty trivially do this via:
SELECT RIGHT(left_to_right, 8) AS right_to_left
FROM Table1
(I'm actually still concerned about the fact that you're splitting off 8 characters consistently, as it implies you have a multi-part key of some sort).
Try something like:
SELECT STRIP(CAST( TRANSLATE('87654321',LEFT_TO_REIGHT, '12345678') AS VARCHAR(8) ))
FROM TABLE1;
Due to the original question this is the first webpage that comes up when one searches for 'How to reverse a string in DB2'.
Here is an answer that doesn't require implementing it in C and shouldn't brake on non-pure-Engilsh strings regardless of their length.
Be warned though, the efficiency is 'meh' at best.
CREATE FUNCTION REVERSE_STRING(STR VARCHAR(100))
RETURNS VARCHAR(100)
LANGUAGE SQL
SPECIFIC REVERSE_STRING
DETERMINISTIC
REVERSE: BEGIN
DECLARE REVERSED_STRING VARCHAR(100);
DECLARE REVERSED_CHARACTERS_INDEX INTEGER;
SET REVERSED_STRING='';
SET REVERSED_CHARACTERS_INDEX=0;
WHILE (REVERSED_CHARACTERS_INDEX < CHARACTER_LENGTH(STR, CODEUNITS16))
DO
SET REVERSED_CHARACTERS_INDEX = REVERSED_CHARACTERS_INDEX + 1;
SET REVERSED_STRING = CONCAT(
REVERSED_STRING,
LEFT(RIGHT(STR, REVERSED_CHARACTERS_INDEX, CODEUNITS16), 1, CODEUNITS16));
END WHILE;
RETURN REVERSED_STRING;
END REVERSE#
The idea is to get a substring which starts from the n-th character from the right till the end of the string, then take the first element of this substring from the left and append it to a reversed string. This operation is conducted n times where n is the length of a string to be reversed.
You can use it like any other function.
SELECT FIRSTNME AS FIRSTNAME, REVERSE_STRING(FIRSTNME) AS REVERSED_FIRSTNAME
FROM SAMPLE.EMPLOYEE#
Example output
Answering the original question of reversing a string there's user defined functions published on the IBM site that will do it that you can find here. There's apparently no built in ability in DB2
https://www.ibm.com/developerworks/community/blogs/SQLTips4DB2LUW/entry/reverse?lang=en
Tortured SQL version:
CREATE OR REPLACE FUNCTION REVERSE(INSTR VARCHAR(4000))
RETURNS VARCHAR(4000) SPECIFIC REVERSE
DETERMINISTIC NO EXTERNAL ACTION CONTAINS SQL
RETURN WITH rec(pos, res) AS (VALUES (1, CAST('' AS VARCHAR(4000)))
UNION ALL
SELECT pos + 1, SUBSTR(INSTR, pos , 1) || res
FROM rec
WHERE pos <= LENGTH(INSTR)
AND pos < 5000)
SELECT res FROM rec WHERE pos > LENGTH(INSTR);
But then you have to do this as well, yuck:
CREATE BUFFERPOOL bp32 PAGESIZE 32K;
CREATE SYSTEM TEMPORARY TABLESPACE tsp32 PAGESIZE 32K BUFFERPOOL bp32;
A saner C implementation
#include <sqludf.h>
#ifdef __cplusplus
extern "C"
#endif
void SQL_API_FN ReverseSBCP(SQLUDF_VARCHAR *inVarchar,
SQLUDF_VARCHAR *outVarchar,
SQLUDF_SMALLINT *inVarcharNullInd,
SQLUDF_SMALLINT *outVarcharNullInd,
SQLUDF_TRAIL_ARGS)
{
int inLen, inPos, outPos;
if (*inVarcharNullInd == -1)
{
*outVarcharNullInd = -1;
}
else
{
inLen = strlen(inVarchar);
for (inPos = 0, outPos = inLen -1; inPos < inLen; inPos++, outPos--)
{
outVarchar[outPos] = inVarchar[inPos];
}
outVarchar[inLen] = '\0';
*outVarcharNullInd = 0;
}
return;
}

MS-SQL - Extracting numerical portion of a string

I have an MS-SQL table, with a column titled 'ImportCount'.
Data in this column follows the below format:
ImportCount
[Schedules] 1376 schedule items imported from location H:\FOLDERA\AA\XX...
[Schedules] 10201 schedule items imported from location H:\FOLDERZZ\PERS\YY...
[Schedules] 999 schedule items imported from location R:\PERS\FOLDERA\AA\XX...
[Schedules] 21 schedule items imported from location H:\FOLDERA\MM\2014ZZ...
What I would like to do is extract that numerical portion of the data (which varies in length), but am struggling to get the right result. Would appreciate any help on this!
Thanks.
Try
select left(ImportCount, patindex('%[^0-9]%', ImportCount+'.') - 1)
select SUBSTRING(ImportCount,13,patindex('% schedule items%',ImportCount)-13) from table name
Try this..You can declare it as a SQL function also.
DECLARE #intText INT
DECLARE #textAplhaNumeric varchar(100)
set #textAplhaNumeric = '1376 schedule items imported from location'
SET #intText = PATINDEX('%[^0-9]%', #textAplhaNumeric)
BEGIN
WHILE #intText > 0
BEGIN
SET #textAplhaNumeric = STUFF(#textAplhaNumeric, #intText, 1, '' )
SET #intText = PATINDEX('%[^0-9]%', #textAplhaNumeric)
END
END
Select #textAplhaNumeric //output is 1376
It will work in case of NULL or empty values.
Please try:
SELECT LEFT(Val,PATINDEX('%[^0-9]%', Val+'a')-1) from(
SELECT
STUFF(ImportCount, 1, PATINDEX('%[0-9]%', ImportCount)-1, '') Val
FROM YourTable
)x

How to remove links from text with SQL

I need to clean up a database by removing links from tables. So for column entry like this:
Thank you for the important information<br />Read More Here<br /> This is great.
i need to remove the entire link, so it would end up like this:
Thank you for the important information<br /><br /> This is great.
Is there a way to do this with a single UPDATE statement?
For extra credit, is there a way to remove the HTML semantics from the link, while leaving the content in the text?
Just try to find the starting and ending of the hrefj and replace it with a single space.
declare #StringToFix varchar(500)
set #StringToFix = 'Thank you for the important information<br /><a href="http://www.cnn.com">Read More'
select REPLACE(
#stringtofix
, Substring(#StringToFix
, CHARINDEX('<a href=', #StringToFix) -- Starting Point
-- End Point - Starting Point with 4 more spaces
, CHARINDEX('</a>', #StringToFix)
- CHARINDEX('<a href=', #StringToFix) +4 )
, ' '
) as ResultField
If all the links are done in a very consistent way than you can just use a regex replace of
'\<a href.*?\</a\>'
to an empty string.
I don't have SQL Server instance handy but the query in oracle would look something like:
update table
set col1 = REGEXP_REPLACE(col1,'\<a href.*?\</a\>', '', 1, 0, 'in');
I want share my sql script that remove ahref tag from text but leave anchor text.
Source text:
Visit Google, then Bing
Result text:
Visit Google, then Bing
MS SQL CODE:
declare #str nvarchar(max) = 'Visit Google, then Bing'
declare #aStart int = charindex('<a ', #str)
declare #aStartTagEnd int = charindex('>', #str, #aStart)
DECLARE #result nvarchar(max) = #str;
set #result = replace(#result, '</a>', '')
select #result
WHILE (#aStart > 0 and #aStartTagEnd > 0)
BEGIN
declare #rep1 nvarchar(max) = substring(#result, #aStart, #aStartTagEnd + 1 - #aStart)
set #result = replace(#result, #rep1, '')
set #aStart = charindex('<a ', #result)
set #aStartTagEnd = charindex('>', #result, #aStart)
END
select #result