SQL: IF clause within WHERE clause - sql

Is it possible to use an IF clause within a WHERE clause in MS SQL?
Example:
WHERE
IF IsNumeric(#OrderNumber) = 1
OrderNumber = #OrderNumber
ELSE
OrderNumber LIKE '%' + #OrderNumber + '%'

Use a CASE statement
UPDATE: The previous syntax (as pointed out by a few people) doesn't work. You can use CASE as follows:
WHERE OrderNumber LIKE
CASE WHEN IsNumeric(#OrderNumber) = 1 THEN
#OrderNumber
ELSE
'%' + #OrderNumber
END
Or you can use an IF statement like #N. J. Reed points out.

You should be able to do this without any IF or CASE
WHERE
(IsNumeric(#OrderNumber) AND
(CAST OrderNumber AS VARCHAR) = (CAST #OrderNumber AS VARCHAR)
OR
(NOT IsNumeric(#OrderNumber) AND
OrderNumber LIKE ('%' + #OrderNumber))
Depending on the flavour of SQL you may need to tweak the casts on the order number to an INT or VARCHAR depending on whether implicit casts are supported.
This is a very common technique in a WHERE clause. If you want to apply some "IF" logic in the WHERE clause all you need to do is add the extra condition with an boolean AND to the section where it needs to be applied.

You don't need a IF statement at all.
WHERE
(IsNumeric(#OrderNumber) = 1 AND OrderNumber = #OrderNumber)
OR (IsNumeric(#OrderNumber) = 0 AND OrderNumber LIKE '%' + #OrderNumber + '%')

There isn't a good way to do this in SQL. Some approaches I have seen:
1) Use CASE combined with boolean operators:
WHERE
OrderNumber = CASE
WHEN (IsNumeric(#OrderNumber) = 1)
THEN CONVERT(INT, #OrderNumber)
ELSE -9999 -- Some numeric value that just cannot exist in the column
END
OR
FirstName LIKE CASE
WHEN (IsNumeric(#OrderNumber) = 0)
THEN '%' + #OrderNumber
ELSE ''
END
2) Use IF's outside the SELECT
IF (IsNumeric(#OrderNumber)) = 1
BEGIN
SELECT * FROM Table
WHERE #OrderNumber = OrderNumber
END ELSE BEGIN
SELECT * FROM Table
WHERE OrderNumber LIKE '%' + #OrderNumber
END
3) Using a long string, compose your SQL statement conditionally, and then use EXEC
The 3rd approach is hideous, but it's almost the only think that works if you have a number of variable conditions like that.

Use a CASE statement instead of IF.

You want the CASE statement
WHERE OrderNumber LIKE
CASE WHEN IsNumeric(#OrderNumber)=1 THEN #OrderNumber ELSE '%' + #OrderNumber END

To clarify some of the logical equivalence solutions.
An if statement
if (a) then b
is logically equivalent to
(!a || b)
It's the first line on the Logical equivalences involving conditional statements section of the Logical equivalence wikipedia article.
To include the else, all you would do is add another conditional
if(a) then b;
if(!a) then c;
which is logically equivalent to
(!a || b) && (a || c)
So using the OP as an example:
IF IsNumeric(#OrderNumber) = 1
OrderNumber = #OrderNumber
ELSE
OrderNumber LIKE '%' + #OrderNumber + '%'
the logical equivalent would be:
(IsNumeric(#OrderNumber) <> 1 OR OrderNumber = #OrderNumber)
AND (IsNumeric(#OrderNumber) = 1 OR OrderNumber LIKE '%' + #OrderNumber + '%' )

I think that where...like/=...case...then... can work with Booleans. I am using T-SQL.
Scenario: Let's say you want to get Person-30's hobbies if bool is false, and Person-42's hobbies if bool is true. (According to some, hobby-lookups comprise over 90% of business computation cycles, so pay close attn.).
CREATE PROCEDURE sp_Case
#bool bit
AS
SELECT Person.Hobbies
FROM Person
WHERE Person.ID =
case #bool
when 0
then 30
when 1
then 42
end;

// an example for using a stored procedure to select users filtered by country and site
CREATE STORED PROCEDURE GetUsers
#CountryId int = null,
#SiteId int = null
AS
BEGIN
SELECT *
FROM Users
WHERE
CountryId = CASE WHEN ISNUMERIC(#CountryId) = 1 THEN #CountryId ELSE CountryId END AND
SiteId = CASE WHEN ISNUMERIC(#SiteId) = 1 THEN #SiteId ELSE SiteId END END
// take from the input countryId AND/OR siteId if exists else don't filter

WHERE (IsNumeric(#OrderNumber) <> 1 OR OrderNumber = #OrderNumber)
AND (IsNumber(#OrderNumber) = 1 OR OrderNumber LIKE '%'
+ #OrderNumber + '%')

CASE Statement is better option than IF always.
WHERE vfl.CreatedDate >= CASE WHEN #FromDate IS NULL THEN vfl.CreatedDate ELSE #FromDate END
AND vfl.CreatedDate<=CASE WHEN #ToDate IS NULL THEN vfl.CreatedDate ELSE #ToDate END

WHERE OrderNumber LIKE CASE WHEN IsNumeric(#OrderNumber) = 1 THEN #OrderNumber ELSE '%' + #OrderNumber END
In line case Condition will work properly.

In sql server I had same problem I wanted to use an and statement only if parameter is false and on true I had to show both values true and false so I used it this way
(T.IsPublic = #ShowPublic or #ShowPublic = 1)

The following example executes a query as part of the Boolean expression and then executes slightly different statement blocks based on the result of the Boolean expression. Each statement block starts with BEGIN and completes with END.
USE AdventureWorks2012;
GO
DECLARE #AvgWeight decimal(8,2), #BikeCount int
IF
(SELECT COUNT(*) FROM Production.Product WHERE Name LIKE 'Touring-3000%' ) > 5
BEGIN
SET #BikeCount =
(SELECT COUNT(*)
FROM Production.Product
WHERE Name LIKE 'Touring-3000%');
SET #AvgWeight =
(SELECT AVG(Weight)
FROM Production.Product
WHERE Name LIKE 'Touring-3000%');
PRINT 'There are ' + CAST(#BikeCount AS varchar(3)) + ' Touring-3000 bikes.'
PRINT 'The average weight of the top 5 Touring-3000 bikes is ' + CAST(#AvgWeight AS varchar(8)) + '.';
END
ELSE
BEGIN
SET #AvgWeight =
(SELECT AVG(Weight)
FROM Production.Product
WHERE Name LIKE 'Touring-3000%' );
PRINT 'Average weight of the Touring-3000 bikes is ' + CAST(#AvgWeight AS varchar(8)) + '.' ;
END ;
GO
Using nested IF...ELSE statements
The following example shows how an IF … ELSE statement can be nested inside another. Set the #Number variable to 5, 50, and 500 to test each statement.
DECLARE #Number int
SET #Number = 50
IF #Number > 100
PRINT 'The number is large.'
ELSE
BEGIN
IF #Number < 10
PRINT 'The number is small'
ELSE
PRINT 'The number is medium'
END ;
GO

If #LstTransDt is Null
begin
Set #OpenQty=0
end
else
begin
Select #OpenQty=IsNull(Sum(ClosingQty),0)
From ProductAndDepotWiseMonitoring
Where Pcd=#PCd And PtpCd=#PTpCd And TransDt=#LstTransDt
end
See if this helps.

USE AdventureWorks2012;
GO
IF
(SELECT COUNT(*) FROM Production.Product WHERE Name LIKE 'Touring-3000%' ) > 5
PRINT 'There are more than 5 Touring-3000 bicycles.'
ELSE PRINT 'There are 5 or less Touring-3000 bicycles.' ;
GO

Related

How to optional "AND" or "OR" between conditions of where in stored procedure with parameter?

I have a stored procedure to return results filtered by some parameter (all parameters are optional).
Sometimes I want to get results where condition1 AND condition2 are true, and sometimes when only one of conditions is true.
SELECT
*
FROM
ProductAndServices
WHERE
Title = ISNULL(#Title,Title) #AndOR
CreatedBy = ISNULL(#CreatedBy,CreatedBy)
You could introduce a variable/parameter that specifies if you want to use AND or OR logic with your fields and incorporate that parameter in your WHERE-clause as well.
Something like this, for example:
-- Declared a variable here, but might be a stored procedure parameter as well
DECLARE #And BIT = 1 -- When 0, WHERE uses OR; when 1, WHERE uses AND
SELECT
*
FROM
ProductAndServices
WHERE
(#And = 0 AND (Title = ISNULL(#Title, Title) OR CreatedBy = ISNULL(#CreatedBy, CreatedBy))) OR
(#And = 1 AND (Title = ISNULL(#Title, Title) AND CreatedBy = ISNULL(#CreatedBy, CreatedBy)))
OPTION (RECOMPILE)
Furthermore, I would avoid using functions like ISNULL in the WHERE-clause, because it might prevent the query optimizer to use indexes.
So instead of this:
Title = ISNULL(#Title, Title)
CreatedBy = ISNULL(#CreatedBy, CreatedBy)
I would use:
(#Title IS NULL OR Title = #Title)
(#CreatedBy IS NULL OR CreatedBy = #CreatedBy)
With this, the query would become:
SELECT
*
FROM
ProductAndServices
WHERE
(#And = 0 AND ((#Title IS NULL OR Title = #Title) OR (#CreatedBy IS NULL OR CreatedBy = #CreatedBy))) OR
(#And = 1 AND ((#Title IS NULL OR Title = #Title) AND (#CreatedBy IS NULL OR CreatedBy = #CreatedBy)))
OPTION (RECOMPILE)
So this latter query is longer and not as readable as the former query, but it should perform better. I think. I have not tested it. You might want to benchmark it with several datasets (and check out the corresponding execution plans) to be sure.
Edit:
Based on a tip by Marc Guillot, I added OPTION (RECOMPILE) to the query. You could check out the Query Hints documentation in Microsoft Docs for more info about it.
I also found the article Improving query performance with OPTION (RECOMPILE), Constant Folding and avoiding Parameter Sniffing issues by Robin Lester just now. Haven't read it completely yet, but after scanning it quickly, it looks like a good read to me.
You can try building dynamic T-SQL statement in your routine. It is a little more difficult to write and debug, but it will lead to more simple T-SQL statements being execute and from there - possibility for better performance. Here is an example:
DECLARE #Tittle VARCHAR(12)
,#CreatedBy VARCHAR(12)
,#AndOR VARCHAR(12)
SELECT #Tittle = 'Here comes the sun'
,#CreatedBy = 'Beatles'
,#AndOR = 'AND';
SELECT #Tittle = ISNULL(#Tittle, '')
,#CreatedBy = ISNULL(#CreatedBy, '')
,#AndOR = ISNULL(#AndOR, 'AND');
DECLARE #DynammicTSQLStatement NVARCHAR(MAX);
SET #DynammicTSQLStatement =N'
SELECT
*
FROM
ProductAndServices
WHERE ' + CASE WHEN #Tittle = '' THEN '' ELSE 'Title = ''' + #Tittle + '''' END
+ CASE WHEN #Tittle <> '' AND #CreatedBy <> '' THEN ' ' + #AndOR + ' CreatedBy = ''' + #CreatedBy + '''' ELSE '' END;
SELECT #DynammicTSQLStatement
EXEC sp_executesql #DynammicTSQLStatement;
Use if condition for your case:
GO
if (#Condtion)
Begin
SELECT * FROM ProductAndServices
WHERE
Title = ISNULL(#Title,Title) And
CreatedBy = ISNULL(#CreatedBy,CreatedBy)
End
Else
Begin
SELECT * FROM ProductAndServices
WHERE
Title = ISNULL(#Title,Title) OR
CreatedBy = ISNULL(#CreatedBy,CreatedBy)
End
I am sure you know your sometimes when you want to use AND and when to use OR.
The following query should do what you want,
SELECT *
FROM ProductAndServices
WHERE
(#Title IS NULL OR Title = #Title) AND
(#CreatedBy IS NULL OR CreatedBy = #CreatedBy)
Write as SQL Statement like below
DECLARE #str VARCHAR(max)=N'SELECT *
FROM ProductAndServices
WHERE Title='+ CASE WHEN #Tittle IS NULL THEN 'Title' ELSE '''' + #Tittle + '''' END
+ ' AND CreatedBy='+ CASE WHEN #CreatedBy IS NULL THEN 'CreatedBy' ELSE '''' + #CreatedBy + '''' END
PRINT #str;

Using different set of WHERE clauses in stored procedure depending on Parameter value

I have 2 stored procedures which return the same columns that I am trying to merge into a single procedure. They both have a different set of parameters and both have different WHERE clauses, but they use the same tables and select the exact same rows.
WHERE clause 1: (uses #UIOID, and #Level)
WHERE ( #UIOID = CASE WHEN #Level = 'Single' THEN C.C_UIOID_PK
WHEN #Level = 'Children' THEN CLC.UIOL_P
WHEN #Level = 'Parent' THEN CLP.UIOL_C
END
OR ( #UIOID = '0'
AND #Level = 'All'
)
)
Where clause 2: (Uses #TeamCode, #Year, #IncludeQCodes)
WHERE C.C_IsChild = 0
AND C.C_MOA <> 'ADD'
AND #TeamCode = C.C_OffOrg
AND C.C_Active = 'Y'
AND ( #Year BETWEEN dbo.f_GetAcYearByDate(C.C_StartDate) AND dbo.f_GetAcYearByDate(C.C_EndDate)
OR #Year = 0 )
AND ( C.C_InstCode NOT LIKE 'Q%'
OR #IncludeQCodes = 1 )
Ideally I want to add a new parameter which basically tells it which of the two WHERE clauses to run, but I can't seem to recreate that with CASE statement because as far as I can tell, they only work for a single WHERE clause, not a whole set of different clauses
I want to do this without having to repeat the select statement again and putting the whole thing in IF statements, and i don't want to put the query into a string either. I just want one select statement ideally.
The problem with using temp tables is the query itself takes a while to run without any parameters and is used in a live website, so I don't want it to have to put all records in a temp table and then filter it.
The problem with using a CTE is you can't follow it with an IF statement, so that wouldn't work either.
Here is the sort of logic I am trying to achieve:
SELECT A
B
C
FROM X
IF #WhichOption = 1 THEN
WHERE ( #UIOID = CASE WHEN #Level = 'Single' THEN C.C_UIOID_PK
WHEN #Level = 'Children' THEN CLC.UIOL_P
WHEN #Level = 'Parent' THEN CLP.UIOL_C
END
OR ( #UIOID = '0'
AND #Level = 'All'
)
)
ELSE IF #WhichOption = 2 THEN
WHERE C.C_IsChild = 0
AND C.C_MOA <> 'ADD'
AND #TeamCode = C.C_OffOrg
AND C.C_Active = 'Y'
AND ( #Year BETWEEN dbo.f_GetAcYearByDate(C.C_StartDate) AND dbo.f_GetAcYearByDate(C.C_EndDate)
OR #Year = 0 )
AND ( C.C_InstCode NOT LIKE 'Q%'
OR #IncludeQCodes = 1 )
Save the following process in a procedure. You can also directly insert into a physical table.
declare #varTable Table (columns exactly as Procedures return)
if(condition is met)
begin
insert into #varTable
exec proc1
end
else
begin
insert into #varTable
exec proc2
end
Add the parameter that you said that it would indicate what filter apply :
select XXXXX
from XXXXX
where (#Mode = 1 and ( filter 1 ))
or
(#Mode = 2 and ( filter 2 ))
option(recompile)
If the #Mode parameter is 1 then it will evaluate the filter 1, otherwise it will evaluate the filter 2.
Add an option(recompile) at the end of the statement, so the SQL engine will replace the variables with their values, eliminate the filter that won't be evaluated, and generate an execution plant for just the filter that you want to apply.
PS: Please notice that although these catchall queries are very easy to code and maintain, and generate a perfectly functional and optimal execution, they are not advised for high-demand applications. The option(recompile) forces the engine to recompile and generate a new execution plan at every execution and that would have a noticeable effect on performance if your query needs to be executed hundreds of times per minute. But for the occasional use it's perfectly fine.
Try to use dynamic SQL:
DECLARE #sql NVARCHAR(max), #where NVARCHAR(max), #WhichOption INT = 1;
SET #sql = 'SELECT A
B
C
FROM X';
IF #WhichOption = 1
SET #where = 'WHERE ( #UIOID = CASE WHEN #Level = ''Single'' THEN C.C_UIOID_PK
WHEN #Level = ''Children'' THEN CLC.UIOL_P
WHEN #Level = ''Parent'' THEN CLP.UIOL_C
END
OR ( #UIOID = ''0''
AND #Level = ''All''
)
)';
ELSE IF #WhichOption = 2
SET #where = ' WHERE C.C_IsChild = 0
AND C.C_MOA <> ''ADD''
AND #TeamCode = C.C_OffOrg
AND C.C_Active = ''Y''
AND ( #Year BETWEEN dbo.f_GetAcYearByDate(C.C_StartDate)
AND dbo.f_GetAcYearByDate(C.C_EndDate)
OR #Year = 0 )
AND ( C.C_InstCode NOT LIKE ''Q%''
OR #IncludeQCodes = 1 ) ';
SET #sql = CONCAT(#sql,' ', #where)
PRINT #sql
EXECUTE sp_executesql #sql

Using SQL Server CASE statement in WHERE

I want to select records from a table in a stored procedure. Given parameters can be empty or a string including some keys separated by comma (1, 2, etc)
I want to manage that when a parameter is an empty string, "WHERE" ignore searching.
I'm using this code:
where (CASE when #PatientID <> 0 then ( dental.ID_Sick in (1,2)) else (1=1) end)
Something like that is working in W3School. I mean:
SELECT * FROM Customers
WHERE (case when 1=1 then (Country IN ('Germany', 'France', 'UK')) else 1=1 end);
What is the problem in my query that does not work? SQLServerManagementStudio is giving error on "IN" statement.
Solution:
The best way to handle such optional parameters is to use dynamic SQL and built the query on the fly. Something like....
CREATE PROCEDURE myProc
#Param1 VARCHAR(100) = NULL
,#Param2 VARCHAR(100) = NULL
,#Param3 VARCHAR(100) = NULL
,#ListParam VARCHAR(100) = NULL
--, etc etc...
AS
BEGIN
SET NOCOUNT ON;
Declare #Sql NVARCHAR(MAX);
SET #Sql = N' SELECT *
FROM TableName
WHERE 1 = 1 '
-- add in where clause only if a value was passed to parameter
+ CASE WHEN #Param1 IS NOT NULL THEN
N' AND SomeColumn = #Param1 ' ELSE N'' END
-- add in where clause a different variable
-- only if a value was passed to different parameter
+ CASE WHEN #Param2 IS NOT NULL THEN
N' AND SomeOtherColumn = #Param3 ' ELSE N'' END
-- List Parameter used with IN clause if a value is passed
+ CASE WHEN #ListParam IS NOT NULL THEN
N' AND SomeOtherColumn IN (
SELECT Split.a.value(''.'', ''VARCHAR(100)'') IDs
FROM (
SELECT Cast (''<X>''
+ Replace(#ListParam, '','', ''</X><X>'')
+ ''</X>'' AS XML) AS Data
) AS t CROSS APPLY Data.nodes (''/X'') AS Split(a) '
ELSE N'' END
Exec sp_executesql #sql
, N' #Param1 VARCHAR(100), #Param2 VARCHAR(100) ,#Param3 VARCHAR(100) ,#ListParam VARCHAR(100)'
, #Param1
, #Param2
,#Param3
, #ListParam
END
Problem with Other approach
There is a major issue with this other approach, you write your where clause something like...
WHERE ( ColumnName = #Parameter OR #Parameter IS NULL)
The Two major issues with this approach
1) you cannot force SQL Server to check evaluate an expression first like if #Parameter IS NULL, Sql Server might decide to evaluate first the expression ColumnName = #Parameterso you will have where clause being evaluated even if the variable value is null.
2) SQL Server does not do Short-Circuiting (Like C#), even if it decides to check the #Parameter IS NULL expression first and even if it evaluates to true, SQL Server still may go ahead and evaluating other expression in OR clause.
Therefore stick to Dynamic Sql for queries like this. and happy days.
SQL Server does not have a Bool datatype, so you can't assign or return the result of a comparison as a Bool as you would in other languages. A comparison can only be used with IF-statements or WHERE-clauses, or in the WHEN-part of a CASE...WHEN but not anywhere else.
Your specific example would become this:
SELECT * FROM Customers
WHERE 1=1 OR Country IN ('Germany', 'France', 'UK')
It would be better readable to rewrite your statement as follows:
WHERE #PatientID = 0
OR dental.ID_Sick in (1,2)
Referring to your actual question, I'd advise to read the linked question as provided by B House.
May be this straight way will work for you
IF (#PatientID <> 0)
BEGIN
SELECT * FROM Customers
WHERE Country IN ('Germany', 'France', 'UK')
END
try this:
WHERE 1=(CASE WHEN #PatientID <>0 AND dental.ID_Sick in (1,2) THEN 1
WHEN #PatientID =0 THEN 1
ELSE 0
END)

Matching strings in TSQL

I have a table that has some columns containing strings ,Let's say nvarchar. Now, the user passes a string to a function that searches for this string in its assigned column. I want to check if that string is present in the database but the problem is it does not necessarily have to be a 100% match.
Let's say for example:
The user passed the string Johnathon and string John is present in this database.
So, basically I want to get the number of characters that matched.In this particular case of John and Johnathon. it should be 4 matched and 5 unmatched.
Can I please get some directions to approach this problem?
Edit: What I am guessing is I can do the percentage match thing once I have retrieved the best matching string from the column. So, likewise, if we ignore the number of matched and unmatched characters and focus on retrieving the matched string from database, that should work.
Forexample, as Johnathon was passed by the user, and John is present in the database, I definitely can not use Like operator here but a piece of code that searches for the most matched string in the column and returns it.
You can do it this way:
SELECT Name, LEN(Name) AS Equals, (LEN('Johnathon') - LEN(Name)) AS NotEquals
FROM TableName
WHERE 'Johnathon' LIKE '%' +Name +'%'
Or if you want to compare both ways then:
DECLARE #parameter NVARCHAR(MAX) = N'Johnathon'
SELECT Name,
CASE WHEN LEN(Name) > LEN(#parameter) THEN LEN(#parameter) ELSE LEN(Name) END AS Equals,
CASE WHEN LEN(Name) > LEN(#parameter) THEN LEN(Name) - LEN(#parameter) ELSE LEN(#parameter) - LEN(Name) END AS NotEquals
FROM TableName
WHERE Name LIKE '%' + #parameter + '%' OR #parameter LIKE '%' +Name +'%'
The Levenshtein distance mentioned by #DeadlyJesus might suit you, but an alternative would be just to count matching characaters from the start of the 2 strings. A simple user defined function could do this.
create function dbo.MatchStart(#input1 nvarchar(100), #input2 nvarchar(100)) returns int as
begin
declare #i int
set #i = 1
if (#input1 is not null and #input2 is not null)
begin
while (1 = 1)
begin
if (#i > len(#input1) or #i > len(#input2))
break
if (substring(#input1, #i, 1) <> substring(#input2, #i, 1))
break;
set #i = #i + 1
end
end
return #i - 1
end
go
declare #testTable table (text1 nvarchar(100))
declare #userInput nvarchar(100)
insert #testTable values
(null),
(''),
('John'),
('Johnathan'),
('JohXXX'),
('Fred'),
('JxOxHxN')
set #userInput = 'Johnathan'
select text1, dbo.MatchStart(text1, #userInput) as result from #testTable
You can try this approach:-
IF EXISTS(SELECT * FROM TAB_NAME WHERE COL LIKE '%JOHN%')
SELECT LEN('JOHN') AS MATCHED, (LEN(COL) - LEN('JOHN')) AS UNMATCHED
FROM TAB_NAME;
I think this approach can solve your problem.

Ranking with LIKE keyword in SQL Server

I have table like
╔═══════════════════╗
║ Title ║
╠═══════════════════╣
║ Blogroll ║
║ Bottom Menu ║
║ Business ║
║ Entertainment ║
║ extend ║
╚═══════════════════╝
and my search criteria is like
WHERE title LIKE '%blogroller%'
obviously I will have no result here but can I find the count where the like clause ended, like here in this case its beyond 8 that made like clause fail??
Any hint would be appreciated.
Thank you
You can do this but only with lots of manual effort:
select title,
(case when title like '%blogroller%' then 10
when title like '%blogrolle%' then 9
when title like '%blogroll%' then 8
. . .
else 0
end) as MatchLen
from table t
order by MatchLen desc;
(Note: in some versions of SQL Server, you might need a subquery to refer to MatchLen.)
You don't have ability to do it at once, so you have to try all options one by one, but you can significantly optimize this process - that's what SQL for.
First, convert pattern into all possible patterns, then just find MAX from all possible LIKEs. Not sure if SQL will be able to really optimize it now, but maybe in future it will.
-- test data
DECLARE #token NVARCHAR(100)
SET #token = 'Blogroller'
DECLARE #titles TABLE (Title NVARCHAR(100))
INSERT #titles VALUES
('Blogroll'),
('Bottom Menu'),
('Business'),
('Entertainment'),
('extend')
-- solution
DECLARE #patterns TABLE (part NVARCHAR(100) PRIMARY KEY, tokenLen int)
WHILE (LEN(#token) > 0)
BEGIN
INSERT #patterns VALUES (#token, LEN(#token))
SET #token = SUBSTRING(#token, 1, LEN(#token) - 1)
END
SELECT MAX(patterns.tokenLen)
FROM #titles titles
INNER JOIN #patterns patterns ON titles.Title LIKE '%' + patterns.part + '%'
If you don't mind declaring a couple of variables and running a simple loop, you can do it this way:
declare #phrase as varchar(100) = 'blogroller'
declare #match_length as int = 0
while len(#phrase) > 0
begin
if (select count(title) from titles where title like '%' + #phrase + '%') > 0
begin
set #match_length = len(#phrase)
break
end
set #phrase = LEFT(#phrase, len(#phrase) - 1)
end
print #match_length
You can use some table with numbers (master..spt_values used in example) to break your search term into all possible shorter terms, and then join on it to find how many characters have matched:
setup:
CREATE TABLE Something (Title NVARCHAR(MAX));
INSERT Something (Title)
VALUES ('Blogroll')
,('Bottom Menu')
,('Business')
,('Entertainment')
,('extend')
query:
DECLARE #SearchTerm NVARCHAR(MAX);
SET #SearchTerm = 'Blogroller';
WITH CTE_SearchBroken AS
(
SELECT LEFT(#SearchTerm, number) BrokenTerm
FROM master..spt_values n
WHERE n.type = 'P' AND n.number <= LEN(#SearchTerm)
)
, CTE_PreliminaryResults AS
(
SELECT *, LEN(BrokenTerm) AS BrokenAt
FROM Something s
LEFT JOIN CTE_SearchBroken b ON s.Title LIKE '%' + b.BrokenTerm + '%'
)
SELECT Title
, MAX(BrokenAt) AS BrokenAt
, CASE WHEN LEN(#SearchTerm) = MAX(BrokenAt) THEN 1 ELSE 0 END AS Found
FROM CTE_PreliminaryResults
GROUP BY Title
Based on Gordon's answer, you can write a function that automatically loops through all the possibilities. Also similar to Lanorkin's answer; his might have better performance because it makes good use of inner join; you may want to do some tests there.
CREATE FUNCTION dbo.MatchLen(#needle AS varchar(max), #haystack AS varchar(max)) RETURNS int
AS
BEGIN
DECLARE #len AS int = LEN(#needle)
WHILE #len > 0 AND #haystack NOT LIKE '%' + REPLACE(REPLACE(REPLACE(LEFT(#needle, #len), '[', '[[]'), '%', '[%]'), '_', '[_]') + '%'
SET #len = #len - 1
RETURN #len
END
Example:
SELECT dbo.MatchLen('blogroller', 'This blog has no blogroll, sir!')
returns 8.
Please try:
select
*,
LEN(Title) MatchLength
From YourTable
where 'Blogroller' like Title+'%'
OR
select
*,
LEN(Title) MatchLength
From YourTable
where 'Blogroller' like '%'+Title+'%'