WHERE clause using values that could be NULL - sql

I need to do something like this:
DECLARE #firstname VARCHAR(35)
DECLARE #lastname VARCHAR(35)
SELECT *
FROM Customers
WHERE Firstname = #firstname AND Lastname = #lastname
The problem is that #firstname and #lastname could sometimes be NULL. In those cases, the query above would fail to find matching rows because NULL is never equal to NULL.
If one of these variables is NULL, I'd like to return a match if the corresponding value in the database is also NULL. But, of course, SQL Server uses IS to compare for NULL. And if either value is NULL in the example above, it is not considered a match.
Is there any way to accomplish this?

Just use AND/OR logic e.g.
SELECT *
FROM Customers
WHERE ((Firstname IS NULL AND #firstname IS NULL) OR Firstname = #firstname)
AND ((Lastname IS NULL AND #lastname IS NULL) OR Lastname = #lastname);

You can leverage INTERSECT in a correlated subquery to do this. This works because set-based operations compare NULLs as equal.
The compiler will automatically compile this down to an IS comparison, there should not be any performance hit.
DECLARE #firstname VARCHAR(35)
DECLARE #lastname VARCHAR(35)
SELECT *
FROM Customers c
WHERE EXISTS (SELECT c.Firstname, c.Lastname
INTERSECT
SELECT #firstname, #lastname)
The logic is: for every row, create a one-row virtual table with the two values, intersect it with the two variables and there must be a result.
For a <> semantic, change EXISTS to NOT EXISTS, rather than changing to EXCEPT, I find the former optimizes better.
All credit to Paul White for this trick.

Related

Why does my SQL query return a record that doesn't match the where clause?

This query returns a result of one record where both the [E-mail] and [E-mail 2] are equal to 'e'. Most definitely the result of a lazy salesman. Any idea why this is the case though?
My query is as shown below so I should only get records where the email is equal to 'emailfromsomeone#hotmail.com' correct?
declare #email as varchar
set #email = 'emailfromsomeone#hotmail.com'
select
C.[No_],
C.[First Name] firstname,
C.[Surname] lastname,
C.[E-Mail] as email,
C.[E-Mail 2] as email_2,
C.[GDPR Opt-in] as GDPR_opt_in,
C.[Salesperson Code] as sales_person
from
[Contact] as C
where
lower(C.[E-Mail]) = lower(#email)
or lower(C.[E-Mail 2]) = lower(#email)
You've declared #email to be a VARCHAR, meaning a single VARCHAR. When you're setting the #email variable, it's being truncated to just the first character.
Try changing VARCHAR to something like VARCHAR(100).
I got it. So i declared the variable like so and then it worked
declare #email VARCHAR(255)
sorry for bothering you stakoverflow!

SQL Update if parameter is not null or empty

I searched some ways to check if a SQL Server parameter is not null or empty but I'm not sure what's the best way to use this when updating several columns:
I had this code at first that was updating without checking for empty or Null values:
UPDATE [Users]
SET FirstName = #firstname, City = #city, Address = #address, ....
WHERE ID = #iduser
Then I added an IF clause before updating, it is working this way but I'm not sure if that's the best way to do it, it is going to be long if I have to update several columns.
--Check if parameter is not null or empty before updating the column
IF (#firstname IS NOT NULL AND #firstname != '')
UPDATE [Users]
SET FirstName = #firstname
WHERE ID = #iduser
IF (#city IS NOT NULL AND #city != '')
UPDATE [Users]
SET City = #city
WHERE ID = #iduser
...
...
If the value is Null or Empty I don't need to update, just keep the original value in the database.
not sure what you are trying to achieve if it is blank, but I would try using IsNull() I don't think there is an IsBlank(), but it shouldn't be too hard to write yourself
Using just IsNull your query would look something like...
Update [Users]
set FirstName = IsNull(#FirstName, FirstName),
City = IsNull(#City, City)
....
Where ...
this will Update the row with the param value if they are NOT null, otherwise update it to itself aka change nothing.
Update [Users]
set FirstName = iif(ISNULL(ltrim(rtrim(#FirstName)), '')='', FirstName, #FirstName),
....
Where ...

sql full text search with nullable parameter

We would like to work with full-text search but keep the incoming parameters in our procedure null-able. In this example #Street can have a value like Heuvel or can be NULL.
Working this out in a query is something that doesn't work.
heuvel* gives all the rows that are expected.
* gives zero rows while other threads on forums say that it will give all rows
street = isnull(#street, street) gives all rows
Knowing all the above I would think this query would work. It does but it's VERY slow.
When I execute the or clause separately then the query is fast.
Declare #Street nvarchar(50)= '"heuvel*"'
Declare #InnerStreet nvarchar(50) = '"'+ isnull(#Street, '') +'*"';
SELECT *
FROM Address
WHERE street = isnull(#street, street) or CONTAINS(street, #InnerStreet)
Now the question is, how to work with full-text search and null-able parameters?
Try this. #street is either NULL or has a value.
declare #streetWildcard nvarchar(50) = '"' + #street + '*"'
SELECT *
FROM Address
WHERE (#street is NULL) or CONTAINS(street, #streetWildcard)
If the performance is still bad then the execution plan may not be short circuiting the WHERE condition. In that case, try this instead.
if (#street is NULL) begin
SELECT *
FROM Address
end
else begin
declare #streetWildcard nvarchar(50) = '"' + #street + '*"'
SELECT *
FROM Address
WHERE CONTAINS(street, #streetWildcard)
end

Use ISNULL or "LIKE"

I have a stored procedure which takes in a few parameters which could be NULL or maybe not.
I can use the ISNULL option if I have a clear value for a parameter, but if I want to use a "LIKE" instead of null I'm not sure how to do it. See below:
Parameters are:
#org_id as int,
#deleted as bit = NULL,
#firstname as char = NULL,
#lastname as char = NULL
Select statement is:
select user_id, firstname,lastname,firstname +' ' + lastname as fullname, email, phone, is_tech, is_admin,is_cust
from users
where users.org_id=#org_id and is_tech=1 and delete_flag=ISNULL(#deleted,delete_flag)
order by lastname,firstname asc
If the firstname and lastname params are not null, but are instead a character e.g."J" ,how can I write something similar to the delete_flag clause ..and firstname like 'J%' but if not use null?
Any ideas?
You can use this:
WHERE field LIKE #param OR #param IS NULL

Efficiently Handling Multiple Optional Constraints in Where Clause

This is somewhat of a sequel to Slow Exists Check. Alex's suggestion works and successfully avoids code repetition, but I still end up with a second issue. Consider the example below (From AlexKuznetsov). In it, I have two branches to handle 1 contraint. If I had 2 optional constraints, I would end up with 4 branches. Basically, the number of branches increases exponentially with the number of constraints.
On the other hand, if I use a Multi-Statement Table-valued function or otherwise use temporary tables, the SQL query optimizer is not able to assist me, so things become slow. I am somewhat distrustful of dynamic SQL (and I've heard it is slow, too).
Can anyone offer suggestions on how to add more constraints without adding lots of if statements?
Note: I have previously tried just chaining x is null or inpo = #inpo together, but this is very slow. Keep in mind that while the inpo = #inpo test can be handled via some sort of indexing black magic, the nullity test ends up being evaluated for every row in the table.
IF #inpo IS NULL BEGIN
SELECT a,b,c
FROM dbo.ReuseMyQuery(#i1)
ORDER BY c;
END ELSE BEGIN
SELECT a,b,c
FROM dbo.ReuseMyQuery(#i1)
WHERE inpo = #inpo
ORDER BY c;
END
Variation Two: 2 constraints:
IF #inpo IS NULL BEGIN
IF #inpo2 IS NULL BEGIN
SELECT a,b,c
FROM dbo.ReuseMyQuery(#i1)
ORDER BY c;
END ELSE BEGIN
SELECT a,b,c
FROM dbo.ReuseMyQuery(#i1)
WHERE inpo2 = #inpo2
ORDER BY c;
END
END ELSE BEGIN
IF #inpo2 IS NULL BEGIN
SELECT a,b,c
FROM dbo.ReuseMyQuery(#i1)
WHERE inpo = #inpo
ORDER BY c;
END ELSE BEGIN
SELECT a,b,c
FROM dbo.ReuseMyQuery(#i1)
WHERE inpo = #inpo AND
inpo2 = #inpo2
ORDER BY c;
END
END
this is the best reference: http://www.sommarskog.se/dyn-search-2005.html
In such cases I use sp_executesql as described in Erland's article: Using sp_executesql
Whenever dynamic SQL is used, missing permissions may be a problem, so I have a real network account for unit testing, I add that account to the actual role, and I impersonate with that real account whenever I test dynamic SQL, as described here: Database Unit Testing: Impersonation
Here's a rough example. Modify the LIKE statements in the WHERE clause depending if you want "starts with" or "contains" or an exact match in your query.
CREATE PROCEDURE dbo.test
#name AS VARCHAR(50) = NULL,
#address1 AS VARCHAR(50) = NULL,
#address2 AS VARCHAR(50) = NULL,
#city AS VARCHAR(50) = NULL,
#state AS VARCHAR(50) = NULL,
#zip_code AS VARCHAR(50) = NULL
AS
BEGIN
SELECT [name],
address1,
address2,
city,
state,
zip_code
FROM my_table
WHERE ([name] LIKE #name + '%' OR #name IS NULL)
AND (address1 LIKE #address1 + '%' OR #address1 IS NULL)
AND (address2 LIKE #address2 + '%' OR #address2 IS NULL)
AND (city LIKE #city + '%' OR #city IS NULL)
AND (state LIKE #state + '%' OR #state IS NULL)
AND (zip_code LIKE #zip_code + '%' OR #zip_code IS NULL)
ORDER BY [name]
END
GO
Select blah from foo
Where (#inpo1 is null or #inpo1 = inpo1)
and (#inpo2 is null or #inpo2 = inpo2)
Apparently this is too slow. Interesting.
Have you considered code generation? Lengthy queries with lots of duplication is only an issue if it has to be maintained directly.
I realise your question may be purely academic, but if you have real world use cases have you considered only providing optimised queries for the most common scenarios?