Compare comma delimited strings in SQL - sql

I have search requests that come in a CDL ("1,2,3,4"),("1,5"). I need to compare that to another CDL and return back all records that have a match. The kicker is the position of each number isn't always the same.
I've got something almost working except for instances where I'm trying to match ("2,5") to ("2,4,5"). Obviously the strings aren't equal but I need to return that match, because it has all the values in the first CDL.
My SQL Fiddle should make it fairly clear...
Any help would be much appreciated.
Oh and I saw this one is similar, but that seems a little drastic and over my head but I'll see if I can try to understand it.
Edit
So I just did a replace to change ("2,5") to ("%2%5%") and changed the were to use LIKE. From what I can initially tell it seems to be working.. SQL Fiddle Any reason I shouldn't do this or maybe I'm crazy and it doesn't work at all?

Just one step further, get closer to your answer.
SQL FIDDLE DEMO
SELECT P.*
FROM Product P
CROSS APPLY(
SELECT *
FROM ShelfSearch SS
WHERE Patindex(char(37)+replace(ss.shelflist, ',',char(37))+char(37),p.shelflist) > 0
)Shelfs

You can convert the lists to a table with the following function:
CREATE FUNCTION DelimitedStringToTable (#cdl varchar(8000), #delimiter varchar(1))
RETURNS #list table (
Token varchar(1000)
)
AS
BEGIN
DECLARE #token varchar(1000)
DECLARE #position int
SET #cdl = #cdl + #delimiter -- tack on delimiter to end
SET #position = CHARINDEX(#delimiter, #cdl, 1)
WHILE #position > 0
BEGIN
SET #token = LEFT(#cdl, #position - 1)
INSERT INTO #list (Token) VALUES (#token)
SET #cdl = RIGHT(#cdl, DATALENGTH(#cdl) - #position)
SET #position = CHARINDEX(#delimiter, #cdl, 1)
END
RETURN
END
Then you can use something like this to find all the matches:
SELECT list1.*
FROM DelimitedStringToTable('1,2,3', ',') list1
INNER JOIN DelimitedStringToTable('2', ',') list2 ON list1.Token = list2.Token

Related

Replace last matched substring of string

I want to replace a matched last substring of string
Example
DECLARE #string NVARCHAR(MAX)= 'Greeting<br><br>As many offices move to remote work. <br><br>Have a good day<br><br>'
SELECT REPLACE(#string,'<br><br>','')
Result need
#string = 'Greeting<br><br>As many offices move to remote work. <br><br>Have a good day'
Need to replace the last two with empty
If the performance is not an issue, you can try reversing the string and then cut the first section and reverse it back
DECLARE #string NVARCHAR(MAX)= 'Greeting<br><br>As many offices move to remote work. <br><br>Have a good day<br><br>'
declare #pat nvarchar(max) = (select REVERSE('<br><br>'))
declare #rsv nvarchar(max) = (select REVERSE(#string))
declare #idx int = (select CHARINDEX(#pat, #rsv, 0))
declare #result nvarchar(max) = (select left(#rsv, #idx - 1) + right(#rsv, len(#rsv) - #idx - LEN(#pat) + 1))
set #result = reverse(#result)
print #result
Result is:
Greeting<br><br>As many offices move to remote work. <br><br>Have a good day
You can shorten the code. I just made it verbose so the logic will be clear.
If it's always on the end another simpler method would be to add a special character to the string then replace including that.
select #string=Replace(#string+'~','<br><br>~','')

Creating multiple UDFs in one batch - SQL Server

I'm asking this question for SQL Server 2008 R2
I'd like to know if there is a way to create multiple functions in a single batch statement.
I've made the following code as an example; suppose I want to take a character string and rearrange its letters in alphabetical order. So, 'Hello' would become 'eHllo'
CREATE FUNCTION char_split (#string varchar(max))
RETURNS #characters TABLE
(
chars varchar(2)
)
AS
BEGIN
DECLARE #length int,
#K int
SET #length = len(#string)
SET #K = 1
WHILE #K < #length+1
BEGIN
INSERT INTO #characters
SELECT SUBSTRING(#string,#K,1)
SET #K = #K+1
END
RETURN
END
CREATE FUNCTION rearrange (#string varchar(max))
RETURNS varchar(max)
AS
BEGIN
DECLARE #SplitData TABLE (
chars varchar(2)
)
INSERT INTO #SplitData SELECT * FROM char_split(#string)
DECLARE #Output varchar(max)
SELECT #Output = coalesce(#Output,' ') + cast(chars as varchar(10))
from #SplitData
order by chars asc
RETURN #Output
END
declare #string varchar(max)
set #string = 'Hello'
select dbo.rearrange(#string)
When I try running this code, I get this error:
'CREATE FUNCTION' must be the first statement in a query batch.
I tried enclosing each function in a BEGIN END block, but no luck. Any advice?
Just use a GO statement between the definition of the UDFs
Not doable. SImple like that.
YOu can make it is one statement using a GO between them.
But as the GO is a batch delimiter.... this means you send multiple batches, which is explicitly NOT Wanted in your question.
So, no - it is not possible to do that in one batch as the error clearly indicates.

How to perform a WHERE similar to "NOT IN" with a list of value coming from a HTTP FORM?

I have web application developed in C#, where a page is showing the output of a SQL query; the query is a linq call to a store procedure and I am using SQL Server 2008.
One of the column display the tags associated with the result line; on the same page a list of check-boxes is displayed, each check-box correspond to a tag, and if the user turn on or off one or multiple check-box I want to filter the query.
My simplest SQL solution would be to us "NOT IN" like the following:
select * from [mytable] where [tags] not in ('tag1','tag2, etc...)
Given I convert the FORM POST to a string with comma separated values.
Example:
string tags = ParseFormAndConvertCheckBoxToCSV(Page.Request.Form);
string sqlcmd = "select * from [mytable] where [tags] not in (" + tags + ")";
But I don't want to dynamically build the SQL because as far as I know that would be bad.
I can imagine few way of splitting the string with comma separated list of values in SQL nd store the values into a in-memory table:
declare #tagscsv nvarchar(MAX)
declare #notags TABLE(Value nvarchar(50))
set #tagscsv = 'taxi,ivf'
Declare #x XML
select #x = cast('<A>'+ replace(#tagscsv,',','</A><A>')+ '</A>' as xml)
insert into #notags
select t.value('.', 'nvarchar(50)') as v from #x.nodes('/A') as x(t)
select * from [mytable] where [tags] not in (select * from #notags)
...but this too sounds like a dirty trick.
To me it looks like this should be a very common situation, and I figure out a lot of people out there faced this problem in the past, but searching on google I could not find an elegant solution.
Anyone can help?
Edit: Missed the stored procedure part.
With stored procedures you do not have a lot of options. I usually do what you did, split the comma seperated string and save the values to a temp table (or table variable).
CREATE PROCEDURE SplitAndStuff
#List nvarchar(MAX) = NULL,
#SplitOn nvarchar(5) = ',' --This can be replaced with a literal if it is always a comma.
AS
BEGIN
DECLARE #Pos int;
DECLARE #SplitOnLength int;
DECLARE #Results TABLE (value nvarchar(MAX))
SET #SplitOnLength = DATALENGTH(#SplitOn) / 2;
IF (RIGHT(#List, #SplitOnLength) <> #SplitOn) SET #List = #List + #SplitOn; --Add trailling split string if there is not one already.
SET #Pos = CHARINDEX(#SplitOn, #List, 1) --Initalize for loop. (The starting position returned is 1-based, not 0-based.)
WHILE #Pos > 0
BEGIN
INSERT INTO #Results (value) SELECT CAST(SUBSTRING(#List,1,#Pos-1)AS int);
SET #List = SUBSTRING(#List, #Pos+#SplitOnLength, DATALENGTH(#List) / 2);
SET #Pos = CHARINDEX(#SplitOn, #List, 1);
END
END
GO
If your stored procedure is not doing anything else (besides the look up) you could use my original LINQ answer below:
Since you are using LINQ you need to split tagscsv and use the resulting array to perform an "where in" query:
string[] tags = tagscsv.Split(',');
var output = from q in db.Table where !tags.Contains(q) select q;
See also:
http://msdn.microsoft.com/en-us/library/ms132407.aspx
Using LINQ to Perform "WHERE IN (Value1,Value2)" Queries
The NOT IN clause in LINQ to SQL

SQL Server - Filter field contents to numbers only

How can I copy the value of a field, but only its numbers?
I am creating a computed column for fulltext search, and I want to copy the values from my Phone Number fields (which are varchar) into it, but not with their formatting - numbers only. What is the command that would do this in my computed column formula?
Thank you!
You are going to have to write a user defined function to do this. There are several ways to do this, here is one that I found with some quick Googling.
CREATE FUNCTION dbo.RemoveChars(#Input varchar(1000))
RETURNS VARCHAR(1000)
BEGIN
DECLARE #pos INT
SET #Pos = PATINDEX('%[^0-9]%',#Input)
WHILE #Pos > 0
BEGIN
SET #Input = STUFF(#Input,#pos,1,'')
SET #Pos = PATINDEX('%[^0-9]%',#Input)
END
RETURN #Input
END
Warning: I wouldn't put this in a WHERE condition on a large table, or in a SELECT that returns millions of rows, but it will work.
Ultimately you are probably better stripping the non-numeric characters out in the UI of your app than in DB code.
Assuming there's only a couple of non-number characters, a nested replace functions do the trick:
select replace(replace(replace(col1,'-',''),'(',''),')','')
from YourTable
You can check if you caught all characters like:
select col1
from YourTable
where col1 not like '%[-()0-9]%'
(This example is checking for -, (), and numbers.)
I'd create a user-defined function that you could use in your select and where criteria, maybe something like this:
DECLARE #position int, #result varchar(50)
SET #position = 1
SET #result = ''
WHILE #position <= DATALENGTH(#input)
BEGIN
IF ASCII(SUBSTRING(#input, #position, 1)) BETWEEN 48 AND 57
BEGIN
SET #result = #result + SUBSTRING(#input, #position, 1)
END
SET #position = #position + 1
END
RETURN #result
Best of luck!
I realize this is a somewhat older question but there is no need to resort to looping for this. And these days we should try to avoid scalar functions when possible as they are not good for performance. We can leverage an inline table valued function in conjunction with the light support of regular expressions that we have in sql server. This article from Jeff Moden explains this in more detail from the perspective of why IsNumeric does not really work. http://www.sqlservercentral.com/articles/ISNUMERIC()/71512/
The gist of it is this nifty function he put together.
CREATE FUNCTION dbo.IsAllDigits
/********************************************************************
Purpose:
This function will return a 1 if the string parameter contains only
numeric digits and will return a 0 in all other cases. Use it in
a FROM clause along with CROSS APPLY when used against a table.
--Jeff Moden
********************************************************************/
--===== Declare the I/O parameters
(#MyString VARCHAR(8000))
RETURNS TABLE AS
RETURN (
SELECT CASE
WHEN #MyString NOT LIKE '%[^0-9]%'
THEN 1
ELSE 0
END AS IsAllDigits
)

SQL Server: How do you remove punctuation from a field?

Any one know a good way to remove punctuation from a field in SQL Server?
I'm thinking
UPDATE tblMyTable SET FieldName = REPLACE(REPLACE(REPLACE(FieldName,',',''),'.',''),'''' ,'')
but it seems a bit tedious when I intend on removing a large number of different characters for example: !##$%^&*()<>:"
Thanks in advance
Ideally, you would do this in an application language such as C# + LINQ as mentioned above.
If you wanted to do it purely in T-SQL though, one way make things neater would be to firstly create a table that held all the punctuation you wanted to removed.
CREATE TABLE Punctuation
(
Symbol VARCHAR(1) NOT NULL
)
INSERT INTO Punctuation (Symbol) VALUES('''')
INSERT INTO Punctuation (Symbol) VALUES('-')
INSERT INTO Punctuation (Symbol) VALUES('.')
Next, you could create a function in SQL to remove all the punctuation symbols from an input string.
CREATE FUNCTION dbo.fn_RemovePunctuation
(
#InputString VARCHAR(500)
)
RETURNS VARCHAR(500)
AS
BEGIN
SELECT
#InputString = REPLACE(#InputString, P.Symbol, '')
FROM
Punctuation P
RETURN #InputString
END
GO
Then you can just call the function in your UPDATE statement
UPDATE tblMyTable SET FieldName = dbo.fn_RemovePunctuation(FieldName)
I wanted to avoid creating a table and wanted to remove everything except letters and digits.
DECLARE #p int
DECLARE #Result Varchar(250)
DECLARE #BadChars Varchar(12)
SELECT #BadChars = '%[^a-z0-9]%'
-- to leave spaces - SELECT #BadChars = '%[^a-z0-9] %'
SET #Result = #InStr
SET #P =PatIndex(#BadChars,#Result)
WHILE #p > 0 BEGIN
SELECT #Result = Left(#Result,#p-1) + Substring(#Result,#p+1,250)
SET #P =PatIndex(#BadChars,#Result)
END
I am proposing 2 solutions
Solution 1: Make a noise table and replace the noises with blank spaces
e.g.
DECLARE #String VARCHAR(MAX)
DECLARE #Noise TABLE(Noise VARCHAR(100),ReplaceChars VARCHAR(10))
SET #String = 'hello! how * > are % u (: . I am ok :). Oh nice!'
INSERT INTO #Noise(Noise,ReplaceChars)
SELECT '!',SPACE(1) UNION ALL SELECT '#',SPACE(1) UNION ALL
SELECT '#',SPACE(1) UNION ALL SELECT '$',SPACE(1) UNION ALL
SELECT '%',SPACE(1) UNION ALL SELECT '^',SPACE(1) UNION ALL
SELECT '&',SPACE(1) UNION ALL SELECT '*',SPACE(1) UNION ALL
SELECT '(',SPACE(1) UNION ALL SELECT ')',SPACE(1) UNION ALL
SELECT '{',SPACE(1) UNION ALL SELECT '}',SPACE(1) UNION ALL
SELECT '<',SPACE(1) UNION ALL SELECT '>',SPACE(1) UNION ALL
SELECT ':',SPACE(1)
SELECT #String = REPLACE(#String, Noise, ReplaceChars) FROM #Noise
SELECT #String Data
Solution 2: With a number table
DECLARE #String VARCHAR(MAX)
SET #String = 'hello! & how * > are % u (: . I am ok :). Oh nice!'
;with numbercte as
(
select 1 as rn
union all
select rn+1 from numbercte where rn<LEN(#String)
)
select REPLACE(FilteredData,' ',SPACE(1)) Data from
(select SUBSTRING(#String,rn,1)
from numbercte
where SUBSTRING(#String,rn,1) not in('!','*','>','<','%','(',')',':','!','&','#','#','$')
for xml path(''))X(FilteredData)
Output(Both the cases)
Data
hello how are u . I am ok . Oh nice
Note- I have just put some of the noises. You may need to put the noises that u need.
Hope this helps
You can use regular expressions in SQL Server - here is an article based on SQL 2005:
http://msdn.microsoft.com/en-us/magazine/cc163473.aspx
I'd wrap it in a simple scalar UDF so all string cleaning is in one place if it's needed again.
Then you can use it on INSERT too...
I took Ken MC's solution and made it into an function which can replace all punctuation with a given string:
----------------------------------------------------------------------------------------------------------------
-- This function replaces all punctuation in the given string with the "replaceWith" string
----------------------------------------------------------------------------------------------------------------
IF object_id('[dbo].[fnReplacePunctuation]') IS NOT NULL
BEGIN
DROP FUNCTION [dbo].[fnReplacePunctuation];
END;
GO
CREATE FUNCTION [dbo].[fnReplacePunctuation] (#string NVARCHAR(MAX), #replaceWith NVARCHAR(max))
RETURNS NVARCHAR(MAX)
BEGIN
DECLARE #Result Varchar(max) = #string;
DECLARE #BadChars Varchar(12) = '%[^a-z0-9]%'; -- to leave spaces - SELECT #BadChars = '%[^a-z0-9] %'
DECLARE #p int = PatIndex(#BadChars,#Result);
DECLARE #searchFrom INT;
DECLARE #indexOfPunct INT = #p;
WHILE #indexOfPunct > 0 BEGIN
SET #searchFrom = LEN(#Result) - #p;
SET #Result = Left(#Result, #p-1) + #replaceWith + Substring(#Result, #p+1,LEN(#Result));
SET #IndexOfPunct = PatIndex(#BadChars, substring(#Result, (LEN(#Result) - #SearchFrom)+1, LEN(#Result)));
SET #p = (LEN(#Result) - #searchFrom) + #indexOfPunct;
END
RETURN #Result;
END;
GO
-- example:
SELECT dbo.fnReplacePunctuation('This is, only, a tést-really..', '');
Output:
Thisisonlyatéstreally
If it's a one-off thing, I would use a C# + LINQ snippet in LINQPad to do the job with regular expressions.
It is quick and easy and you don't have to go through the process of setting up a CLR stored procedure and then cleaning up after yourself.
Can't you use PATINDEX to only include NUMBERS and LETTERS instead of trying to guess what punctuation might be in the field? (Not trying to be snarky, if I had the code ready, I'd share it...but this is what I'm looking for).
Seems like you need to create a custom function in order to avoid a giant list of replace functions in your queries - here's a good example:
http://www.codeproject.com/KB/database/SQLPhoneNumbersPart_2.aspx?display=Print