I have a stored procedure that receives 3 parameters that are used to dynamically filter the result set
create proc MyProc
#Parameter1 int,
#Parameter2 int,
#Paremeter3 int
as
select * from My_table
where
1 = case when #Parameter1 = 0 then 1 when #Parameter1 = Column1 then 1 else 0 end
and
1 = case when #Parameter2 = 0 then 1 when #Parameter2 = Column2 then 1 else 0 end
and
1 = case when #Parameter3 = 0 then 1 when #Parameter3 = Column3 then 1 else 0 end
return
The values passed for each parameter can be 0 (for all items) or non-zero for items matching on specific column.
I may have upwards of 20 parameters (example shown only has 3). Is there a more elegant approach to allow this to scale when the database gets large?
I am using something similar to your idea:
select *
from TableA
where
(#Param1 is null or Column1 = #Param1)
AND (#Param2 is null or Column2 = #Param2)
AND (#Param3 is null or Column3 = #Param3)
It is generally the same, but I used NULLs as neutral value instead of 0. It is more flexible in a sense that it doesn't matter what is the data type of the #Param variables.
I use a slightly different method to some of the ones listed above and I've not noticed any performance hit. Instead of passing in 0 for no filter I would use null and I would force it to be the default value of the parameter.
As we give the parameters default values it makes them optional which lends itself to better readability when your calling the procedure.
create proc myProc
#Parameter1 int = null,
#Parameter2 int = null,
#Paremeter3 int = null
AS
select
*
from
TableA
where
column1 = coalesce(#Parameter1,column1)
and
column2 = coalesce(#Parameter2, column2)
and
column3 = coalesce(#Parameter3,column3)
That said I may well try out the dynamic sql method next time to see if I notice any performance difference
Unfortunately dynamic SQL is the best solution performance/stability wise. While all the other methods commonly used ( #param is not or Col = #param, COALESCE, CASE... ) work, they are unpredictable and unreliable, execution plan can (and will) vary for each execution and you may find yourself spending lots of hours trying to figure out, why your query performs really bad when it was working fine yesterday.
Related
I have a stored procedure that was not written by me.
Could you please help me translate CASE statement in WHERE clause?
--Declaring the parameter for SP
DECLARE
#CompanyGuids varchar(8000) = '29634AF7-D0A2-473D-9574-405C23E10F02'
--Declaring table variable that will contain only CompanyGuid
DECLARE #CompanyGuidsTbl TABLE(Guid uniqueidentifier)
IF #CompanyGuids IS NULL
BEGIN
INSERT INTO #CompanyGuidsTbl
SELECT DISTINCT CompanyGuid FROM tblCompanies
END
ELSE
BEGIN
INSERT INTO #CompanyGuidsTbl
SELECT * FROM dbo.StringOfGuidsToTable(#CompanyGuids,',')
END
--Select statement
SELECT Col1,
Col2,
Col3
FROM MyTable1 INNER JOIN MyTable2
/* this is where I am confused */
WHERE
CASE WHEN #CompanyGuids IS NOT NULL
THEN
CASE WHEN tblCompanies.CompanyGuid in (SELECT Guid FROM #CompanyGuidsTbl)
THEN 1 ELSE 0 END
ELSE 1
END = 1
Correct me if I'm wrong:
"So if the parameter #CompanyGuids is NOT NULL, then we are checking if table #CompanyGuidsTbl has assigned parameter and if it does - then we gonna use it, but if it does not - ??? "
Maybe there is a way to rewrite it?
Thanks
A poorly constructed statement for sure, but yes it is ultimately checking a truth statement where 1 = 1. First checks for an empty variable, then if the variable is not empty it checks if the CompanyGUID in tblCompanies is in the list supplied in the variable, returning 1 if it is found (thus 1 = 1 is true so the record is matched), or if it is not found (in which case 0 = 1, which is false so the record is not matched). Awful stuff!
I am having more controls (assume 10 controls with textbox, dropdown, radio buttons) in my Windows forms application for filtering data which all are not a mandatory, hence user may filter data with 1 control or more.
Now I have to create a stored procedure for filtering the data based on their inputs.
Ex: if user enters some text in 1 textbox control, and left remaining 9 controls with empty data, I have to filter data based on only that textbox which user entered.
If user enters some text in 1 textbox control and 1 dropdown, and left remaining 8 controls with empty data, I have to filter data based on only that textbox and dropdown which user entered.
What am I supposed to do?
In source code:
If user entered/selected text on any control, I am passing values as parameters else i am passing as "null" to remaining all other parameters .
In stored procedure:
I gave all 10 controls parameters to get value from Source Code,based on parameters I am filtering data.
if #Param1=null && #Param2=null && #Param3='SomeText'
begin
sELECT * FROM tABLE1 wHERE TableCOLUMN3=#Param3
END
if #Param1=null && #Param2='SomeText' && #Param3='SomeText'
begin
sELECT * FROM tABLE1 wHERE TableCOLUMN2=#Param2 AND TableCOLUMN3=#Param3
END
Note: I need to filter data with each table column to each parameter , Simply assume #Param1--TableCOLUMN1, #param2--TableCOLUMN2, filter varies depend on parameters text.
If I do like this my stored procedure will be more enormous and very big because I have 10 parameters to check (just for your reference I gave 3 parameters above sample).
What I want is :
Since I gave 10 parameters, based on parameters which having values (some text other than NULL) only I have to filter data by using where condition.
Is there any other way to do this, or does anyone have any other ways for this to do?
As long as you make your params default to null and either don't pass in a value for the params you dont need or pass in dbnull value then you can filter like this
CREATE PROC dbo.SAMPLE
(
#Param1 VARCHAR(255) = NULL,
#Param2 VARCHAR(255) = NULL,
#Param3 VARCHAR(255) = NULL,
#Param4 VARCHAR(255) = NULL,
#Param5 VARCHAR(255) = NULL,
#Param6 VARCHAR(255) = NULL
)
AS
BEGIN
SELECT *
FROM Table1
WHERE (#Param1 IS NULL OR TableCOLUMN1 = #Param1)
AND (#Param2 IS NULL OR TableCOLUMN2 = #Param2)
AND (#Param3 IS NULL OR TableCOLUMN3 = #Param3)
AND (#Param4 IS NULL OR TableCOLUMN4 = #Param4)
AND (#Param5 IS NULL OR TableCOLUMN5 = #Param5)
OPTION (RECOMPILE) -- as JamesZ suggested to prevent caching
END
EXEC dbo.SAMPLE #Param2 = 'SomeText' -- only filter where TableCOLUMN2 = #Param2
I would suggest something like that:
SELECT *
FROM TABLE1
WHERE TableCOLUMN1=ISNULL(#Param1,TableCOLUMN1)
AND TableCOLUMN2=ISNULL(#Param2,TableCOLUMN2)
AND TableCOLUMN3=ISNULL(#Param3,TableCOLUMN3)
AND TableCOLUMN4=ISNULL(#Param4,TableCOLUMN4)
... and so on...
This will filter column1 on a value if you specify param1 otherwise it will use the columnvalue itself which will always be true.
But this will only work if your #Param values were NULL in each case if you won't use them.
If the table is big / you need to use indexes for fetching the rows, the problem with this kind of logic is, that indexes can't really be used. There's basically two ways how you can do that:
Add option (recompile) to the end of the select statement by #Ionic or #user1221684. This will cause the statement to be recompiled every time it is executed, which might be a lot of CPU overhead if it's called often.
Create dynamic SQL and call it using sp_executesql
Example:
set #sql = 'SELECT * FROM TABLE1 WHERE '
if (#Param1 is not NULL) set #sql = #sql + 'TableCOLUMN1=#Param1 AND '
if (#Param2 is not NULL) set #sql = #sql + 'TableCOLUMN2=#Param2 AND '
if (#Param3 is not NULL) set #sql = #sql + 'TableCOLUMN3=#Param3 AND '
-- Note: You're not concatenating the value of the parameter, just it's name
set #sql = #sql + ' 1=1' -- This handles the last 'and'
EXEC sp_executesql #sql,
N'#Param1 varchar(10), #Param2 varchar(10), #Param3 varchar(10)',
#Param1, #Param2, #Param3
As an extra option, you could do some kind of mix between your original idea and totally dynamic one, so that it would have at least the most common search criteria handled so that in can be fetched efficiently.
Normally every parameter will have a default value, for example int will have the default value as zero. So using this you can have the condition. See the exam sql below.
create procedure [dbo].[sp_report_test](
#pParam1 int,
#pParam2 int ,
#pParam3 int,
#pParam4 varchar(50)
)
AS
SELECT
*
FROM [vw_report]
where
(#pParam1 <= 0 or Column1 = #pParam1) and
(#pParam2 <= 0 or Column2 = #pParam2) and
(#pParam3 <= 0 or Column3 = #pParam3) and
(#pParam4 is null or len(#pParam4) <= 0 or Column4 = #pParam4);
GO
I want to create a stored procedure that uses a an input parameter in the where clause. The parameter will either be text or null.
I.e. suppose I have a table [tbl]
Status val
NULL 158
Acc 155
Acc 152
Null 24
Rej 34
In SQL server the null values can be found by using
WHERE Status is NULL
I also want the stored procedure to be able to handle a case when the parameter equals a specific value.
WHERE Status = #parameter
How do I combine both of these cases? I want something like this.
CREATE PROC test #parameter VARCHAR(10) = ''
SELECT *
FROM [tbl]
IF (#parameter is NULL OR #parameter = '') THEN WHERE STATUS is Null
ELSE WHERE STATUS = #parameter
WHERE (ISNULL(#parameter,'') = '' AND Status IS NULL)
OR Status = #parameter
The top half can only ever evaluate to true if #Status isn't NULL. Otherwise it falls back to the bottom half.
ISNULL(#Status,'') = '' is just the same as saying #Status IS NULL OR #Status = '', and if that's the case then it matches rows where Status IS NULL.
Looking at your pseudocode, you were pretty much there (things I added are in bold):
IF( (#parameter is NULL OR #parameter = '')THEN WHEREANDSTATUS is Null)
ELSE WHERE OR STATUS = #parameter
Just realised this might be better:
WHERE ISNULL(#parameter, '') = ISNULL(Status, '')
It will work the same, but I'm not sure if the ISNULL(Status, '') will stop indexes from being used, so it'd be worth checking the execution plan.
Just include both the condition in your WHERE clause such that it will fetch rows where status having null's or status having specific value
WHERE Status is NULL
OR Status = #parameter
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))
I am passing parameters into a stored procedure. The one parameter is a varchar(50) that can be a string like " > 5000" and " <= 10000".
Here is some of the code:
....
....
#colourid int = 0,
#regionid int = 0,
#sellingPrice varchar(50) = '-1'
AS
SELECT
....
....
WHERE
(dbo.tbl_Listings.fld_ColourID = CASE WHEN #colourid = 0 THEN dbo.tbl_Listings.fld_ColourID ELSE #colourid END)
AND (dbo.tbl_Listings.fld_RegionID = CASE WHEN #regionid = 0 THEN dbo.tbl_Listings.fld_RegionID ELSE #regionid END)
AND
How do I add #sellingPrice to the WHERE? I can't mimic how it was done for the int parameters because it's not always going to use =. I need to say "if selling price is not -1 then fld_SellingPrice #sellingPrice".
The only way you can achieve that is by using dynamic SQL, building up your query in a local variable and then executing it via (preferably) sp_executesql.
So something like
DECLARE #sql nvarchar(MAX)
SET #sql = 'SELECT .... WHERE ' + #sellingPrice
sp_executesql #sql
However, this really does open you up to the possibility of SQL injection, and therefore you have to either
a. Be very sure that the procedure will only be called by callers you trust fully
b. Add protection for badly formed parameters within your procedure, which is much harder than it sounds
c. Find a different way to approach the problem entirely.
If you know you are going to have a general set of comparisons to use, I would create a parameter per comparison in your SP and use them as needed. So your SP might have
#greaterThan int,
#lessThan int,
#equalTo int
Then in the SP you could do
if #greaterThan IS NULL
SELECT #greaterThan = MAX(field) FROM table -- or some arbitrary value that will always evaluate to true
if #lessThan IS NULL
SELECT #lessThan = MIN(field) FROM table
Then just use those in your WHERE clause. Otherwise, as posted, you're going to have to do dynamic SQL by building an SQL string with the pieces of the WHERE clause.
I would use a from and a to variable.
So when you want less than 5000, you set to variable = 5000 and leave from blank
....
....
#colourid int = 0,
#regionid int = 0,
#fromsellingprice int = 5000
#tosellingprice int = null
AS
SELECT
....
....
WHERE
(dbo.tbl_Listings.fld_ColourID = CASE WHEN #colourid = 0 THEN dbo.tbl_Listings.fld_ColourID ELSE #colourid END)
AND (dbo.tbl_Listings.fld_RegionID = CASE WHEN #regionid = 0 THEN dbo.tbl_Listings.fld_RegionID ELSE #regionid END)
AND
sellingPrice >= coalesce(#fromsellingprice, sellingprice)
and sellingPrice <= coalesce(#tosellingprice, sellingprice)
You can't do this directly in SQL - the parameter will not be parsed and interpreted as part of the query with predicates.
The only way to do this (passing in the operator) is to use dynamic SQL, which comes with its own pitfalls.
You may consider passing in a parameter for what operator to use and have a bunch of if sections for each supported parameter - this may be worse than dynamic SQL.