CASE WHEN in WHERE with LIKE condition instead of 1 - sql

I have a query with a bunch of OR's inside an AND in the where clause and I'm trying to replace them with CASE WHEN to see if it improves the performance.
The select query inside the stored procedure is something like:
DECLARE #word = '%word%' --These are inputs
DECLARE #type = 'type'
SELECT * FROM table1
WHERE SomeCondition1
AND ( (#type = 'CS' AND col1 like #word)
OR
(#type = 'ED' AND col2 like #word)
....
)
I'm trying to write this query as:
SELECT * FROM table1
WHERE SomeCondition1
AND ( 1= CASE WHEN #type = 'CS'
THEN col1 like #word
WHEN #type = 'ED'
THEN col2 like #word
END )
But SQL 2012 gives the error 'Incorrect Syntax Near Like' for THEN col1 like #word. If I replace THEN col1 like #word with 1 then no complaints but LIKE should return a 0 or 1 anyway.
I tried SELECT (col1 like #word), extra (), etc with no success.
Is there a way to include LIKE in CASE WHEN in WHERE or should I just not bother if using CASE WHEN instead of the original IF's won't make any performance difference?
UPDATE:
This actually didn't make any difference performance wise.

There are is a lot of info online about these 'optional' type stored procedures and how to avoid parameter sniffing performance issues.
This syntax should get you closer though:
AND CASE
WHEN #type = 'CS' THEN col1
WHEN #type = 'ED' THEN col2
END LIKE #word
Just make sure the col1 and col2 datatypes are similar (don't mix INT and VARCHAR)
You should compare query plans between the two syntaxes to ascertain whether it even makes a difference. Your performance issue might be due more to parameter sniffing.

You can also try nested case statements. e.g. based on your latest post, something like:
1 = CASE WHEN #type = 'CandidateStatus'
THEN (CASE WHEN co.[Description] LIKE #text THEN 1 END)
...
END

Here's how I got it to work, now just need to test if it makes any difference to performance. #Nick.McDermaid 's parameter sniffing is worth looking at.
1 = CASE WHEN #type = 'CandidateStatus'
THEN (SELECT 1 WHERE co.[Description] LIKE #text)

Related

Using the LIKE operator and "greater than" in a Case When expression

This is for a section in a unit test that I'm writing.
I am trying to say pass if any row in a column contains a certain string. So in words, what I want is "if the number of row that contain astring is greater than zero than pass the test".
I have something like the code below, but it fails saying that myVariable needs to be declared. What am I doing wrong?
DECLARE #myVariable BIT =
(
SELECT CASE
WHEN Count(Description) LIKE '%astring%' > 0
THEN
1
ELSE
0
END
FROM TABLE
SELECT #myVariable
I think you want:
DECLARE #myVariable BIT =
(SELECT (CASE WHEN Count(*) > 0 THEN 1 ELSE 0 END)
FROM TABLE
WHERE Description LIKE '%astring%'
);
I wouldn't recommend a bit for this. SQL Server doesn't really support booleans. Integers (or tinyints even) are usually easier to work with than bits.
Just:
DECLARE #myVariable BIT = (
SELECT MAX(CASE WHEN Description LIKE '%astring%' THEN 1 ELSE 0 END)
FROM mytable
);
This sets the variable to 1 if at least one row in the table has a Description that matches the pattern.
DECLARE #myVariable BIT=
(
SELECT IIF(ISNULL(count(*),0)>0,1,0)
FROM TABLE
WHERE EXISTS(SELECT * FROM TABLE WHERE Description LIKE '%astring%')
)
SELECT #myVariable AS myVariable

Bit field in where clause

I have a situation where I want to search for a field in the where clause only if the bit variable is 1 else ignore it.
#Active bit = 0
select * from Foo where firstname = 'xyz' and
if(#active=1)
then search on the Active column else ignore the filtering on the Active column. How can I have that in a simple condition instead of checking each parameter seperately and then building the where clause
Just simple logic will usually suffice:
select * from Foo where firstname = 'xyz' and
(#Active = 0 or Active = <filter condition>)
For general advice about writing code for arbitrary search conditions, you could do worse than read Erland Sommarskog's Dynamic Search Conditions in T-SQL
it seems like Active is the Actual Column as well in your table.
using Case stmt you can make the search efficient as it will use appropriate indexes you may have on this table.
DECLARE #Active BIT=0
SELECT *
FROM Foo
WHERE firstname = 'a'
AND active = CASE WHEN #Active=1 THEN #Active ELSE Active END
How about:
DECLARE #Active bit
if #Active = 1
BEGIN
(select * from Foo where firstname = 'bar' and Active = --condition)
END
else
BEGIN
(select * from Foo where firstname = 'bar')
END
of course, something will have to set the value for #Active somewhere between the declaration and the if...else statement.
you can write this as below
select * from Foo where firstname = 'xyz' and (#isactive=0 or (some conditions))

Optional Reporting Parameters (is there a better way?)

I have a report with 4 parameters. I would like to make them not required. The problem is the conventional approach to do this, creates 16 OR/AND statements. If I were to have 10 not required parameters the SOL statement would be out of control. This works but is there an easier way?
Here is what I have:
MAIN DATA SET:
select *
from table
where
table.one = #param1 OR #param1 IS NOT NULL
AND.....(etc.etc..)
#param1, #param2,#param3,#param4: (default value null/blank)
Select some_column from any_table UNION SELECT '' as Nothing
The way I've always done it is
WHERE
col1 = isnull(#col1, col1)
and col2 = isnull(#col2, col2)
...etc
So pretty much what you have, with some semantic corrections.

Best practice to avoid code replication?

currently I have to change a view, a big part of the view code looks like this:
CAST('My_Tag_' + CASE
WHEN FieldX isnull
THEN ''
WHEN FieldX = '123'
THEN 'some_number'
WHEN FieldX = 'abc'
THEN 'some_text'
ELSE 'strange' ) AS VARCHAR(128) AS myField
)
Just a chunk of code, that puts together a string (the code itself doesn't even matter right now, I have like 50 other examples, where I have a lot of code replication). Now I have exact the same code for 30 more fields in the view, just the 'My_Tag_' and FieldX is changing. If this would be C#, I would just write a little helper function.
Of course I could write a function here, too. But as this is a bigger project with a lot of tables, views, etc, I would have hundreds of functions soon.
Now I am pretty new to SQL and normally my home is the OOP-world. But there has to be a solution to avoid code replication and to avoid having hundreds of helper functions in the database?
What's best practice in this case?
The best practice may be to create a user defined function.
The arguments would be the fields that change and it would return the intended value.
You can use a CTE to add a field to a table:
; with TableWithExtraField as
(
select case ... end as NewField
, table1
)
select NewField
from TableWithExtraField
Or a subquery also works:
select NewField
from (
select case ... end as NewField
, table1
) as TableWithExtraField
CREATE FUNCTION dbo.MyTag(#myfield VARCHAR(MAX))
RETURNS VARCHAR(128)
AS
BEGIN
RETURN CAST('My_Tag_' + CASE
WHEN #myfield IS NULL
THEN ''
WHEN #myfield = '123'
THEN 'some_number'
WHEN #myfield = 'abc'
THEN 'some_text'
ELSE 'strange' END AS VARCHAR(128))
)
END

Efficiently Handling Multiple Optional Constraints in Where Clause

This is somewhat of a sequel to Slow Exists Check. Alex's suggestion works and successfully avoids code repetition, but I still end up with a second issue. Consider the example below (From AlexKuznetsov). In it, I have two branches to handle 1 contraint. If I had 2 optional constraints, I would end up with 4 branches. Basically, the number of branches increases exponentially with the number of constraints.
On the other hand, if I use a Multi-Statement Table-valued function or otherwise use temporary tables, the SQL query optimizer is not able to assist me, so things become slow. I am somewhat distrustful of dynamic SQL (and I've heard it is slow, too).
Can anyone offer suggestions on how to add more constraints without adding lots of if statements?
Note: I have previously tried just chaining x is null or inpo = #inpo together, but this is very slow. Keep in mind that while the inpo = #inpo test can be handled via some sort of indexing black magic, the nullity test ends up being evaluated for every row in the table.
IF #inpo IS NULL BEGIN
SELECT a,b,c
FROM dbo.ReuseMyQuery(#i1)
ORDER BY c;
END ELSE BEGIN
SELECT a,b,c
FROM dbo.ReuseMyQuery(#i1)
WHERE inpo = #inpo
ORDER BY c;
END
Variation Two: 2 constraints:
IF #inpo IS NULL BEGIN
IF #inpo2 IS NULL BEGIN
SELECT a,b,c
FROM dbo.ReuseMyQuery(#i1)
ORDER BY c;
END ELSE BEGIN
SELECT a,b,c
FROM dbo.ReuseMyQuery(#i1)
WHERE inpo2 = #inpo2
ORDER BY c;
END
END ELSE BEGIN
IF #inpo2 IS NULL BEGIN
SELECT a,b,c
FROM dbo.ReuseMyQuery(#i1)
WHERE inpo = #inpo
ORDER BY c;
END ELSE BEGIN
SELECT a,b,c
FROM dbo.ReuseMyQuery(#i1)
WHERE inpo = #inpo AND
inpo2 = #inpo2
ORDER BY c;
END
END
this is the best reference: http://www.sommarskog.se/dyn-search-2005.html
In such cases I use sp_executesql as described in Erland's article: Using sp_executesql
Whenever dynamic SQL is used, missing permissions may be a problem, so I have a real network account for unit testing, I add that account to the actual role, and I impersonate with that real account whenever I test dynamic SQL, as described here: Database Unit Testing: Impersonation
Here's a rough example. Modify the LIKE statements in the WHERE clause depending if you want "starts with" or "contains" or an exact match in your query.
CREATE PROCEDURE dbo.test
#name AS VARCHAR(50) = NULL,
#address1 AS VARCHAR(50) = NULL,
#address2 AS VARCHAR(50) = NULL,
#city AS VARCHAR(50) = NULL,
#state AS VARCHAR(50) = NULL,
#zip_code AS VARCHAR(50) = NULL
AS
BEGIN
SELECT [name],
address1,
address2,
city,
state,
zip_code
FROM my_table
WHERE ([name] LIKE #name + '%' OR #name IS NULL)
AND (address1 LIKE #address1 + '%' OR #address1 IS NULL)
AND (address2 LIKE #address2 + '%' OR #address2 IS NULL)
AND (city LIKE #city + '%' OR #city IS NULL)
AND (state LIKE #state + '%' OR #state IS NULL)
AND (zip_code LIKE #zip_code + '%' OR #zip_code IS NULL)
ORDER BY [name]
END
GO
Select blah from foo
Where (#inpo1 is null or #inpo1 = inpo1)
and (#inpo2 is null or #inpo2 = inpo2)
Apparently this is too slow. Interesting.
Have you considered code generation? Lengthy queries with lots of duplication is only an issue if it has to be maintained directly.
I realise your question may be purely academic, but if you have real world use cases have you considered only providing optimised queries for the most common scenarios?