Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
Questions asking for code must demonstrate a minimal understanding of the problem being solved. Include attempted solutions, why they didn't work, and the expected results. See also: Stack Overflow question checklist
Closed 9 years ago.
Improve this question
I've table named EMPLOYEE which contains First Name, Last Name and Employee ID. Now I want to get rows when I search for First Name alone or Last name alone or may be both. How to do that?
You can use the following query. I assume you have 2 paramers #FirstName and #LastName which can be NULL if you don't want to search by them.
SELECT * FROM EMPLOYEE
WHERE ([First Name] = #FirstName OR #FirstName IS NULL)
AND ([Last Name] = #LastName OR #LastName IS NULL)
I would use dynamic queries:
DECLARE #FirstName NVARCHAR(50), #LastName NVARCHAR(50);
SET #FirstName = 'John';
SET #LastName = 'Johnson';
DECLARE #SqlStatement NVARCHAR(MAX), #SqlParamters NVARCHAR(MAX);
SET #SqlStatement = N'SELECT e.EmployeeID, e.FirstName, e.LastName FROM dbo.Employee e
'
+ CASE WHEN #FirstName IS NOT NULL OR #LastName IS NOT NULL THEN 'WHERE '
+ CASE WHEN #FirstName IS NOT NULL THEN 'e.FirstName = #pFirstName'
+ CASE WHEN #LastName IS NOT NULL THEN 'e.LastName = #pLastName';
-- PRINT #SqlStatement;
EXEC sp_executesql
#SqlStatement,
N'#pFirstName NVARCHAR(50), #pLastName NVARCHAR(50)',
#pFirstName = #FirstName,
#pLastName = #LastName;
Indexing: if you have the following index
CREATE INDEX IX_Employee_LastName_FirstName
On dbo.Employee (LastName, FirstName);
then only the following predicates are SARGable (Index Seek):
WHERE e.LastName = #pLastName
and
WHERE e.LastName = #pLastName AND e.FirstName = #pFirstName
but the following predicate isn't SARGable (Index Scan):
WHERE e.FirstName = #pFirstName
Related
For the below query, SQL Server is creating a unique query plan depending on the parameter which is passed, Is there any way to optimize the below query to reduce the number of query plans and optimize the query.
CREATE PROCEDURE [dbo].[Foo_search]
#ItemID INT,
#LastName VARCHAR(50),
#MiddleName VARCHAR(40),
#FirstName VARCHAR(50)
AS
BEGIN
DECLARE #Sql NVARCHAR(max)
SELECT #Sql = N 'Select ID, FirstName, FamilyName, MiddleName, MaidenName, Email, From Employees Where DeletedOn Is Null ' +
CASE WHEN #LastName IS NULL OR #LastName = '' THEN '' ELSE ' And FamilyName=''' + #LastName + ''' ' END +
CASE WHEN #MiddleName IS NULL OR #MiddleName = '' THEN '' ELSE ' And MiddleName=''' + #MiddleName + ''' ' END +
CASE WHEN #FirstName IS NULL OR #FirstName = '' THEN '' ELSE ' And FirstName=''' + #FirstName + ''' ' END +
CASE WHEN #ItemID IS NOT NULL AND #ItemID > 0 THEN ' And ItemID=' + CONVERT(VARCHAR(10), #ItemID) + ' ' ELSE ' ' END
EXEC Sp_executesql #sql
END
Yes there is a way you can improve it, use the parameters correctly, rather than using string concatenation. Your method will generate a different query plan for every different combination of values of parameter, rather than just every different combination of parameters, which will generate orders of magnitude more query plans.
DECLARE #Sql nvarchar(max) = N'
Select ID, FirstName, FamilyName, MiddleName, MaidenName, Email
From Employees
Where DeletedOn Is Null
'
+ CASE WHEN #LastName <> '' THEN ' And FamilyName = #LastName' ELSE '' END
+ CASE WHEN #MiddleName <> '' THEN ' And MiddleName = #MiddleName' ELSE '' END
+ CASE WHEN #FirstName <> '' THEN ' And FirstName = #FirstName' ELSE '' END
+ CASE WHEN #ItemID > 0 THEN ' And ItemID = #ItemID' ELSE '' END;
EXEC sp_executesql
#Sql,
N'#LastName varchar(50), #MiddleName varchar(40), #FirstName varchar(50)',
#LastName = #LastName,
#MiddleName = #MiddleName,
#FirstName = #FirstName;
And as Aaron Bertrand points out, this also fixes the issue where any parameter value containing a single quote (') will fail.
Beyond this however, as Aaron also mentions, the performance is most likely down to other issues such as indexing.
I don't think you're blaming the right thing, but if you really think that multiple plans are causing your CPU spikes, it's easy to change this back to a single plan strategy:
ALTER PROCEDURE dbo.Foo_search
#ItemID INT,
#LastName VARCHAR(50),
#MiddleName VARCHAR(40),
#FirstName VARCHAR(50)
AS
BEGIN
Select ID, FirstName, FamilyName, MiddleName, MaidenName, Email
From dbo.Employees
Where DeletedOn Is Null
AND (FamilyName = #LastName OR #LastName IS NULL)
AND (MiddleName = #MiddleName OR #MiddleName IS NULL)
AND (FirstName = #FirstName OR #FirstName IS NULL)
AND (ItemID = #ItemID OR #ItemID IS NULL);
END
Let us know how that works out for you; I'm curious what index you're going to implement to make that perform well for all possible parameter combinations.
I have the below requirement. When FirstName present I should return the only condtionFirstName when LastName present I should return the only condtionlastName when First and email both are present I should return condtionFirstName + condtionEmail.
Basically whichever value is present I should return that condition, If 1 value 1 condition, if 1 and 2 then condition 1 and 2, if 1 and 3 then condition 1 and 3, if only 3 then condition 3.
Kindly help me to get this logic.
DECLARE #FirstName NVARCHAR(100)
DECLARE #LastName NVARCHAR(100)
DECLARE #Email NVARCHAR(200)
DECLARE #condtionFirstName NVARCHAR(200)
DECLARE #condtionlastName NVARCHAR(200)
DECLARE #condtionEmail NVARCHAR(200)
SET #FirstName = 'JOhn'
SET #LastName = 'David'
SET #Email = 'john.david#abc.com'
SET #condtionFirstName = ' AND FirstName = ' + '''' + #FirstName + ''''
SET #condtionlastName = ' AND LastName = ' + '''' + #LastName + ''''
SET #condtionEmail = ' AND Email = ' + '''' + #Email + ''''
This is what I've long called "the kitchen sink" - you want a single query that can support any combination of search criteria. Some thoughts:
Stop trying to concatenate user input with executable strings - this is dangerous and error-prone. These should always be passed in as explicitly-typed parameters.
You can build a single string containing all the conditions instead of trying to create a new condition string for every possible condition.
You can declare and pass parameters to sp_executesql even if they don't all end up in the dynamic SQL statement. This is just like declaring a local variable and not using it.
e-mail addresses can be 320 characters long. If you only support 200, that could bite you.
Sample:
DECLARE #FirstName nvarchar(100),
#LastName nvarchar(100),
#Email nvarchar(320),
#conditions nvarchar(max) = N'';
SET #FirstName = N'John';
SET #LastName = N'David';
SET #Email = N'john.david#abc.com';
SET #conditions += CASE WHEN #FirstName IS NOT NULL THEN
N' AND FirstName = #FirstName' ELSE N'' END
+ CASE WHEN #LastName IS NOT NULL THEN
N' AND LastName = #LastName' ELSE N'' END
+ CASE WHEN #Email IS NOT NULL THEN
N' AND Email = #Email' ELSE N'' END;
DECLARE #sql nvarchar(max) = N'SELECT ... FROM dbo.table
WHERE 1 = 1' + #conditions;
PRINT #sql; -- try this when populating or not populating each of
-- #FirstName, #LastName, #Email
EXEC sys.sp_executesql #sql,
N'#FirstName nvarchar(100), #LastName nvarchar(100), #Email nvarchar(320)',
#FirstName, #LastName, #Email;
More details:
#BackToBasics: An Updated Kitchen Sink Example
Protecting Yourself from SQL Injection in SQL Server - Part 1
Protecting Yourself from SQL Injection in SQL Server - Part 2
I have the following user-defined function:
create function [dbo].[FullNameLastFirst]
(
#IsPerson bit,
#LastName nvarchar(100),
#FirstName nvarchar(100)
)
returns nvarchar(201)
as
begin
declare #Result nvarchar(201)
set #Result = (case when #IsPerson = 0 then #LastName else case when #FirstName = '' then #LastName else (#LastName + ' ' + #FirstName) end end)
return #Result
end
I can't create an Index on a computed column using this function cause it's not deterministic.
Someone could explain why is it not deterministic and eventually how to modify to make it deterministic?
Thanks
You just need to create it with schemabinding.
SQL Server will then verify whether or not it meets the criteria to be considered as deterministic (which it does as it doesn't access any external tables or use non deterministic functions such as getdate()).
You can verify that it worked with
SELECT OBJECTPROPERTY(OBJECT_ID('[dbo].[FullNameLastFirst]'), 'IsDeterministic')
Adding the schemabinding option to your original code works fine but a slightly simpler version would be.
CREATE FUNCTION [dbo].[FullNameLastFirst] (#IsPerson BIT,
#LastName NVARCHAR(100),
#FirstName NVARCHAR(100))
RETURNS NVARCHAR(201)
WITH SCHEMABINDING
AS
BEGIN
RETURN CASE
WHEN #IsPerson = 0
OR #FirstName = '' THEN #LastName
ELSE #LastName + ' ' + #FirstName
END
END
You need to declare the User Defined Function WITH SCHEMABINDING to appease the 'deterministic' requirement of an index on the computed column.
A function declared WITH SCHEMABINDING will retain additional knowledge about the object dependencies used in the function (e.g. columns in the table), and will prevent any changes to these columns, unless the function itself is dropped beforehand.
Deterministic functions can also assist Sql Server in optimizing its execution plans, most notably the Halloween Protection problem.
Here's an example of creating an index on a computed column using a schema bound function:
create function [dbo].[FullNameLastFirst]
(
#IsPerson bit,
#LastName nvarchar(100),
#FirstName nvarchar(100)
)
returns nvarchar(201)
with schemabinding
as
begin
declare #Result nvarchar(201)
set #Result = (case when #IsPerson = 0 then #LastName
else case when #FirstName = '' then #LastName
else (#LastName + ' ' + #FirstName) end end)
return #Result
end
create table Person
(
isperson bit,
lastname nvarchar(100),
firstname nvarchar(100),
fullname as [dbo].[FullNameLastFirst] (isperson, lastname, firstname)
)
go
insert into person(isperson, lastname, firstname) values (1,'Firstname', 'Surname')
go
create index ix1_person on person(fullname)
go
select fullname from Person with (index=ix1_person) where fullname = 'Firstname Surname'
go
I'm writing a general search Stored Procedure to search in a table based on many filters which user can select in the UI (using MS-SQL 2008).
Here is ther simplified version:
CREATE PROCEDURE SearchAll
#FirstName NVARCHAR(MAX) = NULL,
#LastName NVARCHAR(MAX) = NULL,
#Age INT = NULL
AS
SELECT *
FROM persons
WHERE
(#FirstName IS NULL OR FirstName = #firstname)
AND (#LastName IS NULL OR LastName = #LastName)
AND (#Age IS NULL OR Age = #Age)
It seems that if I pass NULL to #Age there'll be no performance cost. But, when I'm testing with huge amount of data, I have a great perfomance lost!
Here is the queries which are the same logicaly but VERY different practically:
DECLARE #FirstName NVARCHAR(MAX) = NULL
DECLARE #Age INT = 23
------------First slow------------
SELECT *
FROM persons
WHERE
(#FirstName IS NULL OR FirstName = #firstname)
AND (#Age IS NULL OR Age = #Age)
------------Very fast------------
SELECT *
FROM persons
WHERE
Age = #Age
Did is miss a point?
I know that SQL engine finds the best match for indexes, and ... (before running the query),but it's obvious that: #FirstName IS NULL and there's no need to analyse anything.
I've also tested ISNULL function in the query (the same result).
Queries that contain this construct of #variable is null or #variable = column are a performance disaster. This is because SQL plans are created so they work for any value of the variable. For a lengthy discussion of the topic, the problems and possible solutions see Dynamic Search Conditions in T-SQL
The problem, as has already been mentioned, is that the query plan will be built to work with any value of the variables. You can circumvent this by building the query with only the paremeter required, as follows:
CREATE PROCEDURE SearchAll
#FirstName NVARCHAR(MAX) = NULL,
#LastName NVARCHAR(MAX) = NULL,
#Age INT = NULL
AS
BEGIN
DECLARE #sql NVARCHAR(MAX), #has_where BIT
SELECT #has_where = 0, #sql = 'SELECT * FROM persons '
IF #FirstName IS NOT NULL
SELECT #sql = #sql + 'WHERE FirstName = ''' + #FirstName + '''', #has_where = 1
IF #LastName IS NOT NULL
SELECT #sql = #sql + CASE WHEN #has_where = 0 THEN 'WHERE ' ELSE 'AND ' END + 'LastName = ''' + #LastName + '''', #has_where = 1
IF #Age IS NOT NULL
SELECT #sql = #sql + CASE WHEN #has_where = 0 THEN 'WHERE ' ELSE 'AND ' END + 'Age = ' + CAST(#Age AS VARCHAR), #has_where = 1
EXEC sp_executesql #sql
END
I need to write a stored procedure that will search a table based on optional parameters Using sql server 2008.
There will be two modes
basic search mode (we just pass some text)
advanced search mode (Optional parameters are used NOT SearchText is used.)
For testing I am using the AdventureWorks.Person.Contact table
Would you write something like below if not can you make suggestions how to improve it?
Thanks a lot
ALTER PROCEDURE SearchPeople
#SearchText nvarchar(200)=NULL, --- only used in basic search mode
#SearchMode bit,
#FirstName varchar(50)=NULL,
#LastName varchar(50)=NULL,
#EmailAddress varchar(50)=NULL,
#Phone nvarchar(25)=NULL
AS
IF #SearchMode=0
BEGIN
print 'BASIC SEARCH'
SELECT *
FROM [Person].[Contact]
WHERE (FirstName LIKE '%' + #SearchText + '%'
OR LastName LIKE '%' + #SearchText + '%'
OR EmailAddress LIKE '%' + #SearchText + '%'
OR Phone LIKE '%' + #SearchText + '%')
END
ELSE
BEGIN
print 'ADVANCED SEARCH'
SELECT *
FROM [Person].[Contact]
WHERE (FirstName =#FirstName OR #FirstName IS NULL)
AND (LastName =#LastName OR #FirstName IS NULL)
AND (EmailAddress =#EmailAddress OR #EmailAddress IS NULL)
AND (Phone =#Phone OR #Phone IS NULL)
END
Personally, I'd split the basic and advance search out into two separate procedures, and I'd give serious consideration to implementing fulltext searching for your basic search.
I agree with Joe. Your solution would lead to parameter sniffing. one way to solve param sniffing is to split into basic and advance search stored procedure. But even then you would need to use Dynamic SQL in the advanced search stored procedure to avoid parameter sniffing. I dont know about your specific situation, but if you have just one or two fields to search by, then maybe you dont need to worry about param sniffing, but if you have , lets say, more than 5 or 6 parameters you should definitely go with dynamic SQL.
so the advanced search should look something like this.
DECLARE #query VARCHAR(MAX);
SET #query = 'SELECT *
FROM [Person].[Contact]
WHERE 1=1 '
IF #FirstName IS NOT NULL
SET #query = #query + ' AND FirstName = #FirstName '
IF #LastName IS NOT NULL
SET #query = #query + ' AND LastName = #LastName '
IF #EmailAddress IS NOT NULL
SET #query = #query + ' AND EmailAddress = #EmailAddress '
IF #Phone IS NOT NULL
SET #query = #query + ' AND Phone = #Phone '
sp_executesql #query,
N'#FirstName VARCHAR(50),
#LastName VARCHAR(50),
#EmailAddress VARHCAR(50),
#Phone NVARCHAR(25)',
#FirstName,
#LastName,
#EmailAddress,
#Phone
Here is a usefull article on parameter sniffing Link