sql full text search with nullable parameter - sql

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

Related

WHERE clause using values that could be NULL

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.

When using CASE inside a WHERE clause, without ELSE, what is meant by NULL?

declare #Name varchar(45)
SELECT
*
FROM
table law
WHERE
--law.emp = case when isnumeric(#Name) = 1 then #Name else null end
law.emp = case when isnumeric(#Name) = 1 then #Name end
or coalesce(law.lname + ', ' + law.fname, '') like '%' + #Name + '%'
My question here is focusing on the CASE statement when it evaluates to something other than a numeric value...
If #Name is anything other than a numerical value, what is really happening? law.emp = null??? If so, it shouldn't return any results anyway, because the proper syntax is something like where law.emp is null, right?
So, just for clarification, my emp column doesn't actually contain any nulls.
I'm just trying to figure out what is really happening to my WHERE statement when #Name evaluates to something not numeric...
What actual evaluation is being done? Is SQL ignoring the condition?
That's kind of what I want: For SQL not to do anything with law.emp unless #Name is numeric. (I have other conditions that will use the #Name variable if it is text.)
The expression:
law.emp = NULL
always returns NULL. In a where clause, this is treated as false, and the row is not included in the result set.
I much prefer an explicit conversion:
law.emp = try_convert(int, #Name)
Or to whatever type emp is.

Conditional where clause?

I want to apply the conditional where clause That is if my barcode parameter comes null then i want to fetch all the records and if it comes with value then i want to fetch only matching records for the second part i am able to fetch the matching records but i am stuck at fetching the all records in case of null value i have tried as below ,
SELECT item
FROM tempTable
WHERE
((ISNULL(#barcode,0)=1)
// but this is not fetching all the records if barcode is null
OR
ISNULL(#barcode,0!= 1 AND tempTable.barcode LIKE #barcode+'%'))
//THis is working perfect
so any help will be great
I might have misunderstood what you ask, but the logic OR operator might help:
SELECT item
FROM tempTable
WHERE
#barcode IS NULL OR tempTable.barcode LIKE #barcode+'%'
If #barcode is NULL, it returns all the records, and when it is not NULL, it returns all of the records that fulfill the condition LIKE #barcode+'%'
Important
Also, bear in mind that using the OR operator can seemingly cause funny results when used with several complex conditions AND-ed together, and not enclosed properly in braces:
<A> AND <B> AND <C> OR <D> AND <E> AND <F>
Should most likely actually be formulated as:
(<A> AND <B> AND <C>) OR (<D> AND <E> AND <F>)
Remember, the parser does not know what you want to achieve, you have to describe your intents properly...
I think you could simplify it to:
SELECT item
FROM tempTable
WHERE #barcode IS NULL OR tempTable.barcode LIKE #barcode+'%'
so when #barcode is null you'll get everything - i.e. the Like part of the where won't need to execute. If #barcode has a value then the Like will be executed.
If the barcode field is non-null, then this is the method I would use -
SELECT item
FROM tempTable
WHERE barcode like isnull(#barcode, barcode) + '%'
If #barcode is null all records are returned and if it is non null then only matching records are returned.
If the barcode field is nullable then -
SELECT item
FROM tempTable
WHERE isnull(barcode, '') like isnull(#barcode, isnull(barcode, '')) + '%'
Same as the first but here we convert the null values in the barcode field to blank strings before doing the compare.
An alternate answer and an attempt at the bounty
declare #barcode nvarchar(10) -- chose nvarchar not necessarily should be nvarchar
select #barcode= NULL
--select #barcode='XYZ'
if #barcode is null
select item from temptable;
else
select item from temptable where temptable.barcode like #barcode+'%';
If I have to do this, I would have done like
SELECT item
FROM tempTable
WHERE
( ( ISNULL(#barcode,'') <> '') AND ( tempTable.barcode LIKE #barcode+'%' ) )
( ISNULL(#barcode,'') <> '') would also check if the variable is blank then it should not return anything. But if you just check for null, then in case when the #barcode is blank, you will be getting all item selected from the tempTable.
If column barcode would be non-nullable, you could greatly simplify the query.
This is based on the fact that the pattern '%' matches any string; even an empty (i.e. zero-length) string.
Consequently, the following WHERE clause matches all records:
WHERE barcode LIKE '%'
You may notice that this has a very close resemblance to the WHERE clause you are using to filter records on a specific barcode:
WHERE barcode LIKE #barcode + '%'
In fact, they are so similar that we may as well use a single WHERE clause for both cases; after all, '' + '%' equals '%'!
IF #barcode IS NULL SET #barcode = ''
SELECT item FROM tempTable WHERE barcode LIKE #barcode + '%'
There is an even shorter version, which preserves the original value of #barcode:
SELECT item FROM tempTable WHERE barcode LIKE ISNULL(#barcode, '') + '%'
As mentioned earlier, this works only if column barcode is non-nullable.
If column barcode is nullable (and you are genuinely interested in records where barcode IS NULL), then the following query might work for you:
SELECT item FROM tempTable
WHERE ISNULL(barcode, '') LIKE ISNULL(#barcode, '') + '%'
However, this version has two disadvantages:
It may perform much slower, because the query optimizer may not benefit from an index on column barcode.
If #barcode = '', then it will match not only the non-null barcodes, but also the records with barcode IS NULL; whether this is acceptable, is up to you.
One last simplification: you may want to reach consensus with the outside world that they should set #barcode = '' instead of NULL to retrieve all records. Then you could replace ISNULL(#barcode, '') by #barcode.
Apart of ppeterka's solution (which will causes an Index/Table Scan) there are at least three other solutions. These solutions could use an Index Seek if #barcode isn't NULL and, also, if there is an index on barcode column:
Solution #2: The execution plan isn't cached and reused:
SELECT item
FROM tempTable
WHERE #barcode IS NULL OR tempTable.barcode LIKE #barcode+'%'
OPTION(RECOMPILE);
Solution #3: The execution plan is cached (it can be used if the num. of optional parameters is small):
IF #barcode IS NULL
SELECT item
FROM tempTable;
ELSE
SELECT item
FROM tempTable
WHERE tempTable.barcode LIKE #barcode+'%';
Solution #4: The execution plans are cached (it can be used if the num. of optional parameters is high):
DECLARE #SqlStatement NVARCHAR(MAX);
SET #SqlStatement = N'
SELECT item
FROM tempTable
WHERE 1=1 '
+ CASE WHEN #barcode IS NULL THEN N'' ELSE N'AND tempTable.barcode LIKE #pBarcode+''%''; ' END;
-- + CASE WHEN #anotherparam IS NULL THEN N'' ELSE 'AND ...' END ;
EXEC sp_executesql #SqlStatement, N'#pBarcode VARCHAR(10)', #pBarcode = #barcode;
Note: Use the proper type and max. lenght/precision & scale for #pBarcode parameter.
IF #barcode is null
begin
SELECT item FROM tempTable
end
else
SELECT item FROM tempTable where tempTable.barcode LIKE #barcode+'%'

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?

Writing SQL code: same functionality as Yell.com

Can anyone help me with the trying to write SQL (MS SqlServer) - I must admit this is not by best skill.
What I want to do is exactly the same functionality as appears for the search boxes for the Yell website i.e.
Search for company type
AND/OR company name
AND/OR enter a company name
in a Location
if anyone can suggest the SQL code you would need to write in order to get the same functionality as Yell - that would be great.
Typically, one does something like this:
-- All these are NULL unless provided
DECLARE #CompanyType AS varchar
DECLARE #CompanyName AS varchar
DECLARE #Town AS varchar
SELECT *
FROM TABLE_NAME
WHERE (#CompanyType IS NULL OR COMPANY_TYPE_COLUMN LIKE '%' + #CompanyType + '%')
AND (#CompanyName IS NULL OR COMPANY_NAME_COLUMN LIKE '%' + #CompanyName + '%')
AND (#Town IS NULL OR TOWN_COLUMN LIKE '%' + #Town + '%')
Or this (only match start of columns with the wildcards):
-- All these are NULL unless provided
DECLARE #CompanyType AS varchar
DECLARE #CompanyName AS varchar
DECLARE #Town AS varchar
SELECT *
FROM TABLE_NAME
WHERE (#CompanyType IS NULL OR COMPANY_TYPE_COLUMN LIKE #CompanyType + '%')
AND (#CompanyName IS NULL OR COMPANY_NAME_COLUMN LIKE #CompanyName + '%')
AND (#Town IS NULL OR TOWN_COLUMN LIKE #Town + '%')
Can you provide the database layout (schema) that the sql would run against? It would be necessary to give you an exact result.
But generally speaking what you are looking for is
SELECT * FROM tablename WHERE companyType = 'type' OR companyName = 'companyName'
What you need first is not SQL code, but a database design. Only then does it make any sense to start writing SQL.
A simple table schema that matches Yell's functionality might be something like:
CREATE TABLE Company (
company_id INT NOT NULL PRIMARY KEY IDENTITY(1,1),
company_name VARCHAR(255) NOT NULL,
location VARCHAR(255) NOT NULL
)
and then you'd search for it by name with SQL like:
SELECT * FROM Company WHERE company_name like '%text%'
or by location like:
SELECT * FROM Company WHERE location = 'Location'
Of course, a real-world location search would have to use either exact city and state, or a zip code lookup, or some intelligent combination thereof. And a real table would then have lots more fields, like descriptions, etc. But that's the basic idea.