in Sql server
I have a following string
DECLARE #str nvarchar(max);
set #str = "Hello how are you doing today,Its Monday and 5 waiting days";
DECLARE #srch nvarchar(max);
set #srch = " how,doing,monday,waiting";
Now i want to check whether str contains any of string (comma separated string) of srch
I want it in only sql server
is there possibilites to write some query with in clause
like
select from #str where _____ in (select * from CommaSplit(#srch)
where CommaSplit function rerturns rows of #srch comma separted value
I dont want to use cursor or any loop concept as the #srch value can be very long
Thanks
you can use same function to get first string in rows
select string from CommaSplit(#srch,'') where string in (select * from CommaSplit(#srch)
You can use the following common table expressions query to split your string into parts. cte will contain one record per phrase in #srch. In my example below, I show where in #str each of the search phrase is located. It returns 0 if it cannot locate a search phrase.
Note 1: it won't show the location twice if your search phrase is duplicated - you would need another CTE for that.
Note 2: I have to add comma at the end of #srch to make my CTE work. You can do that inside the CTE if you prefer not to change the search string.
DECLARE #str nvarchar(max);
set #str = 'Hello how are you doing today,Its Monday and 5 waiting days';
DECLARE #srch nvarchar(max);
set #srch = 'how,doing,monday,waiting';
set #srch = #srch + ','
-- first split the text into 1 character per row
;with cte
as
(
select substring(#srch, 1, CHARINDEX(',', #srch, 1) - 1) as Phrase, CHARINDEX(',', #srch, 1) as Idx
union all
select substring(#srch, cte.Idx + 1, CHARINDEX(',', #srch, cte.Idx + 1) - cte.Idx - 1) as Phrase, CHARINDEX(',', #srch, cte.Idx + 1) as Idx
from cte
where cte.Idx < CHARINDEX(',', #srch, cte.Idx + 1)
)
select charindex(cte.Phrase, #str, 1) from cte
I don't think that the IN clause is what you need. Instead of this you can use the LIKE construction as following:
if (select count(*) from CommaSplit(#srch) where #str like '%' + val + '%') > 0
select 'true'
else
select 'false'
In this case you will receive 'true' when at least 1 result of CommaSplit function exists in the #str text. But in this case you also will receive a 'true' value when the result of CommaSplit function is a part of the word in the #str string.
If you need more accurate solution, this can be achieved by the following way: you need to split the #str into the words (also replacing punctuation by spaces beforehand). And, after this, intersect of CommaSplit (#srch) and SpaceSplit(#str) will be the answer on the question. Among this, you also will be able to check which words are matching between two strings.
The overhead of this method is to create function SpaceSplit which is copy of CommaSplit but with another separator. Or the function CommaSplit can be modified to receive a separator as parameter.
Related
I have a string like this:
Apple
I want to include a separator after each character so the end result will turn out like this:
A,p,p,l,e
In C#, we have one liner method to achieve the above with Regex.Replace('Apple', ".{1}", "$0,");
I can only think of looping each character with charindex to append the separator but seems a little complicated. Is there any elegant way and simpler way to achieve this?
Thanks HABO for the suggestions. I'm able to generate the result that I want using the code but takes a little bit of time to really understand how the code work.
After some searching, I manage to found one useful article to insert empty spaces between each character and it's easier for me to understand.
I modify the code a little to define and include desire separator instead of fixing it to space as the separator:
DECLARE #pos INT = 2 -- location where we want first space
DECLARE #result VARCHAR(100) = 'Apple'
DECLARE #separator nvarchar(5) = ','
WHILE #pos < LEN(#result)+1
BEGIN
SET #result = STUFF(#result, #pos, 0, #separator);
SET #pos = #pos+2;
END
select #result; -- Output: A,p,p,l,e
Reference
In following SQL scripts, I get each character using SUBSTRING() function using with a number table (basically I used spt_values view here for simplicity) and then I concatenate them via two different methods, you can choose one
If you are using SQL Server 2017, we have a new SQL string aggregation function
First script uses string_agg function
declare #str nvarchar(max) = 'Apple'
SELECT
string_agg( substring(#str,number,1) , ',') Within Group (Order By number)
FROM master..spt_values n
WHERE
Type = 'P' and
Number between 1 and len(#str)
If you are working with a previous version, you can use string concatenation using FOR XML Path and SQL Stuff function as follows
declare #str nvarchar(max) = 'Apple'
; with cte as (
SELECT
number,
substring(#str,number,1) as L
FROM master..spt_values n
WHERE
Type = 'P' and
Number between 1 and len(#str)
)
SELECT
STUFF(
(
SELECT
',' + L
FROM cte
order by number
FOR XML PATH('')
), 1, 1, ''
)
Both solution yields the same result, I hope it helps
If you have SQL Server 2017 and a copy of ngrams8k it's ultra simple:
declare #word varchar(100) = 'apple';
select newString = string_agg(token, ',') within group (order by position)
from dbo.ngrams8k(#word,1);
For pre-2017 systems it's almost as simple:
declare #word varchar(100) = 'apple';
select newstring =
( select token + case len(#word)+1-position when 1 then '' else ',' end
from dbo.ngrams8k(#word,1)
order by position
for xml path(''))
One ugly way to do it is to split the string into characters, ideally using a numbers table, and reassemble it with the desired separator.
A less efficient implementation uses recursion in a CTE to split the characters and insert the separator between pairs of characters as it goes:
declare #Sample as VarChar(20) = 'Apple';
declare #Separator as Char = ',';
with Characters as (
select 1 as Position, Substring( #Sample, 1, 1 ) as Character
union all
select Position + 1,
case when Position & 1 = 1 then #Separator else Substring( #Sample, Position / 2 + 1, 1 ) end
from Characters
where Position < 2 * Len( #Sample ) - 1 )
select Stuff( ( select Character + '' from Characters order by Position for XML Path( '' ) ), 1, 0, '' ) as Result;
You can replace the select Stuff... line with select * from Characters; to see what's going on.
Try this
declare #var varchar(50) ='Apple'
;WITH CTE
AS
(
SELECT
SeqNo = 1,
MyStr = #var,
OpStr = CAST('' AS VARCHAR(50))
UNION ALL
SELECT
SeqNo = SeqNo+1,
MyStr = MyStR,
OpStr = CAST(ISNULL(OpStr,'')+SUBSTRING(MyStR,SeqNo,1)+',' AS VARCHAR(50))
FROM CTE
WHERE SeqNo <= LEN(#var)
)
SELECT
OpStr = LEFT(OpStr,LEN(OpStr)-1)
FROM CTE
WHERE SeqNo = LEN(#Var)+1
I have this data in my column:
32-HC-100-10001-G03P2-N-1-1001
The problem is my value doesn't have a fixed length. What I need to do is split this value into 2 columns 32-HC-100-10001-G03P2-N and 1 - the numbers after last - don't important
Another example
4-G-100-10029-F23S-S-2-1001
should be split into 4-G-100-10029-F23S-S and 2. I have used SUBSTRING([Line No#], 0, 21) but because of the length it didn't work.
Try this way
declare #str varchar(100)=reverse('4-G-100-10029-F23S-S-2-1001')
select reverse(substring(#str,charindex('-',#str)+1,len(#str))) as first_col,
left(substring(#str,charindex('-',#str)+1,len(#str)),charindex('-',substring(#str,charindex('-',#str)+1,len(#str)))-1) as second_col
May not be the shortest method but should get the job done
Note : I did not hard-code any length here
As long as the last part(1-1001,2-2002...) have the same number of values,this will work..
declare #string varchar(max)
set #string='32-HC-100-10001-G03P2-N-1-1001'
select replace(#string, right(#string,7),''),substring(right(#string,6),1,1)
Output:
32-HC-100-10001-G03P2-N 1
When doing complex string operations in SQL Server, one method uses outer apply to simplify the calculations:
select t.col, s2.firstpart, s2.secondpart
from t outer apply
(select left(col, len(col) - charindex('-', reverse(col)) as s1
-- remove the last number
) s1 outer apply
(select left(s1, len(s1) - charindex('-', reverse(s1)) as firstpart,
right(s1, charindex('-', reverse(s1)) -1) as secondpart
) s2;
I find the calculations easier to construct, follow, and debug.
You can try this:
DECLARE #string nvarchar(max) = '32-HC-100-10001-G03P2-N-1-1001'
SELECT REVERSE(STUFF(SUBSTRING(REVERSE(#string),CHARINDEX('-',REVERSE(#string))+1,LEN(#string)),1,CHARINDEX('-',SUBSTRING(REVERSE(#string),CHARINDEX('-',REVERSE(#string))+1,LEN(#string))),'')),
REVERSE(LEFT(SUBSTRING(REVERSE(#string),CHARINDEX('-',REVERSE(#string))+1,LEN(#string)),CHARINDEX('-',SUBSTRING(REVERSE(#string),CHARINDEX('-',REVERSE(#string)),LEN(#string)))))
Output:
32-HC-100-10001-G03P2-N 1
If it is always comes as a 7th part you can use XML:
DECLARE #string nvarchar(max) = '32-HC-100-10001-G03P2-N-1-1001',
#xml xml
SELECT #xml = CAST('<d>'+REPLACE(#string,'-','</d><d>') +'</d>' as xml)
SELECT t.v.value('/d[1]','nvarchar(10)') + '-' +
t.v.value('/d[2]','nvarchar(10)') + '-' +
t.v.value('/d[3]','nvarchar(10)') + '-' +
t.v.value('/d[4]','nvarchar(10)') + '-' +
t.v.value('/d[5]','nvarchar(10)') + '-' +
t.v.value('/d[6]','nvarchar(10)'),
t.v.value('/d[7]','nvarchar(10)')
FROM #xml.nodes('/') as t(v)
Output:
32-HC-100-10001-G03P2-N 1
I have approximately 30,000 records where I need to split the Description field and so far I can only seem to achieve this in Excel. An example Description would be:
1USBCP 2RJ45C6 1DVI 1DP 3MD 3MLP HANDS
Below is my Excel function:
=TRIM(MID(SUBSTITUTE($G309," ",REPT(" ",LEN($G309))),((COLUMNS($G309:G309)-1)*LEN($G309))+1,LEN($G309)))
This is then dragged across ten Excel columns, and splits the description field at each space.
I have seen many questions asked about splitting a string in SQL but they only seem to cover one space, not multiple spaces.
There is no easy function in SQL server to split strings. At least I don't know it. I use usually some trick that I found somewhere in the Internet some time ago. I modified it to your example.
The trick is that first we try to figure out how many columns do we need. We can do it by checking how many empty strings we have in the string. The easiest way is lenght of string - lenght of string without empty string.
After that for each string we try to find start and end of each word by position. At the end we cut simply string by start and end position and assign to coulmns. The details are in the query. Have fun!
CREATE TABLE test(id int, data varchar(100))
INSERT INTO test VALUES (1,'1USBCP 2RJ45C6 1DVI 1DP 3MD 3MLP HANDS')
INSERT INTO test VALUES (2,'Shorter one')
DECLARE #pivot varchar(8000)
DECLARE #select varchar(8000)
SELECT
#pivot=coalesce(#pivot+',','')+'[col'+cast(number+1 as varchar(10))+']'
FROM
master..spt_values where type='p' and
number<=(SELECT max(len(data)-len(replace(data,',',''))) FROM test)
SELECT
#select='
select p.*
from (
select
id,substring(data, start+2, endPos-Start-2) as token,
''col''+cast(row_number() over(partition by id order by start) as varchar(10)) as n
from (
select
id, data, n as start, charindex('','',data,n+2) endPos
from (select number as n from master..spt_values where type=''p'') num
cross join
(
select
id, '' '' + data +'' '' as data
from
test
) m
where n < len(data)-1
and substring(odata,n+1,1) = '','') as data
) pvt
Pivot ( max(token)for n in ('+#pivot+'))p'
EXEC(#select)
Here you can find example in SQL Fiddle
I didn't notice that you want to get rid of multiple blank spaces.
To do it please create some function that preprare your data :
CREATE FUNCTION dbo.[fnRemoveExtraSpaces] (#Number AS varchar(1000))
Returns Varchar(1000)
As
Begin
Declare #n int -- Length of counter
Declare #old char(1)
Set #n = 1
--Begin Loop of field value
While #n <=Len (#Number)
BEGIN
If Substring(#Number, #n, 1) = ' ' AND #old = ' '
BEGIN
Select #Number = Stuff( #Number , #n , 1 , '' )
END
Else
BEGIN
SET #old = Substring(#Number, #n, 1)
Set #n = #n + 1
END
END
Return #number
END
After that use the new version that removes extra spaces.
DECLARE #pivot varchar(8000)
DECLARE #select varchar(8000)
SELECT
#pivot=coalesce(#pivot+',','')+'[col'+cast(number+1 as varchar(10))+']'
FROM
master..spt_values where type='p' and
number<=(SELECT max(len(dbo.fnRemoveExtraSpaces(data))-len(replace(dbo.fnRemoveExtraSpaces(data),' ',''))) FROM test)
SELECT
#select='
select p.*
from (
select
id,substring(data, start+2, endPos-Start-2) as token,
''col''+cast(row_number() over(partition by id order by start) as varchar(10)) as n
from (
select
id, data, n as start, charindex('' '',data,n+2) endPos
from (select number as n from master..spt_values where type=''p'') num
cross join
(
select
id, '' '' + dbo.fnRemoveExtraSpaces(data) +'' '' as data
from
test
) m
where n < len(data)-1
and substring(data,n+1,1) = '' '') as data
) pvt
Pivot ( max(token)for n in ('+#pivot+'))p'
EXEC(#select)
I am probably not understanding your question, but all that you are doing in that formula, can be done almost exactly the same in SQL. I see someone has already answered but to my mind, how can it be necessary to do all that when you can do this. I might be wrong. But here goes.
declare #test as varchar(100)
set #test='abcd1234567'
select right(#test,2)
, left(#test,2)
, len(#test)
, case when len(#test)%2>0
then left(right(#test,round(len(#test)/2,0)+1),1)
else left(right(#test,round(len(#test)/2,0)+1),2) end
Results
67 ab 11 2
So right, left, length and mid can all be achieved.
If the spaces are the "substring" dividers, then: I dont remember well the actual syntax for do-while inside selects of sql, neither have i actually done that per se, but I don't see why it should not be possible. If it doesn't work then you need a temporary table and if that does not work you need a cursor. The cursor would be an external loop around this one to fetch and process a single string at a time. Or you can do something more clever. I am just a novice.
declare #x varchar(1)
declare #n integer
declare #i integer
declare #str varchar(100) -- this is your description. Fetch it and assign it. if in a cursor just use column-name
set #x = null
set #n = 0
set #i = 0
while n < len(#str)
while NOT #x = " "
begin
set #x = left(right(#str,n),1)
n = n+1
end
--insert into or update #temptable blablabla here.
Use i and n to locate substring and then left(right()) it out. or you can SELECT it, but that is a messy procedure if the number of substrings are long. Continue with:
set i = n
set #str = right(#str, i) -- this includes the " ". left() it out at will.
end
Now, a final comment, there should perhaps be a third loop checking for if you are at the last "substring" because I see now this code will throw error when it gets to the end. or "add" an empty space at the end to #str, that will also work. But my time is up. This is a suggestion at least.
trying to do a small stored procedure without needing to add freetext indexing just for this (SQL Server 2008)
Basically, I want to find all records where a certain field contains all the words from a parameter.
So if in the field I have "This is a test field", and the parameter to my SP would be "this test field" it would return it, as it would if the parameter was "field this test".
The table is very small (4000) record and load will be low, so efficiency is not a big deal. Right now the only solution i can think of is to split both strings with table valued function and go from there.
Any simpler idea?
Thanks!
If efficency is not a big problem, why not go with a bit of dynamic SQL. Something like:
create procedure myproc (#var varchar(100))
as
set #var = '%' + replace(#var, ' ', '%') + '%'
exec ('select * from mytable where myfield like '''+ #var + '''')
Here is a solution using recursive CTEs. This actually uses two separate recursions. The first one splits the strings into tokens and the second one recursively filters the records using each token.
declare
#searchString varchar(max),
#delimiter char;
select
#searchString = 'This is a test field'
,#delimiter = ' '
declare #tokens table(pos int, string varchar(max))
;WITH Tokens(pos, start, stop) AS (
SELECT 1, 1, CONVERT(int, CHARINDEX(#delimiter, #searchString))
UNION ALL
SELECT pos + 1, stop + 1, CONVERT(int, CHARINDEX(#delimiter, #searchString, stop + 1))
FROM Tokens
WHERE stop > 0
)
INSERT INTO #tokens
SELECT pos,
SUBSTRING(#searchString, start, CASE WHEN stop > 0 THEN stop-start ELSE 512 END) AS string
FROM Tokens
OPTION (MAXRECURSION 25000) ;
;with filter(ind, myfield) as (
select 1,myfield from mytable where myfield like '%'+(select string from #tokens where pos = 1)+'%'
union all
select ind + 1, myfield from filter where myfield like '%'+(select string from #tokens where pos = ind + 1)+'%'
)
select * from filter where ind = (select COUNT(1) from #tokens)
This took me about 15 seconds to search a table of 10k records for the search string 'this is a test field'.. (the more words in the string, the longer it takes.. )
Edit
If you want a fuzzy search i.e return closely matching results even if there wasnt an exact match, you could modify the last line in the query to be -
select * from (select max(ind) as ind, myfield from filter group by myfield) t order by ind desc
'ind' would give you the number of words from the search string found in myfield.
Does anyone have in their back pocket a function that can achieve this?
Found this here :-
create function ProperCase(#Text as varchar(8000))
returns varchar(8000)
as
begin
declare #Reset bit;
declare #Ret varchar(8000);
declare #i int;
declare #c char(1);
select #Reset = 1, #i=1, #Ret = '';
while (#i <= len(#Text))
select #c= substring(#Text,#i,1),
#Ret = #Ret + case when #Reset=1 then UPPER(#c) else LOWER(#c) end,
#Reset = case when #c like '[a-zA-Z]' then 0 else 1 end,
#i = #i +1
return #Ret
end
Results from this:-
select dbo.propercase('ALL UPPERCASE'); -- All Uppercase
select dbo.propercase('MiXeD CaSe'); -- Mixed Case
select dbo.propercase('lower case'); -- Lower Case
select dbo.propercase('names with apostrophe - mr o''reilly '); -- Names With Apostrophe - Mr O'Reilly
select dbo.propercase('names with hyphen - mary two-barrels '); -- Names With Hyphen - Mary Two-Barrels
I'd do this outside of TSQL, in the calling code tbh.
e.g. if you're using .NET, it's just a case of using TextInfo.ToTitleCase.
That way, you leave your formatting code outside of TSQL (standard "let the caller decide how to use/format the data" approach).
This kind of function is better done on the application side, as it will perform relatively poorly in SQL.
With SQL-Server 2005 and above you could write a CLR function that does that and call it from your SQL. Here is an article on how to do this.
If you really want to do this in T-SQL and without a loop, see Tony Rogerson's article "Turning stuff into "Camel Case" without loops"
I haven't tried it... that's what client code it for :-)
No cursors, no while loops, no (inline) sub-queries
-- ===== IF YOU DON'T HAVE A NUMBERS TABLE =================
--CREATE TABLE Numbers (
-- Num INT NOT NULL PRIMARY KEY CLUSTERED WITH(FILLFACTOR = 100)
--)
--INSERT INTO Numbers
--SELECT TOP(11000)
-- ROW_NUMBER() OVER (ORDER BY (SELECT 1))
--FROM master.sys.all_columns a
-- CROSS JOIN master.sys.all_columns b
DECLARE #text VARCHAR(8000) = 'my text to make title-case';
DECLARE #result VARCHAR(8000);
SET #result = UPPER(LEFT(#text, 1));
SELECT
#result +=
CASE
WHEN SUBSTRING(#text, Num - 1, 1) IN (' ', '-') THEN UPPER(SUBSTRING(#text, Num, 1))
ELSE SUBSTRING(#text, Num, 1)
END
FROM Numbers
WHERE Num > 1 AND Num <= LEN(#text);
PRINT #result;
Will any given row only contain a firstname or a lastname that you wish to convert or will it contain full names separated by spaces? Also, are there any other rules you wish to what characters it should "upper" or lower"?
If you can guarantee that it's only first and last names and you aren't dealing with any specialized capitalization such as after an apostrophe, might this do what you're looking for?
SELECT -- Initial of First Name
UPPER(LEFT(FullName, 1))
-- Rest of First Name
+ SUBSTRING(LOWER(FullName), 2, CHARINDEX(' ', FullName, 0) - 2)
-- Space between names
+ ' '
-- Inital of last name
+ UPPER(SUBSTRING(FullName, CHARINDEX(' ', FullName, 0) + 1, 1))
-- Rest of last name
+ SUBSTRING(LOWER(FullName), CHARINDEX(' ', FullName, 0) + 2, LEN(FullName) - CHARINDEX(' ', FullName, 0) + 2)
FROM Employee