SQL match and remove multi-delimited value - sql

I'm trying to figure out how I can match and delete from on a multi delimited value.
For example
Table 1
Animal
Dog
Cat
Cow
Horse
Table 2
Animals
Leopard;fox;hen
Cat;fish
Cow;Horse;hen
I want to update the values in Table 2 (Animals) so the values in table 1 (Animal) are removed from the varchar value (in Table 2 (Animals)).
Example result:
Leopard;fox;hen
fish
hen
I'm not sure exactly how to approach this. I'm using SQL Server MGMT studio. Thanks in advance.

With the help of a Parse/Split Function and a Cross Apply.
I should note, the logic for the parse can easily be ported into the Cross Apply if you can't use (or want) a UDF.
Declare #Table1 table (Animal varchar(50))
Insert Into #Table1 values
('Dog'),
('Cat'),
('Cow'),
('Horse')
Declare #Table2 table (Animal varchar(50))
Insert Into #Table2 values
('Leopard;fox;hen'),
('Cat;fish'),
('Cow;Horse;hen')
;with cte as (
Select A.Animal
,B.*
From #Table2 A
Cross Apply [dbo].[udf-Str-Parse](A.Animal,';') B
Left Join #Table1 C on B.RetVal=C.Animal
Where C.Animal is null
)
Update #Table2 Set Animal=B.NewString
From #Table2 A
Join (
Select Distinct
Animal
,NewString = Stuff((Select ';' + RetVal From cte Where Animal=A.Animal For XML Path('')),1,1,'')
From cte A
) B on A.Animal = B.Animal
Returns
Animal NewString
Cat;fish fish
Cow;Horse;hen hen
Leopard;fox;hen Leopard;fox;hen
The Parse UDF if needed
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(#String,#Delimiter,'</x><x>')+'</x>' as xml).query('.')) as A
Cross Apply x.nodes('x') AS B(i)
);
--Select * from [dbo].[udf-Str-Parse]('Dog,Cat,House,Car',',')
--Select * from [dbo].[udf-Str-Parse]('John Cappelletti was here',' ')

Related

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

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'

SELECT only numeric without function in sql

I need sql query WITHOUT FUNCTION with SELECT only numeric characters.
For example, I have in sql table 0f-gh 14-2t-4 /// and I want get this -> 01424. How I can do it with sql query SELECT, without anything, only with SELECT
This is the logic from digitsonlyEE which is the fastest T-SQL based "digits only" function available today.
declare #table table (somestring varchar(50));
insert #table VALUES('abc123xxx555!!!999'),('##123ttt999'),('555222!');
SELECT *
FROM #table t
CROSS APPLY
(
SELECT DigitsOnly =
(
SELECT SUBSTRING(t.somestring,n,1)
FROM
(
SELECT TOP (LEN(ISNULL(t.somestring,CHAR(32))))
(CHECKSUM(ROW_NUMBER() OVER (ORDER BY (SELECT NULL))))
FROM
(VALUES ($),($),($),($),($),($),($),($),($),($)) a(x),
(VALUES ($),($),($),($),($),($),($),($),($),($)) b(x),
(VALUES ($),($),($),($),($),($),($),($),($),($)) c(x),
(VALUES ($),($),($),($),($),($),($),($),($),($)) d(x)
) iTally(n)
WHERE ((ASCII(SUBSTRING(t.somestring,N,1)) - 48) & 0x7FFF) < 10
FOR XML PATH('')
)
) digitsOnlyEE(digitsOnly);
Results:
somestring digitsOnly
--------------------- ----------
abc123xxx555!!!999 123555999
##123ttt999 123999
555222! 555222
Here is an inline approach
Declare #YourTable table (ID int,SomeCol varchar(max))
Insert Into #YourTable values
(1,'0f-gh 14-2t-4 ///')
Select A.ID
,B.*
From #YourTable A
Cross Apply (
Select NewValue = (Select substring(A.SomeCol,N,1)
From (Select Top (len(A.SomeCol)) N=Row_Number() Over (Order By (Select NULL)) From master..spt_values n1) S
Where substring(A.SomeCol,N,1) like '[0-9]%'
Order By N
For XML Path (''))
) B
Returns
ID NewValue
1 01424
Note: Use Outer Apply if you want to see null values in the event where the string has NO numerics.

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)

I have a table with ID and Name. I am not able to make query for 4th and 5th row

Table A
ID Name
1 Sachin
2 Rahul
3 Saurav
I want to display Names according to ID on UI.
IDs are 1,2,3,1/2,1/2/3
I have displayed Name for 1,2,3 but I am not able to fetch for id as 1/2 and 1/2/3 as sachin/rahul and sachin/rahul/saurav.
Fun with strings... The following will essentially do a global search and replace on the string of IDs.
Now, we can use a parse/split function if you need a more robust approach
Declare #YourTable table (ID int,Name varchar(25))
Insert Into #YourTable values
(1,'Sachin'),
(2,'Rahul'),
(3,'Saurav')
Declare #Fetch varchar(max) = '1,2,3,1/2,1/2/3'
Select #Fetch = Replace('|'+Replace(Replace(#Fetch,',','|,|'),'/','|/|')+'|',MapFrom,MapTo)
From (
Select MapFrom='|'+cast(ID as varchar(25))+'|'
,MapTo =Name
From #YourTable
) A
Select Replace(#Fetch,'|','')
Returns
Sachin,Rahul,Saurav,Sachin/Rahul,Sachin/Rahul/Saurav
EDIT- Just in case you need a TABLE Version
Declare #Names table (ID int,Name varchar(25))
Insert Into #Names values (1,'Sachin'),(2,'Rahul'),(3,'Saurav')
Declare #IDs table (ID int,IDList varchar(150))
Insert Into #IDs values (1,'1,2,3,1/2,1/2/3'),(2,'2,3,1/2/3')
;with cte as (
Select A.*
,Name = IIF(Charindex('/',B.RetVal)>0 and C.RetVal>1,'/','')+N.Name
,RN = Row_Number() over (Partition By A.ID Order By B.RetSeq,C.RetSeq)
From #IDs A
Cross Apply [dbo].[udf-Str-Parse](A.IDList,',') B
Cross Apply [dbo].[udf-Str-Parse](B.RetVal,'/') C
Join #Names N on N.ID=C.RetVal
)
Select Distinct
ID
,IDList
,NewString = Replace((Select Stuff((Select ',' +Name From cte Where ID=A.ID Order By RN For XML Path ('')),1,1,'') ),',/','/')
From cte A
Returns
ID IDList NewString
1 1,2,3,1/2,1/2/3 Sachin,Rahul,Saurav,Sachin/Rahul,Sachin/Rahul/Saurav
2 2,3,1/2/3 Rahul,Saurav,Sachin/Rahul/Saurav
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(#String,#Delimiter,'</x><x>')+'</x>' as xml).query('.')) as A
Cross Apply x.nodes('x') AS B(i)
);
--Select * from [dbo].[udf-Str-Parse]('Dog,Cat,House,Car',',')
--Select * from [dbo].[udf-Str-Parse]('John Cappelletti was here',' ')
--Performance On a 5,000 random sample -8K 77.8ms, -1M 79ms (+1.16), -- 91.66ms (+13.8)
Something like this?
declare #ids varchar(100) = '1/2/3'
,#names varchar(100) = ''
select #names += case when #names = '' then '' else '/' end + name
from mytable
where '/' + #ids +'/' like '%/' + cast(id as varchar(10)) + '/%'
order by id
select #names
Sachin/Rahul/Saurav

Line break or Carriage return in a Delimited Field in Sql

I have an email column that stores a minimum of more than 10 emails in a row. Now, I want to write a query that puts each email on a separate line, e.g:
hay#line.com
u#y.com
live.gmail.com
How do write this?
If you mean rows of data... Any Parse/Split function will do if you don't have 2016. Otherwise the REPLACE() as JohnHC mentioned
Declare #YourTable table (ID int,Emails varchar(max))
Insert Into #YourTable values
(1,'hay#line.com,u#y.com,live.gmail.com')
Select A.ID
,EMail=B.RetVal
From #YourTable A
Cross Apply [dbo].[udf-Str-Parse](A.EMails,',') B
Returns
ID EMail
1 hay#line.com
1 u#y.com
1 live.gmail.com
Or Simply
Select * from [dbo].[udf-Str-Parse]('hay#line.com,u#y.com,live.gmail.com',',')
Returns
RetSeq RetVal
1 hay#line.com
2 u#y.com
3 live.gmail.com
The Function if Needed
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(#String,#Delimiter,'</x><x>')+'</x>' as xml).query('.')) as A
Cross Apply x.nodes('x') AS B(i)
);
--Select * from [dbo].[udf-Str-Parse]('Dog,Cat,House,Car',',')
--Select * from [dbo].[udf-Str-Parse]('John Cappelletti was here',' ')
Use Replace()
select replace(MyEmailField, '<CurrentDelimeter>', char(13)) as NewEmail
from MyTable