I'm creating a stored procedure to return search results where some of the parameters are optional.
I want an "if statement" in my where clause but can't get it working. The where clause should filter by only the non-null parameters.
Here's the sp
ALTER PROCEDURE spVillaGet
-- Add the parameters for the stored procedure here
#accomodationFK int = null,
#regionFK int = null,
#arrivalDate datetime,
#numberOfNights int,
#sleeps int = null,
#priceFloor money = null,
#priceCeil money = null
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
-- Insert statements for procedure here
select tblVillas.*, tblWeeklyPrices.price from tblVillas
INNER JOIN tblWeeklyPrices on tblVillas.villaId = tblWeeklyPrices.villaFK
where
If #accomodationFK <> null then
accomodationTypeFK = #accomodationFK
#regionFK <> null Then
And regionFK = #regionFK
IF #sleeps <> null Then
And sleeps = #sleeps
IF #priceFloor <> null Then
And price >= #priceFloor And price <= #priceCeil
END
Any ideas how to do this?
select tblVillas.*, tblWeeklyPrices.price
from tblVillas
INNER JOIN tblWeeklyPrices on tblVillas.villaId = tblWeeklyPrices.villaFK
where (#accomodationFK IS null OR accomodationTypeFK = #accomodationFK)
AND (#regionFK IS null or regionFK = #regionFK)
AND (#sleeps IS null OR sleeps = #sleeps)
AND (#priceFloor IS null OR (price BETWEEN #priceFloor And #priceCeil))
We've used a lot of COALESCE here in the past for "dynamic WHERE clauses" like you're talking about.
SELECT *
FROM vehicles
WHERE ([vin] LIKE COALESCE(#vin, [vin]) + '%' ESCAPE '\')
AND ([year] LIKE COALESCE(#year, [year]) + '%' ESCAPE '\')
AND ([make] LIKE COALESCE(#make, [make]) + '%' ESCAPE '\')
AND ([model] LIKE COALESCE(#model, [model]) + '%' ESCAPE '\')
A big problem arises though when you want to optionally filter for a column that is also nullable... if the data in the column is null for a given row AND the user didn't enter anything to search by for that column (so the user input is also null), then that row won't even show up in the results (which, if your filters are optional, is incorrect exclusionary behavior).
In order to compensate for nullable fields, you end up having to do messier looking SQL like so:
SELECT *
FROM vehicles
WHERE (([vin] LIKE COALESCE(#vin, [vin]) + '%' ESCAPE '\')
OR (#vin IS NULL AND [vin] IS NULL))
AND (([year] LIKE COALESCE(#year, [year]) + '%' ESCAPE '\')
OR (#year IS NULL AND [year] IS NULL))
AND (([make] LIKE COALESCE(#make, [make]) + '%' ESCAPE '\')
OR (#make IS NULL AND [make] IS NULL))
AND (([model] LIKE COALESCE(#model, [model]) + '%' ESCAPE '\')
OR (#model IS NULL AND [model] IS NULL))
Just so you understand, IF is procedural code in T-SQl. It canot be used in an insert/update/delete/select statement it can only be used to determine which of two statements you want to run. When you need different possibilities within a statement, you can do as above or use a CASE statement.
You can also use IsNull or Coalesce function
Where accomodationTypeFK = IsNull(#accomodationFK, accomodationTypeFK)
And regionFK = Coalesce(#regionFK,regionFK)
And sleeps = IsNull(#sleeps,sleeps )
And price Between IsNull(#priceFloor, Price) And IsNull(priceCeil, Price)
This does the same thing as Michael's suggestion above...
IsNull(), and Coalesce() work more or less the same way, they return the first non-Null argument in the list, except iSNull only allows 2 arguments, and Coalesce can take any number...
http://blogs.msdn.com/sqltips/archive/2008/06/26/differences-between-isnull-and-coalesce.aspx
Try putting your IF statement around the entire SQL statement. That means will have one SQL statement for each condition. That worked for me.
Related
There is one if case where I am using IF #SKU IS NULL OR #SKU = '', but my friend says it will take more time as compare to IF ISNULL(#SKU, '') = ''. So you should use IF ISNULL(#SKU, '') = ''. But I think I'm using correct. So please suggest me which one is run faster.
This is my stored procedure:
CREATE PROCEDURE USP_GetExistingRefunds
(
#OrderNo VARCHAR(50),
#SKU VARCHAR(255),
#ProfileID INT
)
AS
BEGIN
--IF ISNULL(#SKU, '') = '' --this work faster or
IF #SKU IS NULL OR #SKU = '' --this work faster
BEGIN
SELECT OrderNo, SKU, ISNULL(Quantity, 0) Quantity, ISNULL(Amount, 0) Amount
FROM StoreRefundOrder SRO
INNER JOIN StoreRefundOrderItem SROI ON SRO.ID = SROI.RefundOrderID
WHERE SRO.OrderNo = #OrderNo
AND ProfileID = #ProfileID
END
ELSE
BEGIN
SELECT OrderNo, SKU, ISNULL(SUM(Quantity), 0) Quantity, ISNULL(SUM(Amount), 0) Amount
FROM StoreRefundOrder SRO
INNER JOIN StoreRefundOrderItem SROI ON SRO.ID = SROI.RefundOrderID
WHERE SRO.OrderNo = #OrderNo
AND SROI.SKU = #SKU
AND ProfileID = #ProfileID
GROUP BY OrderNo, SKU
END
END
In the context of an IF/ELSE Procedural batch
It doesn't make any difference whatsoever. It literally takes 0.00 MS to determine if a value is blank or unknown, it takes the 0.00MS to determine if ISNULL(#SKU, '') = ''. If there is a difference it would likely be measured in nanoseconds IMO. Again, this in the context of a procedural batch because the statement is only being evaluated once.
In the context of an FILTER (e.g. ON, WHERE or HAVING CLAUSE)
Here the difference is actually enormous, it cannot be understated. This is tricky to explain with parameters and variables involved, so, for brevity, I show you an example with this sample data:
IF OBJECT_ID('tempdb..#things','U') IS NOT NULL DROP TABLE #things;
SELECT TOP (10000) Txt = SUBSTRING(LEFT(NEWID(),36),1,ABS(CHECKSUM(NEWID())%x.N))
INTO #things
FROM (VALUES(1),(30),(40),(NULL)) AS x(N)
CROSS JOIN sys. all_columns;
UPDATE #things SET Txt = NEWID() WHERE txt = '0';
CREATE NONCLUSTERED INDEX nc_things1 ON #things(Txt);
The following queries will find rows that either do or do not contain blanks or nulls
-- Finding things that are blank or NULL
SELECT t.Txt
FROM #things AS t
WHERE t.Txt IS NULL OR t.Txt = '';
-- Finding things that are NOT blank or NULL
SELECT t.Txt
FROM #things AS t
WHERE NOT(t.Txt IS NULL OR t.Txt = '');
SELECT t.Txt
FROM #things AS t
WHERE t.Txt > '';
-- Finding things that are blank or NULL
SELECT t.Txt
FROM #things AS t
WHERE ISNULL(t.Txt,'') = '';
-- Finding things that are NOT blank or NULL
SELECT t.Txt
FROM #things AS t
WHERE ISNULL(t.Txt,'') <> '';
The first three queries are SARGable, the last two are not because of ISNULL. Even though there's an index to help me, the ISNULL renders it useless here. It's the difference between asking someone to look in a phone book for everyone whose name begins with "A" and finding everyone who's name ends with "A".
SARGable predicates allow a query to seek a portion of an index where non-SARGable predicates force a query to scan the entire table REGARDLESS of the how many matching rows exist (if any). When you are dealing with millions/billions of rows joined to many other tables the difference can be a query that runs in seconds to one that, in some cases, may run for hours or even weeks (I've seen a few).
EXECUTION PLANS:
Note that this last one WHERE t.Txt > '' will work too. Any non-null text value is > '' and if t.Txt was NULL then it will also evaluate to false. I include this because this expression works for filtered indexes. The only catch is you can't use it on a text field where Implicit conversion can transform this into the number 0 or less. Note these queries:
IF '' = 0 PRINT 'True' ELSE PRINT 'False'; -- Returns True
IF '' = '0' PRINT 'True' ELSE PRINT 'False'; -- Returns False
IF '' > -1 PRINT 'True' ELSE PRINT 'False'; -- Returns True
IF '' > '-1' PRINT 'True' ELSE PRINT 'False'; -- Returns False
IF #SKU IS NULL OR #SKU ='' is checking null and blank both. In second case if Isnull(#sku,'') you are checking null and assigning '' for null value. Both are different cases.
Which is faster (ISNULL(#SKU, '') = '') or (#SKU IS NULL OR #SKU =
'')
It really doesn't matter in this case. If you were comparing against a column then (SKU IS NULL OR SKU = '') would be preferable as it can use an index but any difference for a single comparison against a variable will be in the order of microseconds and dwarfed by the execution times of the SELECT statements.
To simplify the IF statement I'd probably invert it anyway as below
IF #SKU <> '' --Not null or empty string
BEGIN
SELECT OrderNo, SKU, ISNULL(SUM(Quantity), 0) Quantity, ISNULL(SUM(Amount), 0) Amount
FROM StoreRefundOrder SRO
INNER JOIN StoreRefundOrderItem SROI ON SRO.ID = SROI.RefundOrderID
WHERE SRO.OrderNo = #OrderNo
AND SROI.SKU = #SKU
AND ProfileID = #ProfileID
GROUP BY OrderNo, SKU
END
ELSE
BEGIN
SELECT OrderNo, SKU, ISNULL(Quantity, 0) Quantity, ISNULL(Amount, 0) Amount
FROM StoreRefundOrder SRO
INNER JOIN StoreRefundOrderItem SROI ON SRO.ID = SROI.RefundOrderID
WHERE SRO.OrderNo = #OrderNo
AND ProfileID = #ProfileID
END
A little long for a comment.
As has been noted by many already, in this case your two options don't really make any appreciable difference. But in the future, when you think of a couple of different ways to code something, there are standard practices you can implement quickly and easily to test for yourself.
At the top of your code block, add this command:
SET STATISTICS TIME, IO ON;
You can use either TIME or IO, but I almost always want to see both, so I always turn them both on at the same time.
The output from this addition will show up in your Messages window after your query or queries run and will give you tangible information about which method is faster, or causes less stress on the SQL Server engine.
You'll want to run a few tests with each option. Warm cache / cold cache especially, but a few iterations is best one way or the other to get an average or eliminate outlier results.
I'm weird, so I always close my code block with:
SET STATISTICS TIME, IO OFF;
But strictly speaking that's unnecessary. I just have a thing about resetting anything I change, just to avoid any possibility of forgetting to reset something that will matter.
Kendra Little has an informative blog post on using STATISTICS.
I have a stored procedure with 2 parameters: #Name and #Surname which have to be set by an external source.
I need the stored procedure to return values based on what parameter has been set. It should "ignore" null parameters and only take set parameters into account.
SELECT *
FROM ParticipantNames
WHERE -- The if below should go here...
if(#Name iS NOT NULL) - find the row whose [NAME] column LIKE #Name else skip name comparison.
if(#Surname iS NOT NULL) - find the row whose [SURNAME] column LIKE #Surname else skip surname comparison.
if(#Name AND #Surname is NOT NULL) - find the row whose [NAME] column LIKE #Name AND whose [SURNAME] column LIKE #Surname
The following works if all the parameters have been set but return 0 rows when one parameter has not been set.
;WITH tempSearch AS
(
SELECT *
FROM ParticipantNames f
WHERE ((f.Name LIKE CASE WHEN #Name IS NULL THEN NULL ELSE #Name END)
AND (f.Surname LIKE CASE WHEN #Surname IS NULL THEN NULL ELSE #Surname END)
...
I know it returns 0 rows because of the AND clause, but I don't know how to fix it.
Any advice on how to achieve this?
It seems you are trying to use a 'default' pattern on your queries.
The problem here is that you are projecting NULL out of the CASE WHEN, which then leads to WHERE Column LIKE NULL, which won't work.
What you can do is subtitute the actual column back in, like this:
SELECT *
FROM ParticipantNames f
WHERE ((f.Name LIKE CASE WHEN #Name IS NULL THEN Name ELSE #Name END)
AND (f.Surname LIKE CASE WHEN #Surname IS NULL THEN Surname ELSE #Surname END)
This leads to WHERE Column LIKE Column in the default case, which will work.
Note however that queries like these are hard on the query optimizer, given the conditional use of a predicate. There are several similar discussions on how best to approach the application of 'optional parameters'.
Issue
I want to write a query that will select all from a table where my string value is equal to two columns concatenated together.
This is plain English version:
#MYSTRING varchar(50)
SELECT ALL FROM [FFLOCNP] WHERE COLUMN1 + COLUMN2 = #MYSTRING
I have tried to use the COALESCE but i have never used this before and it is returning me an error:
#CODE varchar(50)
SELECT * FROM [dbo].[FFLOCNP] WHERE COALESCE([LOCTRY], '') || COALESCE([LOCLCN], '') = #CODE
you have to use ISNULL for this.
Use below query may be it helps you.
SELECT * FROM [FFLOCNP] WHERE ISNULL(COLUMN1,'') + ISNULL(COLUMN2,'') = #MYSTRING
Be careful, when using ISNULL instead of COALESCE. ISNULL limits the returned value to the datatype of the first input parameter. In the given example column V1 will be implicitly defined with nvarchar(1), because the longest text in column V1 consists of only one character. ISNULL(V1, [param2]) will therefor return always a one character long string, regardless of the length of the second parameter. In your case ISNULL would work, if you wanted to replace a NULL with an empty string. If you wanted to replace a NULL with a longer string then you MUST use COALESCE instead of ISNULL. COALESCE returns the full string in parameter 2 regardless of the datatype of parameter 1. Apart from this COALESCE is standard SQL whereas ISNULL is a flavor of SQL-Server. Standard SQL should be preferred to T-SQL flavor to get more portable code.
WITH CTE_SRC AS
(
SELECT
[V1]
,[V2]
FROM
(VALUES
(N'A', N'BB')
,(NULL, N'BB')
,(N'A', NULL)
) T([V1],[V2])
)
SELECT
ISNULL([V1], '1234') AS [ISNULL]
,COALESCE([V1], '123') AS [COALESCE]
FROM
CTE_SRC
Result
ISNULL COALESCE
------ --------
A A
1 123
A A
Facing problem for generating SQL Server Query
In the Following query dynamic conditions are added to check whether value is null or not
Select *
From tblEmployees
where EmployeeName = Case
When #EmployeeName Is Not Null
Then #EmployeeName
Else EmployeeName
End
But I need to add IN () Conditions and the parameter with in the IN () could be null or blank also ,if the parameter /string which is passed to the IN condition is blank then i donot want to add that condition in the query.
So how can i Achieve this.A helping hand will be very useful for me.
Thanks and Regards,
D.Mahesh
Depending on value of your parameter (blank of not), you can create SQL string accordingly.
DECLARE #sqlCommand VARCHAR(1000)
IF(ISNULL(#YourParameter,'')='')
#sqlCommand = 'your query goes here'
ELSE
#sqlCommand = 'your query goes here'
and then, run it using dynamic query execution
EXEC (#sqlCommand)
If not dynamic query then,
SELECT ....
FROM ....
WHERE CASE WHEN ISNULL(#YourParameter,'')='' THEN '' ELSE EmployeeName END IN (ISNULL(#YourParameter,''))
See if this works...
I think the Dynamic query is the best solution, however you could put the "IS NULL" and "IS BLANK" condition in OR with your IN clause.
Something like that
Select *
From tblEmployees
where #EmployeeName is null or EmployeeName in (#EmployeeName)
When #EmployeeName is null, your IN clause will be ignored
If i get this right you have #EmployeeName = 'Name1,Name2,Name3' and you want to get the employees that is named Name1 or Name2 or Name3, also the variable #EmployeeName can be null or contain an empty string.
Instead of using IN you can split the string #EmployeeName on , and store it in a table variable or temporary table. Then you can use that table in a join against tblEmployees to get the rows you need.
There are a lot of posts in S.O. about how to split a string. Here is one recent variant.
Group by sql query on comma joined column
This will work for SQL Server 2005 or later.
declare #EmployeeName varchar(100) = 'Name2,Name3,Name5'
-- Null or empty will have a comma
set #EmployeeName = coalesce(#EmployeeName, '') + ','
-- cteNames splits the string to rows
;with cteNames
as
(
select
left(#EmployeeName, charindex(',', #EmployeeName)-1) as Name,
right(#EmployeeName, len(#EmployeeName)-charindex(',', #EmployeeName)) as EmployeeName
union all
select
left(EmployeeName, charindex(',', EmployeeName)-1) as Name,
right(EmployeeName, len(EmployeeName)-charindex(',', EmployeeName)) as EmployeeName
from cteNames
where charindex(',', EmployeeName) > 1
)
select E.*
from tblEmployees as E
inner join cteNames as N
on E.Name = N.Name or
#EmployeeName = ','
-- #EmployeeName = ',' will give you all names when #EmployeeName is null of empty
I have a table and the columns on this table contains empty spaces for some records. Now I need to move the data to another table and replace the empty spaces with a NULL value.
I tried to use:
REPLACE(ltrim(rtrim(col1)),' ',NULL)
but it doesn't work. It will convert all of the values of col1 to NULL. I just want to convert only those values that have empty spaces to NULL.
I solved a similar problem using NULLIF function:
UPDATE table
SET col1 = NULLIF(col1, '')
From the T-SQL reference:
NULLIF returns the first expression if the two expressions are not equal. If the expressions are equal, NULLIF returns a null value of the type of the first expression.
Did you try this?
UPDATE table
SET col1 = NULL
WHERE col1 = ''
As the commenters point out, you don't have to do ltrim() or rtrim(), and NULL columns will not match ''.
SQL Server ignores trailing whitespace when comparing strings, so ' ' = ''. Just use the following query for your update
UPDATE table
SET col1 = NULL
WHERE col1 = ''
NULL values in your table will stay NULL, and col1s with any number on space only characters will be changed to NULL.
If you want to do it during your copy from one table to another, use this:
INSERT INTO newtable ( col1, othercolumn )
SELECT
NULLIF(col1, ''),
othercolumn
FROM table
This code generates some SQL which can achieve this on every table and column in the database:
SELECT
'UPDATE ['+T.TABLE_SCHEMA+'].[' + T.TABLE_NAME + '] SET [' + COLUMN_NAME + '] = NULL
WHERE [' + COLUMN_NAME + '] = '''''
FROM
INFORMATION_SCHEMA.columns C
INNER JOIN
INFORMATION_SCHEMA.TABLES T ON C.TABLE_NAME=T.TABLE_NAME AND C.TABLE_SCHEMA=T.TABLE_SCHEMA
WHERE
DATA_TYPE IN ('char','nchar','varchar','nvarchar')
AND C.IS_NULLABLE='YES'
AND T.TABLE_TYPE='BASE TABLE'
A case statement should do the trick when selecting from your source table:
CASE
WHEN col1 = ' ' THEN NULL
ELSE col1
END col1
Also, one thing to note is that your LTRIM and RTRIM reduce the value from a space (' ') to blank (''). If you need to remove white space, then the case statement should be modified appropriately:
CASE
WHEN LTRIM(RTRIM(col1)) = '' THEN NULL
ELSE LTRIM(RTRIM(col1))
END col1
Maybe something like this?
UPDATE [MyTable]
SET [SomeField] = NULL
WHERE [SomeField] is not NULL
AND LEN(LTRIM(RTRIM([SomeField]))) = 0
here's a regex one for ya.
update table
set col1=null
where col1 not like '%[a-z,0-9]%'
essentially finds any columns that dont have letters or numbers in them and sets it to null. might have to update if you have columns with just special characters.