Is it possible to compare comma delimited string in T-SQL without looping? - sql

Let's say I have 2 tables where both has column called Brand. The value is comma delimited so for example if one of the table has
ACER,ASUS,HP
AMD,NVIDIA,SONY
as value. Then the other table has
HP,GIGABYTE
MICROSOFT
SAMSUNG,PHILIPS
as values.
I want to compare these table to get all matched record, in my example ACER,ASUS,HP and HP,GIGABYTE match because both has HP. Right now I'm using loop to achieve this, I'm wondering if it's possible to do this in a single query syntax.

You are correct in wanting to step away from the loop.
Since you are on 2012, String_Split() is off the table. However, there are any number of split/parse TVF functions in-the-wild.
Example 1 - without a TVF
Declare #T1 table (Brand varchar(50))
Insert Into #T1 values
('ACER,ASUS,HP'),
('AMD,NVIDIA,SONY')
Declare #T2 table (Brand varchar(50))
Insert Into #T2 values
('HP,GIGABYTE'),
('MICROSOFT'),
('SAMSUNG,PHILIPS')
Select Distinct
T1_Brand = A.Brand
,T2_Brand = B.Brand
From (
Select Brand,B.*
From #T1
Cross Apply (
Select RetVal = LTrim(RTrim(B.i.value('(./text())[1]', 'varchar(max)')))
From (Select x = Cast('<x>' + replace(Brand,',','</x><x>')+'</x>' as xml)) as A
Cross Apply x.nodes('x') AS B(i)
) B
) A
Join (
Select Brand,B.*
From #T2
Cross Apply (
Select RetVal = LTrim(RTrim(B.i.value('(./text())[1]', 'varchar(max)')))
From (Select x = Cast('<x>' + replace(Brand,',','</x><x>')+'</x>' as xml)) as A
Cross Apply x.nodes('x') AS B(i)
) B
) B
on A.RetVal=B.RetVal
Example 2 - with a TVF
Select Distinct
T1_Brand = A.Brand
,T2_Brand = B.Brand
From (
Select Brand,B.*
From #T1
Cross Apply [dbo].[tvf-Str-Parse](Brand,',') B
) A
Join (
Select Brand,B.*
From #T2
Cross Apply [dbo].[tvf-Str-Parse](Brand,',') B
) B
on A.RetVal=B.RetVal
Both Would Return
T1_Brand T2_Brand
ACER,ASUS,HP HP,GIGABYTE
The UDF if interested
CREATE FUNCTION [dbo].[tvf-Str-Parse] (#String varchar(max),#Delimiter varchar(10))
Returns Table
As
Return (
Select RetSeq = Row_Number() over (Order By (Select null))
,RetVal = LTrim(RTrim(B.i.value('(./text())[1]', 'varchar(max)')))
From (Select x = Cast('<x>' + replace((Select replace(#String,#Delimiter,'§§Split§§') as [*] For XML Path('')),'§§Split§§','</x><x>')+'</x>' as xml).query('.')) as A
Cross Apply x.nodes('x') AS B(i)
);
--Thanks Shnugo for making this XML safe
--Select * from [dbo].[tvf-Str-Parse]('Dog,Cat,House,Car',',')
--Select * from [dbo].[tvf-Str-Parse]('John Cappelletti was here',' ')
--Select * from [dbo].[tvf-Str-Parse]('this,is,<test>,for,< & >',',')

Had the same problem with comparing "," delimited strings
you can use "XML" to do that and compare the outputs and return the same/different value:
declare #TestInput nvarchar(255)
, #TestInput2 nvarchar(255)
set #TestInput = 'ACER,ASUS,HP'
set #TestInput2 = 'HP,GIGABYTE'
;WITH FirstStringSplit(S1) AS
(
SELECT CAST('<x>' + REPLACE(#TestInput,',','</x><x>') + '</x>' AS XML)
)
,SecondStringSplit(S2) AS
(
SELECT CAST('<x>' + REPLACE(#TestInput2,',','</x><x>') + '</x>' AS XML)
)
SELECT STUFF(
(
SELECT ',' + part1.value('.','nvarchar(max)')
FROM FirstStringSplit
CROSS APPLY S1.nodes('/x') AS A(part1)
WHERE part1.value('.','nvarchar(max)') IN(SELECT B.part2.value('.','nvarchar(max)')
FROM SecondStringSplit
CROSS APPLY S2.nodes('/x') AS B(part2)
)
FOR XML PATH('')
),1,1,'') as [Same Value]
Edit:
Changed 'Stuff' to 'XML'

Related

SQL replace/ remove multiple date and time stamp from any part of a string

I have a nvarchar field that contains multiple date & time stamps and various text. The date and time can be at any position in the field.
I want to select only the text from the field. I have tried with REPLACE and PATINDEX to no avail.
Please can anyone share how i would write my select on this example notes field which contains this string:
ADMIN1 21/04/2017 02:01:01 This student is here and trying to gain a masters.
ITSYS2 09/05/2017 03:51:04 60 APL Credits on xout
The following will exclude dates and times from the note_detail. This is an in-line approach, but just about any split/parse function will do the trick as well.
Example
Declare #YourTable table(studend_id int,note_detail varchar(max))
Insert Into #YourTable values
(1,'CHIDLOL 21/04/2017 02:01:01 '+CHAR(13)+CHAR(10)+'This studend is here and trying to gain a masters. THOMASXC 09/05/2014 03:54:04 60 APL Credon on xout')
Select A.studend_id
,new_note_detail = B.S
From #YourTable A
Cross Apply (
Select S = Stuff((Select ' ' +RetVal
From (
Select RetSeq = Row_Number() over (Order By (Select null))
,RetVal = LTrim(RTrim(B.i.value('(./text())[1]', 'varchar(max)')))
From (Select x = Cast('<x>' + replace((Select replace(replace(replace(A.note_detail,char(13),' '),char(10),' '),' ','§§Split§§') as [*] For XML Path('')),'§§Split§§','</x><x>')+'</x>' as xml).query('.')) as A
Cross Apply x.nodes('x') AS B(i)
) B1
Where RetVal not like '%[0-9]/[0-9][0-9]/[0-9]%'
and RetVal not like '%[0-9]:[0-9][0-9]:[0-9]%'
Order by RetSeq
For XML Path ('')),1,1,'')
) B
Returns
studend_id new_note_detail
1 CHIDLOL This studend is here and trying to gain a masters. THOMASXC 60 APL Credon on xout
Edit - Option 2 with a Parse Function
Select A.studend_id
,new_note_detail = B.S
From #YourTable A
Cross Apply (
Select S = Stuff((Select ' ' +RetVal
From [dbo].[udf-Str-Parse](replace(replace(A.note_detail,char(13),' '),char(10),' '),' ') B1
Where RetVal not like '%[0-9]/[0-9][0-9]/[0-9]%'
and RetVal not like '%[0-9]:[0-9][0-9]:[0-9]%'
Order by RetSeq
For XML Path ('')),1,1,'')
) B
The UDF if Interested
CREATE FUNCTION [dbo].[udf-Str-Parse] (#String varchar(max),#Delimiter varchar(10))
Returns Table
As
Return (
Select RetSeq = Row_Number() over (Order By (Select null))
,RetVal = LTrim(RTrim(B.i.value('(./text())[1]', 'varchar(max)')))
From (Select x = Cast('<x>' + replace((Select replace(#String,#Delimiter,'§§Split§§') as [*] For XML Path('')),'§§Split§§','</x><x>')+'</x>' as xml).query('.')) as A
Cross Apply x.nodes('x') AS B(i)
);
--Thanks Shnugo for making this XML safe
--Select * from [dbo].[udf-Str-Parse]('Dog,Cat,House,Car',',')
--Select * from [dbo].[udf-Str-Parse]('John Cappelletti was here',' ')
--Select * from [dbo].[udf-Str-Parse]('this,is,<test>,for,< & >',',')

SQL: How to create columns dynamically

I have a table which is created dynamically. So the number of columns is unknown at the time of creation. I want to create copies of each column in the same table with first column holding the first part of comma separated value, second column the second part and so on
For example,
ID Value1 Value2 .... Valuen
1 1;2;3 4;5;6
2 A;B;C D;E;F
I want to get the output like
ID Value1Copy1 Value1Copy2 Value1Copy3 Value2Copy1 Value2Copy2 Value2Copy3 .... ValuenCopy1
1 1 2 3 4 5 6
2 A B C D E F
I am unable to achieve this for variable number of columns
The following will dynamically unpivot your data. You may notice that the only field specified is ID.
The results are dropped into a #Temp table. From there we perform a dynamic pivot
Example
Declare #YourTable table (ID int,Value1 varchar(50),Value2 varchar(50))
Insert Into #YourTable values
( 1, '1;2;3','4;5;6'),
( 2, 'A;B;C','D;E;F')
Select A.ID
,Col = concat(C.Item,'Copy',D.RetSeq)
,Value = D.RetVal
Into #Temp
From #YourTable A --<< Replace with Your actual table
Cross Apply (Select XMLData = cast((Select A.* For XML Raw) as xml ) ) B
Cross Apply (
Select Item = attr.value('local-name(.)','varchar(100)')
,Value = attr.value('.','varchar(max)')
From B.XMLData.nodes('/row') as A(r)
Cross Apply A.r.nodes('./#*') AS B(attr)
Where attr.value('local-name(.)','varchar(100)') not in ('ID','Other2Exclude')
) C
Cross Apply (
Select RetSeq = Row_Number() over (Order By (Select null))
,RetVal = LTrim(RTrim(B.i.value('(./text())[1]', 'varchar(max)')))
From (Select x = Cast('<x>' + replace((Select replace(C.Value,';','§§Split§§') as [*] For XML Path('')),'§§Split§§','</x><x>')+'</x>' as xml).query('.')) as A
Cross Apply x.nodes('x') AS B(i)
) D
Where A.ID is not null -- or any other WHERE statement
Declare #SQL varchar(max) = Stuff((Select Distinct ',' + QuoteName(Col) From #Temp Order by 1 For XML Path('')),1,1,'')
Select #SQL = '
Select *
From #Temp
Pivot (max(Value) For [Col] in (' + #SQL + ') ) p'
Exec(#SQL);
Returns

Break multiple values from lines

I have a select that gives me a result like:
A, B, C
C, D, E
G, A
NULL
O, U, B, X
I would like to have the result like:
A
B
C
D
E
G
O
U
X
So, instead of multiple values from lines, each value should be in 1 line with DISTINCT and without NULL
Tried to use SQLFIDDLE to create an example but it is not working.
Everyone should have a good split/parse function.
Option 1 without a UDF
Declare #YourTable table (SomeCol varchar(100))
Insert Into #YourTable values
('A, B, C'),
('C, D, E'),
('G, A'),
(NULL),
('O, U, B, X')
Select Distinct B.RetVal
From #YourTable A
Cross Apply (
Select RetSeq = Row_Number() over (Order By (Select null))
,RetVal = LTrim(RTrim(B.i.value('(./text())[1]', 'varchar(max)')))
From (Select x = Cast('<x>' + replace((Select replace(A.SomeCol,',','§§Split§§') as [*] For XML Path('')),'§§Split§§','</x><x>')+'</x>' as xml).query('.')) as X
Cross Apply x.nodes('x') AS B(i)
) B
Where B.RetVal is not null
Option 2 With a Parse/Spit UDF
Select Distinct B.RetVal
From #YourTable A
Cross Apply [dbo].[udf-Str-Parse](A.SomeCol,',') B
Where B.RetVal is not null
Both Return
RetVal
A
B
C
D
E
G
O
U
X
The UDF if Interested
CREATE FUNCTION [dbo].[udf-Str-Parse] (#String varchar(max),#Delimiter varchar(10))
Returns Table
As
Return (
Select RetSeq = Row_Number() over (Order By (Select null))
,RetVal = LTrim(RTrim(B.i.value('(./text())[1]', 'varchar(max)')))
From (Select x = Cast('<x>' + replace((Select replace(#String,#Delimiter,'§§Split§§') as [*] For XML Path('')),'§§Split§§','</x><x>')+'</x>' as xml).query('.')) as X
Cross Apply x.nodes('x') AS B(i)
);
--Thanks Shnugo for making this XML safe
--Select * from [dbo].[udf-Str-Parse]('Dog,Cat,House,Car',',')
--Select * from [dbo].[udf-Str-Parse]('John Cappelletti was here',' ')
--Select * from [dbo].[udf-Str-Parse]('this,is,<test>,for,< & >',',')
You can use cross apply to unpivot them. The rest is just select distinct:
select distinct v.val
from t cross apply
(values (col1), (col2), (col3)) val
where val is not null;

Parsing a SQL field in a query

I've inherited a database of user profile information which has a column for personal interests. Multiple interests are separated by a pipe (|). In a SQL query, how can I split a field with this value: 2|27|33|14|15
To look like this:
2
27
33
14
15
The exact syntax depends on which dbms you are using. Assuming you are using MSSQL this is the general syntax
STRING_SPLIT ( string , separator )
For example
DECLARE #string_to_be_split NVARCHAR(400) = '2|27|33|14|15'
SELECT value
FROM STRING_SPLIT(#string_to_be_split, '|')
WHERE RTRIM(value) <> '';
Edit - Could have sworn that I saw SQL Server
If not 2016, just about any Split/Parse Function will do.
Option 1 - With UDF
Declare #YourTable table (ID int,Interests varchar(250))
Insert Into #YourTable values
(1,'2|27|33|14|15')
Select A.ID
,B.*
From #YourTable A
Cross Apply [dbo].[udf-Str-Parse](A.Interests,'|') B
Option 2 - Without a UDF
Select A.ID
,B.*
From #YourTable A
Cross Apply (
Select RetSeq = Row_Number() over (Order By (Select null))
,RetVal = LTrim(RTrim(B.i.value('(./text())[1]', 'varchar(max)')))
From (Select x = Cast('<x>' + replace((Select replace(A.Interests,'|','§§Split§§') as [*] For XML Path('')),'§§Split§§','</x><x>')+'</x>' as xml).query('.')) as X
Cross Apply x.nodes('x') AS B(i)
) B
Both Return
ID RetSeq RetVal
1 1 2
1 2 27
1 3 33
1 4 14
1 5 15
The UDF if Interested
CREATE FUNCTION [dbo].[udf-Str-Parse] (#String varchar(max),#Delimiter varchar(10))
Returns Table
As
Return (
Select RetSeq = Row_Number() over (Order By (Select null))
,RetVal = LTrim(RTrim(B.i.value('(./text())[1]', 'varchar(max)')))
From (Select x = Cast('<x>' + replace((Select replace(#String,#Delimiter,'§§Split§§') as [*] For XML Path('')),'§§Split§§','</x><x>')+'</x>' as xml).query('.')) as X
Cross Apply x.nodes('x') AS B(i)
);
--Thanks Shnugo for making this XML safe
--Select * from [dbo].[udf-Str-Parse]('Dog,Cat,House,Car',',')
--Select * from [dbo].[udf-Str-Parse]('John Cappelletti was here',' ')
--Select * from [dbo].[udf-Str-Parse]('this,is,<test>,for,< & >',',')

prevent automatic sorting when inserting with fn split

Inserts properly, however sorts id while inserting
If I execute the stored procedure parameters username = dynamic and id = 19,1,10
then when i check the Favorites table i see:
INSERT INTO Favorites(username, id)
SELECT #username, i.item
FROM fnSplit(#id, ',') i
INNER JOIN dbo.Link f on f.id = i.item
WHERE id IS NOT NULL
More information about split function:
https://msdn.microsoft.com/en-us/library/mt684588.aspx
NOTE: I am using a different name for the function but it is the same thing
I believe your inner join is changing the order. Since you are only using it for filtering, you can change the inner join into a where exists. This should preserve the order:
INSERT INTO Favorites( username, id )
SELECT #username, i.item
FROM fnSplit(#id, ',') i
WHERE EXISTS
(
SELECT 1
FROM dbo.Link f
WHERE f.id = i.item AND f.id IS NOT NULL
)
Example
Declare #username varchar(50) = 'dynamic'
Declare #favorite varchar(50) = '19,1,10'
Insert Into Favorites (username,id)
Select #username,f.ID
From [dbo].[udf-Str-Parse](#favorite,',') i
Join dbo.Link f on f.id = i.RetSeq
Where f.ID is not null
Order By RetSeq -- << Notice we added an Order By
If it helps with the visualization:
Select * From [dbo].[udf-Str-Parse]('19,1,10',',')
Returns
RetSeq RetVal
1 19
2 1
3 10
The TVF which will supply a Sequence (RetSeq)
CREATE FUNCTION [dbo].[udf-Str-Parse] (#String varchar(max),#Delimiter varchar(10))
Returns Table
As
Return (
Select RetSeq = Row_Number() over (Order By (Select null))
,RetVal = LTrim(RTrim(B.i.value('(./text())[1]', 'varchar(max)')))
From (Select x = Cast('<x>' + replace((Select replace(#String,#Delimiter,'§§Split§§') as [*] For XML Path('')),'§§Split§§','</x><x>')+'</x>' as xml).query('.')) as A
Cross Apply x.nodes('x') AS B(i)
);
--Thanks Shnugo for making this XML safe
--Select * from [dbo].[udf-Str-Parse]('Dog,Cat,House,Car',',')
--Select * from [dbo].[udf-Str-Parse]('John Cappelletti was here',' ')
--Select * from [dbo].[udf-Str-Parse]('this,is,<test>,for,< & >',',')
--Performance On a 5,000 random sample -8K 77.8ms, -1M 79ms (+1.16), -- 91.66ms (+13.8)