how to covert this string in 10 different nodes in SQL Server 2012? - sql

My String is =
[10, 1],[7, 3],[15, 4],[10, 1],[14, 1]
How to convert it into 10 different nodes/values? My current attempt is like this
select CAST('<A>'+REPLACE(REPLACE( REPLACE(REPLACE('[10, 1],[7, 3],[15, 4],[10, 1],[14, 1]', '[', ''), ']', ''),',',''),' ','</A><A>')+'</A>' AS XML) AS Data
Answer=
<A>10</A><A>17</A><A>315</A><A>410</A><A>114</A><A>1</A>
I want it in 10 nodes/values instead of above. How i should do it in sql server 2012?

This is too long for comments
select REPLACE(REPLACE(REPLACE(#data, '],[', ''), '[', ''), ']', '')
Result :
10, 17, 315, 410, 114, 1
EDIT :
Is seems to you are looking for values only
select LTRIM(REPLACE(REPLACE(a.value('.', 'VARCHAR(30)'), '[', ''), ']', '')) [Data] from
(
select CAST('<A>'+REPLACE('[10, 1],[7, 3],[15, 4],[10, 1],[14, 1]', ',', '</A><A>')+'</A>' AS xml) AS Data
)a cross apply Data.nodes ('/A') as split(a)
Result :
Data
10
1
7
3
15
4
10
1
14
1

Already provided answers seem to work well, but I thought about a more versatile one (may work in more complex scenarios) using regular expressions:
Install sql-server-regex (e.g. for Sql Server 2014)
Use a "split" method
select Match from dbo.RegexSplit(#data, '\D') where Match <> ''
Performance testing
I noticed that using CLR functions is much faster than REPLACE as indicated below:
Using RegexSplit (about 20s for 1M elements)
declare #baseMsg varchar(max) = '[10, 1],[7, 3],[15, 4],[10, 1],[14, 1],'
declare #data varchar(max) = replicate(#baseMsg, 1000000)
select Match from dbo.RegexSplit(#data, '\D') where Match <> ''
Using REPLACE (about 15s for 2K elements)
declare #baseMsg varchar(max) = '[10, 1],[7, 3],[15, 4],[10, 1],[14, 1],'
declare #data varchar(max) = replicate(#baseMsg, 200)
select LTRIM(REPLACE(REPLACE(a.value('.', 'VARCHAR(30)'), '[', ''), ']', '')) [Data] from
(
select CAST('<A>'+REPLACE(#data, ',', '</A><A>')+'</A>' AS xml) AS Data
)a cross apply Data.nodes ('/A') as split(a)
So, we are talking about a difference of three orders of magnitude.
Of course, the solution should be chosen based on string length ,security permissions (maybe the SQLCLR is not allowed or the external library must be analyzed before it is allowed to run within SQL Server).

I found the answer, SQL should be as below :
select CAST('<A>'+REPLACE(REPLACE( REPLACE(
REPLACE('[10, 1],[7, 3],[15, 4],[10, 1],[14, 1]', '[', ''),
']', ' '),
',',''),
' ','</A><A>') +'</A>' AS XML) AS Data

Related

Multiple IIFs and CHARINDEXs searches to extract data from same column in SQL?

This is MS SQL Server 2017.
This currently works. I just can't believe that this is the best way to perform these actions.
The Meeting table is populated from multiple services. It has a MeetingComment column that we used to encode some additional information from some of those sources but is unused by other sources. I want to extract some of the coded information, when used, into separate columns: Source and MeetingType.
MeetingComment
Source
MeetingType
{{{[ASTRA][CLASS][TMR:OFF]}}} ...
ASTRA
CLASS
{{{[ASTRA][CLASS][MERGED][TMR:OFF]}}} ...
ASTRA
CLASS
{{{[ASTRA][EVENT:Study Session][TMR:OFF]}}} ...
ASTRA
EVENT:Study Session
{{{[ASTRA][EVENT:Meeting][TMR:OFF]}}} ...
ASTRA
EVENT:Meeting
{{{[ASTRA][EVENT:Maintenance][TMR:ON]}}} ...
ASTRA
EVENT:Maintenance
UNK
UNK
Here is the SQL that I currently have that is working:
SELECT
Meeting.MeetingID,
Meeting.MeetingComment,
Meeting.Subject,
Rooms.RoomName,
IIF(
CHARINDEX(
'{{{[', Meeting.MeetingComment
) = 1,
SUBSTRING(
Meeting.MeetingComment,
5,
CHARINDEX(
']', Meeting.MeetingComment
)-5
),
'UNK'
) AS Source,
IIF(
CHARINDEX(
'{{{[', Meeting.MeetingComment
) = 1,
SUBSTRING(
Meeting.MeetingComment,
CHARINDEX(
'[',
Meeting.MeetingComment,
(
CHARINDEX(
'[', Meeting.MeetingComment,
1
)
) + 2
) + 1,
CHARINDEX(
']',
Meeting.MeetingComment,
(
CHARINDEX(
']', Meeting.MeetingComment,
1
)
)+ 2
)- CHARINDEX(
'[',
Meeting.MeetingComment,
(
CHARINDEX(
'[', Meeting.MeetingComment,
1
)
) + 2
) -1
),
'UNK'
) AS MeetingType,
Meeting.Recurrence,
Meeting.Location
But, as a programmer, it bugs me to have to use the same conditional test (the IIF statements) for both fields and to have to do the same CHARINDEX lookups multiple times. So before I move on, I just wanted to check to see if there is a better way to do this. Thanks in advance.
It appears from your example you are simply after the content of the first and second set of brackets.
Assuming the current version of SQL Server a more elegant solution would be a little parsing with json conbined with conditional aggregation:
select MeetingComment, Max([Source]) [Source], Max(MeetingType) MeetingType
from t
cross apply (
select
case when [key]='1' then j.[value] end [Source],
case when [key]='3' then j.[value] end MeetingType
from OpenJson(Concat('["', replace(Translate(MeetingComment,'[]',',,'), ',', '","'), '"]')) j
)s
group by MeetingComment;

Find Substring in SQL

I have to find substring as follows.
Data as below
aaaa.bbb.ccc.dddd.eee.fff.ggg
qq.eeddde.rrr.t.hh.jj.jj.hh.hh
ee.r.t.y.u.i.ii.
I want output as-
bbb
eeeddde
r
challenge I am facing is all have (.) as separator so sub-string is tough to work.
SELECT SUBSTRING(string,CHARINDEX('.',string)+1,
(((LEN(string))-CHARINDEX('.', REVERSE(string)))-CHARINDEX('.',string))) AS Result
FROM [table]
bbb
eeeddde
r
looking substring between first and secound (.)
then it might be between second and third (.)
Here is one method:
select left(v.str1, charindex('.', v.str1 + '.') - 1)
from t cross apply
(values (stuff(t.string, 1, charindex('.', t.string + '.'), '')
) v(str1)
I assume (CHARINDEX) this is ms sql server.
CROSS APPLY is handy for intermediate calculations.
SELECT t.pos, t1.pos,
SUBSTRING(string, t.pos + 1, t1.pos - t.pos -1) AS Result
FROM [table]
CROSS APPLY ( VALUES(CHARINDEX('.',string)) ) t(pos)
CROSS APPLY ( VALUES(CHARINDEX('.',string, t.pos+1))) t1(pos)
Just another option is to use a little XML
Example
Declare #YourTable table (ID int,SomeColumn varchar(max))
Insert Into #YourTable values
(1,'aaaa.bbb.ccc.dddd.eee.fff.ggg')
,(2,'qq.eeddde.rrr.t.hh.jj.jj.hh.hh')
,(3,'ee.r.t.y.u.i.ii.')
Select ID
,SomeValue = convert(xml,'<x>' + replace(SomeColumn,'.','</x><x>')+'</x>').value('/x[2]','varchar(100)')
From #YourTable
Returns
ID SomeValue
1 bbb
2 eeddde
3 r
You can use left(), replace() and charindex() functions together :
select replace(
replace(
left(str,charindex('.',str,charindex('.',str)+1)),
left(str,charindex('.',str)),
''
),
'.'
,''
) as "Output"
from t;
Demo

Need Help in creating Dynamic SQL Query

I am using a SQL Server database. I have a SQL query which I have to write inside a stored procedure using SQL string and I am unable to write it.
The SQL query is
SELECT TOP (1000)
[OfficeNo], [CustNo], [SAPNo],
[Name1], [Name2],
[HomePhone], [OtherPhone], [FaxPhone], [cellPhone], [workPhone]
FROM
[dbo].[tblCustomers]
WHERE
OfficeNo = '1043'
AND (REPLACE(REPLACE(REPLACE(REPLACE(HomePhone,'(',''),' ',''),'-',''),')','') = '6147163987' )
OR (REPLACE(REPLACE(REPLACE(REPLACE(OtherPhone,'(',''),' ',''),'-',''),')','') = '6147163987'
OR (REPLACE(REPLACE(REPLACE(REPLACE(FaxPhone,'(',''),' ',''),'-',''),')','') = '6147163987'
OR (REPLACE(REPLACE(REPLACE(REPLACE(cellPhone,'(',''),' ',''),'-',''),')','') = '6147163987'
OR (REPLACE(REPLACE(REPLACE(REPLACE(workPhone,'(',''),' ',''),'-',''),')','') = '6147163987'))))
The above SQL query works, but I am unable to convert the above REPLACE statements inside a dynamic SQL string due to lot of single quotes and colons. And it is throwing errors.
Here is another option. This is using an inline table valued function which is a whole lot better for performance than a scalar function. There are several ways this could work but I chose to pass in both the stored (or formatted) value in addition to the desired clean value. This lets us use cross apply to filter out those rows that don't match.
create function PhoneNumberCheck
(
#StoredValue varchar(20)
, #CleanValue varchar(20)
) returns table as return
select CleanValue = #CleanValue
where #CleanValue = REPLACE(REPLACE(REPLACE(REPLACE(#StoredValue, '(', ''),' ', ''), '-', ''), ')', '')
Then to use this function we simply need to call it for each column of phone number values. One thing I should mention is in your original query you have top 1000 but you do not have an order by. This means you have no way of ensuring which rows you get back. If you use top you almost always need to include an order by.
SELECT TOP (1000) [OfficeNo]
,[CustNo]
,[SAPNo]
,[Name1]
,[Name2]
,[HomePhone]
,[OtherPhone]
,[FaxPhone]
,[cellPhone]
,[workPhone]
FROM [dbo].[tblCustomers] c
cross apply dbo.PhoneNumberCheck(HomePhone, '6147163987') hp
cross apply dbo.PhoneNumberCheck(OtherPhone, '6147163987') op
cross apply dbo.PhoneNumberCheck(FaxPhone, '6147163987') fp
cross apply dbo.PhoneNumberCheck(cellPhone, '6147163987') cp
cross apply dbo.PhoneNumberCheck(workPhone, '6147163987') wp
where OfficeNo = '1043'
--order by ???
Depending on what version of SQL server you are using there are better ways to do this now, but here is a function I have to clean phones for 2012 and earlier.
Create FUNCTION [dbo].[fn_CleanPhone] (
#phone VARCHAR(20))
RETURNS VARCHAR(10)
AS
BEGIN
RETURN CASE WHEN ISNUMERIC(LEFT(NULLIF(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(#phone,
'`', 1), '{', ''), '}', ''),'_', ''), ' ', ''), '-', ''), '.', ''), '(', ''), ')', ''), '/', ''), ''), 10)) = 1
THEN LEFT(NULLIF(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(#phone,
'`', 1), '{', ''), '}', ''), '_', ''), ' ', ''), '-', ''), '.', ''), '(', ''), ')', ''), '/', ''), ''), 10)
ELSE NULL
END
END
Then call the function like this in place of all your NULL and IF statements and LEFT statements as all of them are done in the function above
SELECT dbo.fn_CleanPhone('1234-22(23)')
so this in your where statement:
where OfficeNo = '1043'
AND (
dbo.fn_CleanPhone(HomePhone) = '6147163987' )
OR dbo.fn_CleanPhone(OtherPhone) = '6147163987' )
OR dbo.fn_CleanPhone(FaxPhone) = '6147163987' )
OR dbo.fn_CleanPhone(cellPhone) = '6147163987' )
OR dbo.fn_CleanPhone(workPhone) = '6147163987' )
) -- end for and
create a function to return the numbers of the input(sorry for bad naming):
CREATE FUNCTION GETCleand(#INPUT VARCHAR(MAX))
RETURNS VARCHAR(MAX)
AS
BEGIN
DECLARE #INDEX INT = 0
DECLARE #CLEANED VARCHAR(MAX)=''
WHILE(#INDEX<LEN(#INPUT))
BEGIN
IF(ISNUMERIC(SUBSTRING(#INPUT,#INDEX,1))=1)
SET #CLEANED = #CLEANED + SUBSTRING(#INPUT,#INDEX,1)
SET #INDEX = #INDEX + 1
END
RETURN #CLEANED
END
SELECT TOP (1000) [OfficeNo]
,[CustNo]
,[SAPNo]
,[Name1]
,[Name2]
,[HomePhone]
,[OtherPhone]
,[FaxPhone]
,[cellPhone]
,[workPhone]
FROM [dbo].[tblCustomers]
where OfficeNo = '1043' and
(GetCleaned(HomePhone) = '6147163987'
or GetCleaned(OtherPhone) = '6147163987'
or GetCleand(FaxPhone) = '6147163987'
or GetCleand(cellPhone) = '6147163987'
or GetCleand(workPhone) = '6147163987')
when you have some OR and AND conditions on your where you should use parentheses on OR ones
Perhaps the easiest way is to let TSQL do the loop without the performance hit when using a Row-By-Row query.
I have created a little test query hoping it will be easier for you to implement it in your case.
declare #table table
(
id int
,PhoneNr nvarchar(18)
)
insert into #table
values(1,'(123) 4567')
,(2,'123, 4567')
,(3,'123 4567')
,(4,'123 - 4567');
;with t1 as
(
select PhoneNr, id from #table
union all
select cast(replace(PhoneNr, substring(PhoneNr, PatIndex('%[^a-z0-9]%', PhoneNr), 1), '') as nvarchar(18)), id
from t1
where PatIndex('%[^a-z0-9]%', PhoneNr) > 0
)
select t1.PhoneNr from t1
where PatIndex('%[^a-z0-9]%', t1.PhoneNr) = 0
option (maxrecursion 0)
I would:
Replace the values entered in my test table with your test cases
Run the query and alter the regex if needed
integrate it into your table structure and cast the table phone column to in, if no error you will have achieved your transformation.
When you have your 'set up working' then compare the execution plans and you can pick your winner ;-)

Scaler Function in Where Clause Really Slow? How to use Cross Apply Instead?

I have some data, some of it was imported with different separators such as * - . or a space...some of it was removed on import, some was not. Some of the external values being compared to it has the same issue. So we remove all separators and compare that way, I don't want to just update the columns yet as the data isn't "mine".
So since I see this over and over in the code I am moving to stored procedures, I wrote a stored function to do it for me.
ALTER FUNCTION [dbo].[fn_AccountNumber_Format2]
(#parAcctNum NVARCHAR(50))
RETURNS NVARCHAR(50)
AS
BEGIN
SET #parAcctNum = REPLACE(REPLACE(REPLACE(REPLACE(#parAcctNum, '.', ''), '*', ''), '-', ''), ' ', '');
RETURN #parAcctNum
END
Normally the queries looked something like this and it takes less than a second to run on a few millions rows :
SELECT name1, accountID FROM tblAccounts WHERE (Replace(Replace(Replace(accountnumber, '.', ''), '*', ''), '-', '') = Replace(Replace(Replace('123-456-789', '.', ''), '*', ''), '-', ''));
So my first attempt with it like this takes 24 seconds to excecute:
SELECT name1, accountID FROM tblAccounts WHERE (dbo.fn_AccountNumber_Format2 ([accountnumber])) = Replace(Replace(Replace('123-456-789', '.', ''), '*', ''), '-', '');
This one 43 seconds:
SELECT name1, accountID FROM tblAccounts WHERE (dbo.fn_AccountNumber_Format2(accountnumber)) = (dbo.fn_AccountNumber_Format2 ('123-456-789'));
So the drastic slow down came as a complete shock to me as I expected the user defined function to run just the same as the system function REPLACE... After some research on stackexchange and google it seems that using Cross Apply and creating a table with the function may be a better solution but I have no idea how that works, can anyone help me with that?
Inline Function
CREATE FUNCTION [dbo].[uspAccountNumber_Format3]
(
#parAcctNum NVARCHAR(50))
RETURNS TABLE
AS
RETURN
(
SELECT REPLACE(REPLACE(REPLACE(REPLACE(#parAcctNum, '.', ''), '*', ''),'-', ''), ' ', '') AS AccountNumber
)
Usage
SELECT name1 ,
accountID
FROM tblAccounts
CROSS APPLY dbo.uspAccountNumber_Format3(accountnumber) AS a
CROSS APPLY dbo.uspAccountNumber_Format3('123-456-789') AS b
WHERE a.AccountNumber = b.AccountNumber

Select part of VARCHAR value separated by the same special characters [duplicate]

This question already has answers here:
How do I split a delimited string so I can access individual items?
(46 answers)
Closed 8 years ago.
I am using SQL Server 2008 R2
This is the value that I have :
DECLARE #DBB varchar(200) = 'A2gg3h.B2g3ghh3.Cggh3663.D1jhg23.Eh2hjj2g'
Returning the 2 outer values are easy enough :
SELECT LEFT(#DBB, CHARINDEX('.', #DBB)-1)
SELECT RIGHT(#DBB, CHARINDEX('.', #DBB)-1)
How would I alter script in order to select values :
1. 'Bg2g3ghh3'
2. 'Chggh3663'
3. 'Dh1jhg23'
Using CHARINDEX would only bring back (LEFT) 7 and (RIGHT) 9.
Thanks
Use this.
DECLARE #param NVARCHAR(MAX)
SET #param = 'A2gg3h.B2g3ghh3.Cggh3663.D1jhg23.Eh2hjj2g'
SELECT
Split.a.value('.', 'VARCHAR(100)') AS CVS
FROM
(
SELECT CAST ('<M>' + REPLACE(#param, '.', '</M><M>') + '</M>' AS XML) AS CVS
) AS A CROSS APPLY CVS.nodes ('/M') AS Split(a)
TRY THIS:
DECLARE #string VARCHAR(MAX),
#Split CHAR(1),
#X xml
SELECT #string = 'A2gg3h.B2g3ghh3.Cggh3663.D1jhg23.Eh2hjj2g',
#Split = '.'
SELECT #X = CONVERT(xml,'<root><s>' + REPLACE(#string,#Split,'</s><s>') + '</s></root>')
SELECT T.c.value('.','varchar(max)') AS Result
FROM #X.nodes('/root/s') T(c)