SQL OR statement hit when previous condition is true - Full Text Search - sql

In the query below I would expect the OR CONTAINS(d.Data, #SearchText) never to be reached due to #SearchText being set to NULL. However running this query throws a
"Null or empty full-text predicate."
error because the CONTAINS(d.Data, #SearchText) is called with a null value for #SearchText.
Any ideas why the second part of the OR statement is called when the first part is true?
DECLARE #searchText nvarchar(300);
set #SearchText = null
SELECT * FROM Person p
JOIN PersonDocument pd ON p.Id = pd.PersonId
JOIN Document d ON d.Id = pd.DocumentId
WHERE #SearchText IS NULL OR CONTAINS(d.Data, #SearchText)

Contains will get called because it's an OR statement, even if it the value wasn't null it would still get called due to the OR statement, I think you need a guard statement such as:
WHERE #SearchText IS NULL OR #SearchText IS NOT NULL AND CONTAINS(d.Data, #SearchText)

I got it working in the end like this:
DECLARE #searchText nvarchar(300);
set #SearchText = null -- imagine this parameter was passed in null
IF #SearchText is null
BEGIN
SET #SearchText = '""'
END
SELECT * FROM Person p
JOIN PersonDocument pd ON p.Id = pd.PersonId
JOIN Document d ON d.Id = pd.DocumentId
WHERE #SearchText = '""' OR CONTAINS(d.Data, #SearchText)

Related

How can I create a conditional WHERE clause in SQL Server that returns multiple values?

I have the following SQL code for a SSRS report. I simplified the code because the original query is much longer.
There is a parameter #ARTICLE which a user can input. What I want to do is create a conditional WHERE statement. If a user enters an article number (#ARTICLE) the query should filter ID's from Table1 that match with ID's for which the entered article number (#ARTICLE) have a match with a 'detailcode' from another table. If there is no article number given, do not filter (or skip the whole WHERE statement)
With the code below I get the following error:
Subquery returned more than 1 value. This is not permitted when the subquery follows =, !=, <, <= , >, >= or when the subquery is used as an expression.'
Logically it works perfectly fine without the CASE statement, so when only the subquery is used to check for matching ID's. However, I only want to return matching IDs if the #ARTICLE parameter has a value. If it is NULL or an empty string I want to return all IDs (or just skip the entire WHERE statement). How can I include a condition in the WHERE clause that allows multiple rows to return given the example below?
I feel like my approach is way to complicated, any help is much appreciated!
DECLARE #ARTICLE AS VARCHAR(50) = '1234567'
SELECT * FROM Table1
WHERE
Table1.ID IN (
CASE
WHEN ISNULL(#ARTICLE,'')<>'' THEN
(
SELECT ID
FROM Table2
WHERE detailcode IN (#ARTICLE)
)
ELSE Table1.ID
END
)
You're right, you're overcomplicating it a bit - if you look at the LIKE operator you can do something like:
DECLARE #filter NVARCHAR(50) = '123456';
DECLARE #f NVARCHAR(100) = '%' + #filter + '%';
SELECT *
FROM [Table1] AS [t1]
INNER JOIN [Table2] AS [t2]
ON [t2].[joinField] = [t1].[joinField]
AND [t2].[detailCode] LIKE #f;
Where #filter is a parameter to the stored procedure.
Or to account for detailCode being null:
DECLARE #filter NVARCHAR(50) = '123456';
DECLARE #f NVARCHAR(100) = '%' + #filter + '%';
IF #filter != NULL
SELECT *
FROM [Table1] AS [t1]
INNER JOIN [Table2] AS [t2]
ON [t2].[joinField] = [t1].[joinField]
AND [t2].[detailCode] LIKE #f;
ELSE
SELECT *
FROM [Table1] AS [t1]
INNER JOIN [Table2] AS [t2]
ON [t2].[joinField] = [t1].[joinField];
I would check wether #ARTICLE is NULL or if it is NOT NULL and your subquery is fulfilled, like so:
WHERE
ISNULL(#ARTICLE, '') = ''
OR
(
ISNULL(#ARTICLE, '') <> ''
AND ID IN
(
SELECT ID FROM Table2
WHERE detailcode = #ARTICLE
)
)
Maybe you can do it entire different, with an exists for example.
So you return all rows when #ARTICLE is null or '' OR exists at least one row in table2 with this article
The OR will have the effect that no filtering is done when the variable is null or ''
DECLARE #ARTICLE AS VARCHAR(50) = '1234567'
select t1.*
from table1 t1
where ( isnull(#ARTICLE, '') = ''
or
exists ( select 1 from table2 t2 where t2.detailcode = #ARTICLE )
)

How can you use search filters that can take multiple search values?

I'm trying to generate reports that has multiple search filters and I need those search filters to be able to take multiple values as search parameters.
Create Proc [procedure_name](
#name nvarchar(50) null,
#center nvarchar(3)null,
#branch nvarchar(6)null
)
As
Begin
Select [column1], [column2], [column3], [column4]
from table1 a
left join table2 b
on a.cid=b.cid and a.acc=b.acc
Where #name is null or a.Name=#name
and #center is null or a.center=#center
and #branch is null or a.branch=#branch
End
I need those search parameters to be able to take multiple values and filter them in the same table i.e.
instead of #name = 'Mark' gives all result that has Mark
I need #name = 'Mark', 'James', 'Adam' that give all results that has Mark, James and Adam.
Use an SplitString function, like this one : T-SQL split string
Now your query can filter multiple values if you pass them separated by commas. #name = 'Mark,James,Adam'
Instead of :
#name is null or a.Name=#name
use :
#name is null or a.Name in (select Name from dbo.splitstring(#name))
PS: by the way, when using this kind of catch-all statements I always add option(recompile) at the end, so the SQL engine recalculates the execution plan depending on the parameters that you have passed.
First, your existing logic is wrong. It should be:
where (#name is null or a.Name = #name) and
(#center is null or a.center = #center) and
(#branch is null or a.branch = #branch)
For your fix:
where (#name is null or a.Name in (select value from string_split(#name)) and
(#center is null or a.center in (select value from string_split(#center)) and
(#branch is null or a.branch in (select value from string_split(#branch))

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)

LIKE and NULL in WHERE clause in SQL

I have a store procedure which i have planned to use for search and get all values.
Scenario:
If the parameter passed is NULL it should return all the values of the table and if the parameter passed is not NULL it should return the values according to the condition which is in LIKE.
//Query:
ALTER procedure [dbo].[usp_GetAllCustomerDetails]
(
#Keyword nvarchar(20) = null
)
As
Begin
Select CustomerId,CustomerName,CustomerTypeName,CustomerCode,CategoryName,CustomerMobile,CustomerEmail,CustomerAddress,CustomerCity,CustomerState,Pincode
from tblCustomerMaster CM
inner join dbo.tblCustomerTypeMaster CTM on CTM.CustomerTypeId = CM.CustomerType
inner join dbo.tblCategoryMaster CCM on CCM.CategoryId= CM.CustomerCategory
where CustomerName like '%'+#Keyword+'%'
In the above query it returns no values when i execute since the NULL is assumed as string by SQL, so what should i write in the where clause to get the desired output?
You can use condition like this in you where clause
where #Keyword is null or CustomerName like '%' + #Keyword + '%'
I just want to point out another way of solving this problem. The issue is that the default value for #KeyWord is NULL. If you change the default to '', then the problem goes away:
ALTER procedure [dbo].[usp_GetAllCustomerDetails]
(
#Keyword nvarchar(20) = ''
)
Any non-NULL customer name would then be like '%%'.
You just need to add SET #Keyword = coalesce(#Keyword,'') to your procedure like this :
ALTER procedure [dbo].[usp_GetAllCustomerDetails]
(
#Keyword nvarchar(20) = null
)
As
Begin
SET #Keyword = coalesce(#Keyword,'')
Select CustomerId,CustomerName,CustomerTypeName,CustomerCode,CategoryName,CustomerMobile,CustomerEmail,CustomerAddress,CustomerCity,CustomerState,Pincode
from tblCustomerMaster CM
inner join dbo.tblCustomerTypeMaster CTM on CTM.CustomerTypeId = CM.CustomerType
inner join dbo.tblCategoryMaster CCM on CCM.CategoryId= CM.CustomerCategory
where CustomerName like '%'+#Keyword+'%'

TSQL Number of reads significantly different after query and stored procedure execution

After query optimization I got results that are ok and I wanted to alter stored procedure, but got much worst results after SP execution, than it was after query execution!
Firstly, I think at number of reads. What can be reason for so different results?
Query is identical like in SP, only difference is that in query I declared parameter, but in SP that was input parameter. Value that is set to parameter is also same. To avoid 'recorded data' first I recompiled SP and after that done DROP and CREATE, but results were also much different.
Query is like this (table and column names are changed because of simplification, and number of columns is reduced):
DECLARE #Var1 varchar(20)
SET #Var1 = #Var1 + '%'
DECLARE #Var2 TIMESTAMP
SELECT #Var2 = CONVERT(TIMESTAMP, ID, 0)
FROM
X_TIMESTAMPS (NOLOCK)
WHERE
TABLE = 'T1'
declare #Var3 varbinary(8)
SELECT #Var3 = max(IdTimeStamps)
FROM
T1 (NOLOCK)
SELECT o.c1
, o.c2
, o.c3
, v.c4
, v.c5
, p.c6
, p.c7
, va.c8
, isnull(s.c9, '') AS c9
, CASE o.c10
WHEN 1 THEN
0
ELSE
1
END c10
, o.c11
FROM
T1 o (NOLOCK)
JOIN T2 p (NOLOCK)
ON o.c1 = p.c12
JOIN T3 i (NOLOCK)
ON (o.c13 = i.c14)
JOIN T4 v (NOLOCK)
ON (v.c4 = i.c15)
LEFT JOIN T5 s (NOLOCK)
ON (o.c16 = s.c17)
JOIN T6 va (NOLOCK)
ON o.c11 = va.c18
WHERE
o.c1 LIKE #Var1
AND o.c2 > #Var2
And procedure is like this:
CREATE PROCEDURE [dbo].[SP1] #Var1 varchar(20) =''
WITH RECOMPILE
AS
BEGIN
PREVIOUS QUERY WITHOUT DECLARATION FOR #Var1
END
TnX in advance!
Nemanja
It's because different execution plans are used for query with constants and sp with parameeters. You can try a few tricks
Create inline table function and try it
create function sf_test
(
#param1 int
)
returns table
as
return
your query using #in_param1
or
declare additional parameters in your procedure like this
create procedure sp_test
(
#param1 int
)
as
begin
declare #in_param1 int
select #in_param1 = #param1
your query using #in_param1
end
you can also try using option with recompile in your procedure, or use dynamic SQL
This is almost certainly a parameter sniffing issue. Personally, I liked the dummy variable option to work around this issue and (only when I run into this problem) create variable(s) that are set to the value of the incoming parameter(s).