SQL conditional statement to include operator - sql

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.

Related

Conditional statement in sql query on classic ASP Page

I am fetching three values in my page named 123.asp
(123.asp?School=NewEnglishSchool&Batch=1&AcademicYear=2)
School = Request.QueryString("School")
Batch = Request.QueryString("Batch")
AcademicYear = Request.QueryString("AcademicYear")
I have a simple query in form like below
strSqlData="select * from MyTable where gender="male""
I need to create a query like below, if I found a value for Batch and AcademicYear except for School on the page via Request.QueryString means (123.asp?Batch=1&AcademicYear=2)
then I want to write a query something like this
strSqlData="select * from MyTable where gender="male"
if len(AcademicYear)<>o then
and AcademicYear='"&AcademicYear&"'
elseif len(Batch)<>o then
and Batch='"&Batch&"'
end if
elseif len(School)<>o then
and School='"&School&"'
end if "
I want to create something like above but in proper method without using case, because I have so many filters on the page like school, batch and Academicyear.
In VBScript you can use the ampersand (&) symbol to concatenate strings. Try this:
strSqlData="select * from MyTable where gender='male'"
if AcademicYear <> "" then
strSqlData= strSqlData & " and AcademicYear=" & AcademicYear
end if
if Batch <> "" then
strSqlData= strSqlData & " and Batch=" & Batch
end if
if School <> "" then
strSqlData= strSqlData & " and School=" & School
end if
You have three separate "and" clauses to append to your sql query. The conditional statements are independent of each other so you shouldn't be using elseif which is for different options within a single conditional statement. It's simpler to check whether or not a string isn't empty if stringname <> "" than using len, (and I doubt your conditional statements would work because you appear to be using a lower case letter "o" where you should be using a zero)
It's very easy to make mistakes when you're assembling sql queries like this. In testing it's often worth adding a line like Response.Write strSqlData before you try to execute it to check that the query is what you were intending
As other comments have suggested though, your code is vulnerable to an SQL injection attack. Urls which contain ".asp?" can almost be guaranteed to be hit by an ASPROX type attack sooner or later. Parameterised queries are the best means of guarding against this, but one quick approach if your querystring values are all numeric is to use cint() - eg
strSqlData= strSqlData & " and AcademicYear=" & cint(AcademicYear)
This will throw a type mismatch error if the querystring contains anything other than numbers, and the script will fall over before it attempts to execute your sql query.
Instead of creating a sql query in your asp page you may create a Store Procedure and call it on your ASP page with the required parameter from your Query String.
Here is the sample SP which I have created as per your asked scenario
CREATE PROCEDURE Sp_fetch_data #school VARCHAR(max),
#batch VARCHAR(max),
#academicYear VARCHAR(max)
AS
BEGIN
SET #school = (SELECT CASE
WHEN #school = '' THEN NULL
ELSE #school
END)
SET #batch = (SELECT CASE
WHEN #batch = '' THEN NULL
ELSE #batch
END)
SET #academicYear = (SELECT CASE
WHEN #academicYear = '' THEN NULL
ELSE #academicYear
END)
SELECT *
FROM mytable
WHERE gender = 'male'
AND school = #school
AND batch = #batch
AND academicyear = #academicYear
END
Now simply call this SP in your ASP page:
strSqlData = "sp_fetch_data'"&Request.QueryString("school")&"','"&Request.QueryString("batch")&"','"&Request.QueryString("academicYear")&"'"
EDIT:Execute Store Procedure with NULL data
ALTER PROCEDURE Sp_fetch_data #school VARCHAR(max) = NULL,
#batch VARCHAR(max) = NULL,
#academicYear VARCHAR(max) = NULL
AS
BEGIN
SELECT *
FROM mytable
WHERE gender = 'male'
AND ( #school = school
OR #school IS NULL )
AND ( #batch = batch
OR #batch IS NULL )
AND ( #academicYear = academicyear
OR #academicYear IS NULL )
END
You should think the problem only as a string creation. You have to create a string that will be a correct sql query.
The approach that you have tried wants to create a query that contains logical statements. Instead of that, you should write your C# code in a way to create a proper sql query.
For an example, let's take the first part of your query:
strSqlData="select * from MyTable where gender="male"
if len(AcademicYear)<>o then
and AcademicYear='"&AcademicYear&"'
You can easily transfer that for a proper C# solution something like that:
string strSqlData;
int AcademicYear = 1994;
int o = 2000;
string male = "male";
strSqlData="select * from MyTable where gender= " + male;
if (AcademicYear != o)
strSqlData += " and AcademicYear=" + AcademicYear.ToString();
Console.WriteLine(strSqlData);
Actually after you have fixed that part of your code, there will be some additional to do that you have to do. For example, your should use parametrized queries instead of hard coding an sql string.
Maybe you should find an example about building data access layer.

Multi Columns filtration in stored procedure in SQL Server

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

Building dynamic WHERE clause in stored procedure

I'm using SQL Server 2008 Express, and I have a stored procedure that do a SELECT from table, based on parameters. I have nvarchar parameters and int parameters.
Here is my problem, my where clause looks like this:
WHERE [companies_SimpleList].[Description] Like #What
AND companies_SimpleList.Keywords Like #Keywords
AND companies_SimpleList.FullAdress Like #Where
AND companies_SimpleList.ActivityId = #ActivityId
AND companies_SimpleList.DepartementId = #DepartementId
AND companies_SimpleList.CityId = #CityId
This parameters are the filter values set by the user of my ASP.NET MVC 3 application, and the int parameters may not be set, so their value will be 0. This is my problem, the stored procedure will search for items who have 0 as CityId for example, and for this, it return a wrong result. So it will be nice, to be able to have a dynamic where clause, based on if the value of int parameter is grater than 0, or not.
Thanks in advance
Try this instead:
WHERE 1 = 1
AND (#what IS NULL OR [companies_SimpleList].[Description] Like #What )
AND (#keywords IS NULL OR companies_SimpleList.Keywords Like #Keywords)
AND (#where IS NULL OR companies_SimpleList.FullAdress Like #Where)
...
If any of the parameters #what, #where is sent to the stored procedure with NULL value then the condition will be ignored. You can use 0 instead of null as a test value then it will be something like #what = 0 OR ...
try something like
AND companies_SimpleList.CityId = #CityId or #CityID = 0
Here is another easy-to-read solution for SQL Server >=2008
CREATE PROCEDURE FindEmployee
#Id INT = NULL,
#SecondName NVARCHAR(MAX) = NULL
AS
SELECT Employees.Id AS "Id",
Employees.FirstName AS "FirstName",
Employees.SecondName AS "SecondName"
FROM Employees
WHERE Employees.Id = COALESCE(#Id, Employees.Id)
AND Employees.SecondName LIKE COALESCE(#SecondName, Employees.SecondName) + '%'

SQL Data Filtering approach

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.

creating SQL command to return match or else everything else

i have three checkboxs in my application. If the user ticks a combination of the boxes i want to return matches for the boxes ticked and in the case where a box is not checked i just want to return everything . Can i do this with single SQL command?
I recommend doing the following in the WHERE clause;
...
AND (#OnlyNotApproved = 0 OR ApprovedDate IS NULL)
It is not one SQL command, but works very well for me. Basically the first part checks if the switch is set (checkbox selected). The second is the filter given the checkbox is selected. Here you can do whatever you would normally do.
You can build a SQL statement with a dynamic where clause:
string query = "SELECT * FROM TheTable WHERE 1=1 ";
if (checkBlackOnly.Checked)
query += "AND Color = 'Black' ";
if (checkWhiteOnly.Checked)
query += "AND Color = 'White' ";
Or you can create a stored procedure with variables to do this:
CREATE PROCEDURE dbo.GetList
#CheckBlackOnly bit
, #CheckWhiteOnly bit
AS
SELECT *
FROM TheTable
WHERE
(#CheckBlackOnly = 0 or (#CheckBlackOnly = 1 AND Color = 'Black'))
AND (#CheckWhiteOnly = 0 or (#CheckWhiteOnly = 1 AND Color = 'White'))
....
sure. example below assumes SQL Server but you get the gist.
You could do it pretty easily using some Dynamic SQL
Lets say you were passing your checkboxes to a sproc as bit values.
DECLARE bit #cb1
DECLARE bit #cb2
DECLARE bit #cb3
DECLARE nvarchar(max) #whereClause
IF(#cb1 = 1)
SET #whereClause = #whereClause + ' AND col1 = ' + #cb1
IF(#cb2 = 1)
SET #whereClause = #whereClause + ' AND col2 = ' + #cb2
IF(#cb3 = 1)
SET #whereClause = #whereClause + ' AND col3 = ' + #cb3
DECLARE nvarchar(max) #sql
SET #sql = 'SELECT * FROM Table WHERE 1 = 1' + #whereClause
exec (#sql)
Sure you can.
If you compose your SQL SELECT statement in the code, then you just have to generate:
in case nothing or all is selected (check it using your language), you just issue non-filter version:
SELECT ... FROM ...
in case some checkboxes are checked, you create add a WHERE clause to it:
SELECT ... FROM ... WHERE MyTypeID IN (3, 5, 7)
This is single SQL command, but it is different depending on the selection, of course.
Now, if you would like to use one stored procedure to do the job, then the implementation would depend on the database engine since what you need is to be able to pass multiple parameters. I would discourage using a procedure with just plain 3 parameters, because when you add another check-box, you will have to change the SQL procedure as well.
SELECT *
FROM table
WHERE value IN
(
SELECT option
FROM checked_options
UNION ALL
SELECT option
FROM all_options
WHERE NOT EXISTS (
SELECT 1
FROM checked_options
)
)
The inner subquery will return either the list of the checked options, or all possible options if the list is empty.
For MySQL, it will be better to use this:
SELECT *
FROM t_data
WHERE EXISTS (
SELECT 1
FROM t_checked
WHERE session = 2
)
AND opt IN
(
SELECT opt
FROM t_checked
WHERE session = 2
)
UNION ALL
SELECT *
FROM t_data
WHERE NOT EXISTS (
SELECT 1
FROM t_checked
WHERE session = 2
)
MySQL will notice IMPOSSIBLE WHERE on either of the SELECT's, and will execute only the appropriate one.
See this entry in my blog for performance detail:
Selecting options
If you pass a null into the appropriate values, then it will compare that specific column against itself. If you pass a value, it will compare the column against the value
CREATE PROCEDURE MyCommand
(
#Check1 BIT = NULL,
#Check2 BIT = NULL,
#Check3 BIT = NULL
)
AS
SELECT *
FROM Table
WHERE Column1 = ISNULL(#Check1, Column1)
AND Column2 = ISNULL(#Check2, Column2)
AND Column3 = ISNULL(#Check3, Column3)
The question did not specify a DB product or programming language. However it can be done with ANSI SQL in a cross-product manner.
Assuming a programming language that uses $var$ for variable insertion on strings.
On the server you get all selected values in a list, so if the first two boxes are selected you would have a GET/POST variable like
http://url?colors=black,white
so you build a query like this (pseudocode)
colors = POST['colors'];
colors_list = replace(colors, ',', "','"); // separate colors with single-quotes
sql = "WHERE ('$colors$' == '') OR (color IN ('$colors_list$'));";
and your DB will see:
WHERE ('black,white' == '') OR (color IN ('black','white')); -- some selections
WHERE ('' == '') OR (color IN ('')); -- nothing selected (matches all rows)
Which is a valid SQL query. The first condition matches any row when nothing is selected, otherwise the right side of the OR statement will match any row that is one of the colors. This query scales to an unlimited number of options without modification. The brackets around each clause are optional as well but I use them for clarity.
Naturally you will need to protect the string from SQL injection using parameters or escaping as you see fit. Otherwise a malicious value for colors will allow your DB to be attacked.