In SQL, how do I allow for nulls in the parameter? - sql

I have what seems to be a really easy SQL query I can't figure out and its driving me nuts. This is SQL 2008. Basically, there is a status field where the can pick "pending", "satisfied" or all. If they send in "pending" or "satisfied" there's no problem. But when they pick all I'm having problems. Mostly because I can't figure out how to get the records where this field is null to show up (because it has to be 'is null' instead of '= null'. (This is the way the data will come over; I have no control over that.)
The code I've been using does not work for nulls.
SELECT *
FROM Payment_Table
where Payment.Status_code = #status_id

You can try
SELECT Col1, Col2,...,Coln --Required Columns
FROM Payment_Table
where (Payment.Status_code = #status_id OR #status_id IS NULL)

Try:
SELECT *
FROM Payment_Table
WHERE Payment.Status_code = ISNULL(#status_id, Status_code)
This will return all payments.

Try
WHERE
((#status_id IS NULL) OR (Payment.Status_code = #status_id))

WHERE Payment_Table.Status = ISNULL(#StatusID, Payment_Table.Status)
It usually works better then OR
Edit: you want to select rows where Payment_Table.Status = NULL when #StatusID = NULL!!
SELECT * FROM Payment_Table where Payment.Status_code = #status_id
UNION ALL
SELECT * FROM Payment_Table where Payment.Status_code IS NULL AND #StatusID IS NULL
OR
...
WHERE
Payment_Table.Status #StatusID
OR
(Payment.Status_code IS NULL AND #StatusID IS NULL)

You can use coalesce or IsNull on your Payment.StatusCode field, this will allow you to do a substitution for null with a specific value.

SELECT *
FROM Payment_Table
WHERE (Payment.Status_code is null or Payment.Status_code = #status_id)

There are many approaches depending which version of sql server you are using. This articles has an in-depth description: Dynamic Search Conditions in T-SQL

The best way to do this is below. However, you MUST watch out for parameter sniffing. This will become an issue as your table gets bigger and will affect your execution times randomly. This is an annoying issue that can pop up. Use the code below.
CREATE PROCEDURE GetPaymentStatus
#StatusID varchar(50)=NULL
AS
BEGIN
SET NOCOUNT ON;
DECLARE #StatusId_local varchar(50)
SET #StatusID_local = #StatusId
SELECT MyField1, MyField2
FROM Payment_Table
WHERE Payment_Table.Status=#StatusID_local
OR (#StatusID_local IS NULL AND Payment_Table.Status IS NULL)
END
Check out this article or google sql parameter sniffing for more info.

Related

SQL Script Needs improvements, Could use some tips

I recently got a test to figure out how to optimize this SQL Script, I have never made a script, and only create queries to get data at my work. This is the Script.
The tables have valid data and joins, the #GUID is already declared and has NVARCHAR values.
This is the SQL script
I am pretty new to this, I need to optimize this script, and from what I know, its better to Join the query instead of using Sub-queries. (correct me if I am wrong)
From What I understand from this script, it is asking information from 2 different Databases
CUSTOMER_CARDNUMBER and PROFILE
I can't see the information in these Fake databases, but that is not part of this test.
In these databases it is asking for multiple information GENERATED_CARDNUMBER, and CUSTOMER_CARDNUMBER with the values = #GUID (which I don't completely understand)
IT WIll Begin a Update if the REVISION_Status = A, if GUID = #GUID what I understand this Script is looking for in both databases. and If it does not find it, it will set a REVISION_STATUS with a Date if the query does not find GUID = #GUID; ( Please correct me if wrong)
I could use help, in improving this script, how and why.. even if there are little mistakes.
Thanks!!
IF EXISTS
(
SELECT *
FROM CUSTOMER_CARDNUMBER
WHERE GENERATED_CARDNUMBER IN
(
SELECT CARDNUMBER
FROM Profile
WHERE Cardnumber IN
(
SELECT GENERATED_CARDNUMBER
FROM CUSTOMER_CARDNUMBER
WHERE CUSTOMER_CARDNUMBER = #GUID
)
)
)
BEGIN
UPDATE GUID_ACTION
SET
REVISION_STATUS = 'A',
REVISION_DATE_CREATED = GETDATE()
WHERE GUID = #GUID;
END;
ELSE
BEGIN
UPDATE GUID_ACTION
SET
REVISION_STATUS = 'F',
REVISION_DATE_CREATED = GETDATE()
WHERE GUID = #GUID;
END;
``UPDATE GUID_ACTION
SET REVISION_STATUS =
CASE WHEN (0 < select count(*) from CUSTOMER_CARDNUMBERCUSTOMER_CARDNUMBER
inner join PROFILE
ON PROFILE.Cardnumber = CUSTOMER_CARDNUMBER.GENERATED_CARDNUMBER
AND PROFILE.Cardnumber = #GUID
)
THEN 'A'
ELSE 'F'
END;
,GUID_ACTION.REVISION_DATE_CREATED = GETDATE()
WHERE CUSTOMER_CARDNUMBER.GUID = #GUID;
This is a another more cleaner way, the query script with the subquery was difficult to understand with no insight of the tables or declared #GUID

Adding to WHERE clause conditions using CASE

I have a stored procedure that accepts an optional #ID param. When the param is passed in, I want the WHERE statement to include something like id = #ID, otherwise, when #ID is null, I don't want it to be filtered.
For example:
#ID BIGINT = NULL
SELECT * from myTable
WHERE
CASE
WHEN #ID IS NOT NULL THEN mytable.id = #ID
END
I am running this in SQL server 2016 and it says bad syntax near mytable.id = #ID. Can CASE be used in this way or should I try a different SQL method for this?
The only other option I considered to accomplish this was by using IF conditions in my stored procedure, but that didn't seem possible either based on my searches.
CASE is an expression, not a statement. It is not used to control flow like this and it will not work.
Your logic would need to be something like this.
Where mytable.id = ISNULL(#ID, mytable.id)
I should caution you that this pattern can lead to some poor performance. For a more detailed explanation and some other options you should check out this article. http://www.sqlinthewild.co.za/index.php/2009/03/19/catch-all-queries/
A bad-performance approach would be:
WHERE ISNULL(#ID,mytable.id) = mytable.id
A better-performance approach would be:
if(#ID IS NULL)
select * from ... without the WHERE condition
else
do your query with the condition mytable.id = #ID
Or build the query dynamically in the stored proc and execute it through sp_executesql passing parameters
Note: If the table is small enough, stick to simplicity and use the first option.

SQL Search using case or if

Everyone has been a super help so far. My next question is what is the best way for me to approach this... If I have 7 fields that a user can search what is the best way to conduct this search, They can have any combination of the 7 fields so that is 7! or 5040 Combinations which is impossible to code that many. So how do I account for when the User selects field 1 and field 3 or they select field 1, field 2, and field 7? Is there any easy to do this with SQL? I dont know if I should approach this using an IF statement or go towards a CASE in the select statement. Or should I go a complete different direction? Well if anyone has any helpful pointers I would greatly appreciate it.
Thank You
You'll probably want to look into using dynamic SQL for this. See: Dynamic Search Conditions in T-SQL and Catch-all queries for good articles on this topic.
Select f1,f2 from table where f1 like '%val%' or f2 like '%val%'
You could write a stored procedure that accepts each parameter as null and then write your WHERE clause like:
WHERE (field1 = #param1 or #param1 is null)
AND (field2 = #param2 or #param2 is null) etc...
But I wouldn't recommend it. It can definitely affect performance doing it this way depending on the number of parameters you have. I second Joe Stefanelli's answer with looking into dynamic SQL in this case.
Depends on:
how your data looks like,
how big they are,
how exact result is expected (all matching records or top 100 is enough),
how much resources has you database.
you can try something like:
CREATE PROC dbo.Search(
#param1 INT = NULL,
#param2 VARCHAR(3) = NULL
)
AS
BEGIN
SET NOCOUNT ON
-- create temporary table to keep keys (primary) of matching records from searched table
CREATE TABLE #results (k INT)
INSERT INTO
#results(k)
SELECT -- you can use TOP here to norrow results
key
FROM
table
-- you can use WHERE if there are some default conditions
PRINT ##ROWCOUNT
-- if #param1 is set filter #result
IF #param1 IS NOT NULL BEGIN
PRINT '#param1'
;WITH d AS (
SELECT
key
FROM
table
WHERE
param1 <> #param1
)
DELETE FROM
#results
WHERE
k = key
PRINT ##ROWCOUNT
END
-- if #param2 is set filter #result
IF #param2 IS NOT NULL BEGIN
PRINT '#param2'
;WITH d AS (
SELECT
key
FROM
table
WHERE
param2 <> #param2
)
DELETE FROM
#results
WHERE
k = key
PRINT ##ROWCOUNT
END
-- returns what left in #results table
SELECT
table.* -- or better only columns you need
FROM
#results r
JOIN
table
ON
table.key = r.k
END
I use this technique on large database (millions of records, but running on large server) to filter data from some predefined data. And it works pretty well.
However I don't need all matching records -- depends on query 10-3000 matching records is enough.
If you are using a stored procedure you can use this method:
CREATE PROCEDURE dbo.foo
#param1 VARCHAR(32) = NULL,
#param2 INT = NULL
AS
BEGIN
SET NOCOUNT ON
SELECT * FROM MyTable as t
WHERE (#param1 IS NULL OR t.Column1 = #param1)
AND (#param2 IS NULL OR t.COlumn2 = #param2)
END
GO
These are usually called optional parameters. The idea is that if you don't pass one in it gets the default value (null) and that section of your where clause always returns true.

SQL Query...nulls hosing me

I tried to ask this question before but I don't think I explained myself very well. So here it is: asp.net 2.0 app hitting a SQL 2008 backend. This seems simple but I can't get it. 1 table. The user selects a status. The query should return all records = the chosen status only. If the user select "All Status", then ALL records should be returned, including those with a status = null (which is the part that is hosing me).
Ex:
CASE 1: User selects Status = "Satisfied"; ONLY satisfied records are return
CASE 2: User selects All Status = everything is returned, satisfied AND nulls and anything else
I tried passign in a wildcard but this doesn't return nulls. I tried dynamically buildign the query but I would like to avoid it.
For the case where you want them all, how about: (am I missing something?)
SELECT * FROM <tablename>
SELECT * FROM <tablename> WHERE Status = '*' OR Status IS NULL
...or '%' or whatever wildcard your SQL implementation uses.
...or if you really have no other conditions following this, just select *...
Try this in a stored procedure.
--Pass in #status as a parameter
DECLARE #Status varchar(100)
IF #Status = 'All Status'
BEGIN
SELECT * FROM tablename
END
ELSE
BEGIN
SELECT * FROM tablename where statusfield = #Status
END
One option is
declare #status varchar(50)
SELECT * FROM <tablename> WHERE (#status is null) or (Status = #status)
if you pass null in for the #status parameter then it will return all records. If you pass 'satisfied' or whatever then it will return just those matchng records.
If doing this in SQL 2008, be sure you have SP1 and Cumulative Update 5 installed. Further, I would recommend adding the WITH RECOMPILE option to the procedure. Under those conditions it will be as performant as embedded SQL or even using unions.
See the following article for an indepth discussion of the myriad of ways to perform searching in SQL 2008: Dynamic Search Conditions in T-SQL
The problem is that SQL's = operator always returns NULL when one of the operands is NULL, so using status = '%' indeed doesn't work. The best method is to just not include a condition on status if you want all of them. You can add extra NULL tests to the query, but that again is building it dynamically, I don't see a way to avoid that...
basically your statement will be for 'Statisfied'
SELECT * FROM testtab WHERE
COALESCE(statuscolumn, '') LIKE '%Statified'
for 'All Status' it will be
SELECT * FROM testtab WHERE
COALESCE(statuscolumn, '') LIKE '%'
you could use this statment and if selection is 'All Status' then pass a '' for the #status from your UI
SELECT * FROM testtab WHERE
COALESCE(statuscolumn, '') LIKE '%' || #status
Or you can use this one and when you pass the selection from UI make sure it has a '%' (wild char) appended to your status when it not 'All Status'. When its 'All Status' just pass '%' for the #status
SELECT * FROM testtab WHERE
COALESCE(statuscolumn, '') LIKE #status
oh your db is mssql? :) then you will need to replace the collace(statuscolumn, '') with isnull(statuscolumn, '').
Just skip the where clause or the part that is about the status field, example:
SELECT * from table_1 Where status = 'Satisfied'
and
SELECT * from table_1
When you want all records, you have to exclude STATUS from your WHERE clause (or use a UNION and a select statement where STATUS IS NULL).
Depending on what version of SQL you are using, you might be able to use an IF..ELSE... statement.
IF Status='ALL' THEN
... A SELECT statement where STATUS is NOT included in the WHERE
ELSE
... A SELECT statement that has a WHERE with only the status you are looking for
SELECT * FROM <tablename> WHERE isnull(Status,'*') = '*'
Assuming you're passing a variable to an SP:
SELECT * FROM Table WHERE Status CASE #status WHEN 'All status' THEN Status ELSE #status END
Otherwise, you need to concatenate in the selected value within quotes at both places where it currently says #status
The idea is what to do when the users chooses 'All Status'. By setting the param to NULL, you can use the isnull and then each [status] field just needs to equal itself. I've used ISNULL to set to '' to avoid having NULL = NULL>
declare #param_choice varchar(25)
if #param_choice = 'All Status'
Begin
#param_choice = NULL
End
-- get your results
Select * from Some_Table
Where IsNull([Status], '') = IsNull(#param_choice, IsNull([Status], ''))
You'll get the best performance from:
IF #status IS NULL
BEGIN
SELECT t.*
FROM TABLE t
END
ELSE
BEGIN
SELECT t.*
FROM TABLE t
WHERE t.status = #status
END
The next option is to use:
SELECT t.*
FROM TABLE t
WHERE (#status IS NULL OR t.status = #status)
...but that is not sargable.
I do not believe that anyone suggested using a UNION.
SELECT t.*
FROM TABLE t
WHERE (#status IS NULL)
UNION ALL
SELECT t.*
FROM TABLE t
WHERE (t.status = #status)
If #status is NULL, then the first query in the union is executed, and t.status = #status is clearly always false, so the second query in the union is not executed at all.
If #status is not null, then the first query in the union is not exected at all, and the second one is.
Importantly, since ISNULL, COALESCE or a function are not used on t.status or on #status, then if there is an index on status, it can be used. That is, the predicate is SARGABLE.
And I used UNION ALL (instead of UNION) to prevent a SORT and DISTINCT operation that can be very slow.

How would you build one Select stored procedure to handle any request from a table?

I want to build a single select stored procedure for SQL 2005 that is universal for any select query on that table.
**Columns**
LocationServiceID
LocationID
LocationServiceTypeID
ServiceName
ServiceCode
FlagActive
For this table I may need to select by LocationServiceID, or LocationID, or LocationServiceTypeID or ServiceName or a combination of the above.
I'd rather not have a separate stored procedure for each of them.
I assume the best way to do it would be to build the 'WHERE' statement on NOT NULL. Something like
SELECT * FROM LocationServiceType WHERE
IF #LocationID IS NOT NULL (LocationID = #LocationID)
IF #LocationServiceID IS NOT NULL (LocationServiceID = #LocationServiceID)
IF #LocationServiceTypeID IS NOT NULL (LocationServiceTypeID = #LocationServiceTypeID)
IF #ServiceName IS NOT NULL (ServiceName = #ServiceName)
IF #ServiceCode IS NOT NULL (ServiceCode = #ServiceCode)
IF #FlagActive IS NOT NULL (FlagActive = #FlagActive)
Does that make sense?
here is the most extensive article I've ever seen on the subject:
Dynamic Search Conditions in T-SQL by Erland Sommarskog
here is an outline of the article:
Introduction
The Case Study: Searching Orders
The Northgale Database
Dynamic SQL
Introduction
Using sp_executesql
Using the CLR
Using EXEC()
When Caching Is Not Really What You Want
Static SQL
Introduction
x = #x OR #x IS NULL
Using IF statements
Umachandar's Bag of Tricks
Using Temp Tables
x = #x AND #x IS NOT NULL
Handling Complex Conditions
Hybrid Solutions – Using both Static and Dynamic SQL
Using Views
Using Inline Table Functions
Conclusion
Feedback and Acknowledgements
Revision History
First of all, your code will not work. It should look like this:
SELECT * FROM LocationServiceType WHERE
(#LocationID IS NULL OR (LocationID = #LocationID)
... -- all other fields here
This is totally valid and known as 'all-in-one query'. But from a performance point of view this is not a perfect solution as soon as you don't allow SQL Server to select optimal plan. You can see more details here.
Bottom line: if your top priority is 'single SP', then use this approach. In case you care about the performance, look for a different solution.
SELECT *
FROM LocationServiceType
WHERE LocationServiceID = ISNULL(#LocationServiceID,LocationServiceID)
AND LocationID = ISNULL(#LocationID,LocationID)
AND LocationServiceTypeID = ISNULL(#LocationServiceTypeID,LocationServiceTypeID)
AND ServiceName = ISNULL(#ServiceName,ServiceName)
AND ServiceCode = ISNULL(#ServiceCode,ServiceCode)
AND FlagActive = ISNULL(#FlagActive,FlagActive)
If a null value is sent in it will cancel out that line of the where clause, otherwise it will return rows that match the value sent in.
What I've always done is is set the incoming parameters to null if should be ignored in query
then check variable for null first, so if variable is null condition short circuits and filter is not applied. If variable has value then 'or' causes filter to be used. Has worked for me so far.
SET #LocationID = NULLIF(#LocationID, 0)
SET #LocationServiceID = NULLIF(#LocationServiceID, 0)
SET #LocationServiceTypeID = NULLIF(#LocationServiceTypeID, 0)
SELECT * FROM LocationServiceType WHERE
(#LocationID IS NULL OR LocationID = #LocationID)
AND (#LocationServiceID IS NULL OR LocationServiceID = #LocationServiceID)
AND (#LocationServiceTypeID IS NULL OR #LocationServiceTypeID = #LocationServiceTypeID)
etc...