Search by different parameters of stored procedure SQL Server - sql

I am using SQL Server 2008, and in my database I have a table called Student, with 3 columns name, lastname, year.
I want to give the user the opportunity to search students from that database by different attributes. I want to write one stored procedure, which will have 3 parameters (all the columns of Student) which could implement search in different ways.
If I write procedure this way:
Create Procedure ProcSearchStudents
#name nchar(20) = NULL,
#lastname nchar(20) = NULL,
#year int = NULL,
AS
if(#lastname = NULL)
BEGIN
Select *
from Student
where name = #name
and year = #year
END
if(#name = NULL)
BEGIN
Select *
from Student
where lastname = #lastname
and year = #year
END
if(#year = NULL)
BEGIN
Select *
from Student
where name = #name
and lastname = #lastname
END
...
...
GO
I have to write if() statement 3!(factorial) times. So, If I have 8 attributes, it will be almost impossible to write this.
Is there any other better way to do that, or a kind of engine, which can help me?

There's a simple solution:
select *
from Student
where (#name is null or Name = #name)
and (#lastname is null or LastName = #lastname)
and (#year is null or Year = #year)
Although simple, this suffers from the problem of "parameter sniffing". Basically, SQL Server creates a plan for the query based on the first search you run. This can be resolved by adding a query hint that forces SQL Server to re-create the query plan for every search:
select *
from Student
where (#name is null or Name = #name)
and (#lastname is null or LastName = #lastname)
and (#year is null or Year = #year)
option (recompile)
If you do a lot of queries, the overhead of compiling a plan can become too high. In that case you have to resort to dynamic SQL. But that's the answer to a different question.

Related

How to filter data based on different values of a column in sql server

I am stuck at a point.
I want to select based on the column entitytype if entitytype value is Booking or JOb then it will filter on its basis but if it is null or empty string('') then i want it to return all the rows containing jobs and bookings
create proc spproc
#entityType varchar(50)
as
begin
SELECT TOP 1000 [Id]
,[EntityId]
,[EntityType]
,[TenantId]
FROM [FutureTrakProd].[dbo].[Activities]
where TenantId=1 and EntityType= case #EntityType when 'BOOKING' then 'BOOKING'
when 'JOB' then 'JOB'
END
end
Any help would be appreciable
Thankyou
create proc spproc
#entityType varchar(50)
as
begin
SELECT TOP 1000 [Id]
,[EntityId]
,[EntityType]
,[TenantId]
FROM [FutureTrakProd].[dbo].[Activities]
where TenantId=1 and (#EntityType is null OR EntityType= #EntityType)
end
You don't need to use case expression you can do :
SELECT TOP 1000 [Id], [EntityId], [EntityType], [TenantId]
from [FutureTrakProd].[dbo].[Activities]
WHERE TenantId = 1 AND
(#EntityType IS NULL OR EntityType = #EntityType)
ORDER BY id; -- whatever order you want (asc/desc)
For your query procedure you need to state explicit ORDER BY clause otherwise TOP 1000 will give random Ids.
You don't need a CASE expression for this, you just need an OR. The following should put you on the right path:
WHERE TenantId=1
AND (EntityType = #EntityType OR #EntityType IS NULL)
Also, note it would also be wise to declare your parameter as NULLable:
CREATE PROC spproc #entityType varchar(50) = NULL
This means that someone can simply exclude the paramter, value than having to pass NULL (thus EXEc spproc; would work).
Finally, if you're going to have lots of NULLable parameters, then you're looking at a "catch-all" query; the solution would be different if that is the case. "Catch-all" queries can be notoriously slow.
You can execute a dynamic sql query.
Query
create proc spproc
#entityType varchar(50)
as
begin
declare #sql as nvarchar(max);
declare #condition as nvarchar(2000);
select = case when #entityType is not null then ' and [EntityType] = #entityType;' else ';' end;
select #sql = 'SELECT TOP 1000 [Id], [EntityId], [EntityType], [TenantId] FROM [FutureTrakProd].[dbo].[Activities] where TenantId = 1 ';
exec sp_executesql #sql,
N'#entityType nvarchar(1000)',
#entityType = #entityType
end

Using SQL Server CASE statement in WHERE

I want to select records from a table in a stored procedure. Given parameters can be empty or a string including some keys separated by comma (1, 2, etc)
I want to manage that when a parameter is an empty string, "WHERE" ignore searching.
I'm using this code:
where (CASE when #PatientID <> 0 then ( dental.ID_Sick in (1,2)) else (1=1) end)
Something like that is working in W3School. I mean:
SELECT * FROM Customers
WHERE (case when 1=1 then (Country IN ('Germany', 'France', 'UK')) else 1=1 end);
What is the problem in my query that does not work? SQLServerManagementStudio is giving error on "IN" statement.
Solution:
The best way to handle such optional parameters is to use dynamic SQL and built the query on the fly. Something like....
CREATE PROCEDURE myProc
#Param1 VARCHAR(100) = NULL
,#Param2 VARCHAR(100) = NULL
,#Param3 VARCHAR(100) = NULL
,#ListParam VARCHAR(100) = NULL
--, etc etc...
AS
BEGIN
SET NOCOUNT ON;
Declare #Sql NVARCHAR(MAX);
SET #Sql = N' SELECT *
FROM TableName
WHERE 1 = 1 '
-- add in where clause only if a value was passed to parameter
+ CASE WHEN #Param1 IS NOT NULL THEN
N' AND SomeColumn = #Param1 ' ELSE N'' END
-- add in where clause a different variable
-- only if a value was passed to different parameter
+ CASE WHEN #Param2 IS NOT NULL THEN
N' AND SomeOtherColumn = #Param3 ' ELSE N'' END
-- List Parameter used with IN clause if a value is passed
+ CASE WHEN #ListParam IS NOT NULL THEN
N' AND SomeOtherColumn IN (
SELECT Split.a.value(''.'', ''VARCHAR(100)'') IDs
FROM (
SELECT Cast (''<X>''
+ Replace(#ListParam, '','', ''</X><X>'')
+ ''</X>'' AS XML) AS Data
) AS t CROSS APPLY Data.nodes (''/X'') AS Split(a) '
ELSE N'' END
Exec sp_executesql #sql
, N' #Param1 VARCHAR(100), #Param2 VARCHAR(100) ,#Param3 VARCHAR(100) ,#ListParam VARCHAR(100)'
, #Param1
, #Param2
,#Param3
, #ListParam
END
Problem with Other approach
There is a major issue with this other approach, you write your where clause something like...
WHERE ( ColumnName = #Parameter OR #Parameter IS NULL)
The Two major issues with this approach
1) you cannot force SQL Server to check evaluate an expression first like if #Parameter IS NULL, Sql Server might decide to evaluate first the expression ColumnName = #Parameterso you will have where clause being evaluated even if the variable value is null.
2) SQL Server does not do Short-Circuiting (Like C#), even if it decides to check the #Parameter IS NULL expression first and even if it evaluates to true, SQL Server still may go ahead and evaluating other expression in OR clause.
Therefore stick to Dynamic Sql for queries like this. and happy days.
SQL Server does not have a Bool datatype, so you can't assign or return the result of a comparison as a Bool as you would in other languages. A comparison can only be used with IF-statements or WHERE-clauses, or in the WHEN-part of a CASE...WHEN but not anywhere else.
Your specific example would become this:
SELECT * FROM Customers
WHERE 1=1 OR Country IN ('Germany', 'France', 'UK')
It would be better readable to rewrite your statement as follows:
WHERE #PatientID = 0
OR dental.ID_Sick in (1,2)
Referring to your actual question, I'd advise to read the linked question as provided by B House.
May be this straight way will work for you
IF (#PatientID <> 0)
BEGIN
SELECT * FROM Customers
WHERE Country IN ('Germany', 'France', 'UK')
END
try this:
WHERE 1=(CASE WHEN #PatientID <>0 AND dental.ID_Sick in (1,2) THEN 1
WHEN #PatientID =0 THEN 1
ELSE 0
END)

Dynamic Where Class Using 'CASE' will affect performance

I have a table called Employee.
Fields are
1.EmployeeId,
2.Name,
3.Salary,
4.Address
I need to write a procedure that will fetch all the employee details. The where clause of this procedure is dynamic. I can able to write a dynamic query do fetch the details. But i recently explore the below format query.
SELECT * FROM Employee
WHERE 1=1
AND EmployeeId = CASE WHEN #EmployeeId IS NULL OR LEN(#EmployeeId) =0 THEN EmployeeId ELSE #EmployeeId END
AND Name = CASE WHEN #Name IS NULL OR LEN(#Name) =0 THEN Name ELSE #Name END
...
...
ORDER BY Name ASC
#EmployeeId , #name are coming from application
Can any tell me this will make any performance impact over a dynamic query? or Which one is better performance?
The where clause condition is dynamic. This is not strict with 2 condition. Some times its 1 or 3 or 4 or 0
Sorry for any typo-errors
Thanks in advance.
Let's say this was the data you are working with:
-- sample data
USE tempdb;
GO
CREATE TABLE dbo.employee (EmployeeId int, Name varchar(100));
INSERT dbo.employee
SELECT TOP (100)
ABS(checksum(newid())%10)+1,
CASE abs(checksum(newid())%3)+1 WHEN 1 THEN 'Peter' WHEN 2 THEN 'Paul' ELSE 'Mary' END
+ ' ' +
CASE abs(checksum(newid())%3)+1 WHEN 1 THEN 'Smith' WHEN 2 THEN 'Jones' ELSE 'White' END
FROM sys.all_columns;
-- A good covering index
CREATE NONCLUSTERED INDEX nc_employee ON dbo.employee (EmployeeId, Name);
First note that you can get the exact same results and execution plan with this query:
SELECT *
FROM dbo.employee
WHERE (EmployeeId = #EmployeeId OR NULLIF(LEN(#EmployeeId),0) IS NULL)
AND (Name = #Name OR NULLIF(LEN(#Name),0) IS NULL)
Presuming there is a usable index present - your query (or the query above) will create a basic execution plan consisting of an index seek or index Scan, and a SELECT operator. A scan if you run the query as-is, a seek if you include OPTION (RECOMPILE) at the end of either query... An index seek is much better than a scan.
An alternative to using the aforementioned OPTION (RECOMPILE) hint to get an index seek would be to use Dynamic SQL as shown below.
DECLARE #EmployeeId int = 5,
#Name varchar(100) = 'Peter White';
DECLARE #sql nvarchar(2000) = N'SELECT * FROM dbo.employee';
DECLARE #ParmDefinition nvarchar(500);
SET #ParmDefinition = N'#EmployeeId int, #Name varchar(100)';
SET #sql +=
CASE
WHEN (NULLIF(LEN(#EmployeeId),0) IS NOT NULL AND NULLIF(LEN(#Name),0) IS NOT NULL)
THEN ' WHERE EmployeeId = #EmployeeId AND Name = #Name;'
WHEN (NULLIF(LEN(#EmployeeId),0) IS NULL AND NULLIF(LEN(#Name),0) IS NOT NULL)
THEN ' WHERE Name = #Name;'
WHEN (NULLIF(LEN(#EmployeeId),0) IS NOT NULL AND NULLIF(LEN(#Name),0) IS NULL)
THEN ' WHERE EmployeeId = #EmployeeId;'
ELSE ''
END;
EXECUTE sp_executesql #sql, #ParmDefinition, #EmployeeId = #EmployeeId, #Name = #Name;
Finally, it's worth noting that, for cases where #EmployeeID is NULL or blank but #Name has a value - you will get a scan. If you wanted a seek in those cases you would want another index that looks like this present:
CREATE NONCLUSTERED INDEX nc_employee2 ON dbo.employee (Name, EmployeeId);

How can I use <> operator and || operator inside a stored procedure

I have created an database file inside my asp.net application. Now from server explorer I tried to write a Stored procedure as follows
CREATE PROCEDURE insertData
(
#ID int,
#Name varchar(50),
#Address varchar(50),
#bit BIT OUTPUT
)
as
begin
declare #oldName as varchar(45)
declare #oldAddress as varchar(45)
set #oldName=(select EmployeeName from Employee where EmployeeName=#Name)
set #oldAddress=(select Address from Employee where Address=#Address)
if(#oldName <> #Name | #oldAddress <> #Address)
insert into Employee(EmpID,EmployeeName,Address)values(#ID,#Name,#Address)
SET #bit = 1
END
But this is giving me an error when I am saving it like Incorrect syntax near <..
There are several things wrong here
if(#oldName <> #Name | #oldAddress <> #Address)
Won't work - maybe try
if #oldName <> #Name OR #oldAddress <> #Address
Of course, this will never be true because of the way the two queries above (which could and should have just been one query assigning both variables) make sure that the variables are always equal.
I.e.:
set #oldName=(select EmployeeName from Employee where EmployeeName=#Name)
What can #oldName be, if not equal to #Name? (Okay, it could be NULL, but then <> is the wrong operator to use if NULL is what you're checking for)
I think that what you wanted to write here was:
select #oldName=EmployeeName,#oldAddress = Address from Employee where EmpID = #ID
You should use OR and not |
You can also do this instead of querying and checking each value separately, this will insert new row if name and/or address do not match for given empid
IF NOT EXISTS (
select * from Employee
where EmpID = #ID AND EmployeeName = #Name AND Address = #Address)
insert into Employee(EmpID,EmployeeName,Address)values(#ID,#Name,#Address)
SET #bit = 1
END
You can use != instead of "<>" and you can use Or instead of "|".

How do I create a stored procedure that will optionally search columns?

I'm working on an application for work that is going to query our employee database. The end users want the ability to search based on the standard name/department criteria, but they also want the flexibility to query for all people with the first name of "James" that works in the Health Department. The one thing I want to avoid is to simply have the stored procedure take a list of parameters and generate a SQL statement to execute, since that would open doors to SQL injection at an internal level.
Can this be done?
While the COALESCE trick is neat, my preferred method is:
CREATE PROCEDURE ps_Customers_SELECT_NameCityCountry
#Cus_Name varchar(30) = NULL
,#Cus_City varchar(30) = NULL
,#Cus_Country varchar(30) = NULL
,#Dept_ID int = NULL
,#Dept_ID_partial varchar(10) = NULL
AS
SELECT Cus_Name
,Cus_City
,Cus_Country
,Dept_ID
FROM Customers
WHERE (#Cus_Name IS NULL OR Cus_Name LIKE '%' + #Cus_Name + '%')
AND (#Cus_City IS NULL OR Cus_City LIKE '%' + #Cus_City + '%')
AND (#Cus_Country IS NULL OR Cus_Country LIKE '%' + #Cus_Country + '%')
AND (#Dept_ID IS NULL OR Dept_ID = #DeptID)
AND (#Dept_ID_partial IS NULL OR CONVERT(varchar, Dept_ID) LIKE '%' + #Dept_ID_partial + '%')
These kind of SPs can easily be code generated (and re-generated for table-changes).
You have a few options for handling numbers - depending if you want exact semantics or search semantics.
The most efficient way to implement this type of search is with a stored procedure. The statement shown here creates a procedure that accepts the required parameters. When a parameter value is not supplied it is set to NULL.
CREATE PROCEDURE ps_Customers_SELECT_NameCityCountry
#Cus_Name varchar(30) = NULL,
#Cus_City varchar(30) = NULL,
#Cus_Country varchar(30) =NULL
AS
SELECT Cus_Name,
Cus_City,
Cus_Country
FROM Customers
WHERE Cus_Name = COALESCE(#Cus_Name,Cus_Name) AND
Cus_City = COALESCE(#Cus_City,Cus_City) AND
Cus_Country = COALESCE(#Cus_Country,Cus_Country)
Taken from this page: http://www.sqlteam.com/article/implementing-a-dynamic-where-clause
I've done it before. It works well.
Erland Sommarskog's article Dynamic Search Conditions in T-SQL is a good reference on how to do this. Erland presents a number of strategies on how to do this without using dynamic SQL (just plain IF blocks, OR, COALESCE, etc) and even lists out the performance characteristics of each technique.
In case you have to bite the bullet and go through the Dynamic SQL path, you should also read Erland's Curse and Blessings of Dynamic SQL where he gives out some tips on how to properly write dynamic SQLs
It can be done, but usually these kitchen-sink procedures result in some poor query plans.
Having said all that, here is the tactic most commonly used for "optional" parameters. The normal approach is to treat NULL as "ommitted".
SELECT
E.EmployeeID,
E.LastName,
E.FirstName
WHERE
E.FirstName = COALESCE(#FirstName, E.FirstName) AND
E.LastName = COALESCE(#LastName, E.LastName) AND
E.DepartmentID = COALESCE(#DepartmentID, E.DepartmentID)
EDIT:
A far better approach would be parameterized queries.
Here is a blog post from one of the world's foremost authorities in this domain, Frans Bouma from LLBLGen Pro fame:
Stored Procedures vs. Dynamic Queries
Using the COALESCE method has a problem in that if your column has a NULL value, passing in a NULL search condition (meaning ignore the search condition) will not return the row in many databases.
For example, try the following code on SQL Server 2000:
CREATE TABLE dbo.Test_Coalesce (
my_id INT NOT NULL IDENTITY,
my_string VARCHAR(20) NULL )
GO
INSERT INTO dbo.Test_Coalesce (my_string) VALUES (NULL)
INSERT INTO dbo.Test_Coalesce (my_string) VALUES ('t')
INSERT INTO dbo.Test_Coalesce (my_string) VALUES ('x')
INSERT INTO dbo.Test_Coalesce (my_string) VALUES (NULL)
GO
DECLARE #my_string VARCHAR(20)
SET #my_string = NULL
SELECT * FROM dbo.Test_Coalesce WHERE my_string = COALESCE(#my_string, my_string)
GO
You will only get back two rows because in the rows where the column my_string is NULL you are effective getting:
my_string = COALESCE(#my_string, my_string) =>
my_string = COALESCE(NULL, my_string) =>
my_string = my_string =>
NULL = NULL
But of course, NULL does not equal NULL.
I try to stick with:
SELECT
my_id,
my_string
FROM
dbo.Test_Coalesce
WHERE
(#my_string IS NULL OR my_string = #my_string)
Of course, you can adjust that to use wild cards or whatever else you want to do.
Copying this from my blog post:
USE [AdventureWorks]
GO
CREATE PROCEDURE USP_GET_Contacts_DynSearch
(
-- Optional Filters for Dynamic Search
#ContactID INT = NULL,
#FirstName NVARCHAR(50) = NULL,
#LastName NVARCHAR(50) = NULL,
#EmailAddress NVARCHAR(50) = NULL,
#EmailPromotion INT = NULL,
#Phone NVARCHAR(25) = NULL
)
AS
BEGIN
SET NOCOUNT ON
DECLARE
#lContactID INT,
#lFirstName NVARCHAR(50),
#lLastName NVARCHAR(50),
#lEmailAddress NVARCHAR(50),
#lEmailPromotion INT,
#lPhone NVARCHAR(25)
SET #lContactID = #ContactID
SET #lFirstName = LTRIM(RTRIM(#FirstName))
SET #lLastName = LTRIM(RTRIM(#LastName))
SET #lEmailAddress = LTRIM(RTRIM(#EmailAddress))
SET #lEmailPromotion = #EmailPromotion
SET #lPhone = LTRIM(RTRIM(#Phone))
SELECT
ContactID,
Title,
FirstName,
MiddleName,
LastName,
Suffix,
EmailAddress,
EmailPromotion,
Phone
FROM [Person].[Contact]
WHERE
(#lContactID IS NULL OR ContactID = #lContactID)
AND (#lFirstName IS NULL OR FirstName LIKE '%' + #lFirstName + '%')
AND (#lLastName IS NULL OR LastName LIKE '%' + #lLastName + '%')
AND (#lEmailAddress IS NULL OR EmailAddress LIKE '%' + #lEmailAddress + '%')
AND (#lEmailPromotion IS NULL OR EmailPromotion = #lEmailPromotion)
AND (#lPhone IS NULL OR Phone = #lPhone)
ORDER BY ContactID
END
GO
We can use Generic #Search Parameter and pass any value to it for searching.
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- =============================================
-- Author: --
-- Create date:
-- Description: --
-- =============================================
CREATE PROCEDURE [dbo].[usp_StudentList]
#PageNumber INT = 1, -- Paging parameter
#PageSize INT = 10,-- Paging parameter
#Search VARCHAR(MAX) = NULL, --Generic Search Parameter
#OrderBy VARCHAR(MAX) = 'FirstName', --Default Column Name 'FirstName' for records ordering
#SortDir VARCHAR(MAX) = 'asc' --Default ordering 'asc' for records ordering
AS
BEGIN
SET NOCOUNT ON;
--Query required for paging, this query used to show total records
SELECT COUNT(StudentId) AS RecordsTotal FROM Student
SELECT Student.*,
--Query required for paging, this query used to show total records filtered
COUNT(StudentId) OVER (PARTITION BY 1) AS RecordsFiltered
FROM Student
WHERE
--Generic Search
-- Below is the column list to add in Generic Serach
(#Search IS NULL OR Student.FirstName LIKE '%'+ #Search +'%')
OR (#Search IS NULL OR Student.LastName LIKE '%'+ #Search +'%')
--Order BY
-- Below is the column list to allow sorting
ORDER BY
CASE WHEN #SortDir = 'asc' AND #OrderBy = 'FirstName' THEN Student.FirstName END,
CASE WHEN #SortDir = 'desc' AND #OrderBy = 'FirstName' THEN Student.FirstName END DESC,
CASE WHEN #SortDir = 'asc' AND #OrderBy = 'LastName' THEN Student.LastName END,
CASE WHEN #SortDir = 'desc' AND #OrderBy = 'LastName' THEN Student.LastName END DESC,
OFFSET #PageSize * (#PageNumber - 1) ROWS FETCH NEXT #PageSize ROWS ONLY;
END
My first thought was to write a query something like this...
SELECT EmpId, NameLast, NameMiddle, NameFirst, DepartmentName
FROM dbo.Employee
INNER JOIN dbo.Department ON dbo.Employee.DeptId = dbo.Department.Id
WHERE IdCrq IS NOT NULL
AND
(
#bitSearchFirstName = 0
OR
Employee.NameFirst = #vchFirstName
)
AND
(
#bitSearchMiddleName = 0
OR
Employee.NameMiddle = #vchMiddleName
)
AND
(
#bitSearchFirstName = 0
OR
Employee.NameLast = #vchLastName
)
AND
(
#bitSearchDepartment = 0
OR
Department.Id = #intDeptID
)
...which would then have the caller provide a bit flag if they want to search a particular field and then supply the value if they are to search for it, but I don't know if this is creating a sloppy WHERE clause or if I can get away with a CASE statement in the WHERE clause.
As you can see this particular code is in T-SQL, but I'll gladly look at some PL-SQL / MySQL code as well and adapt accordingly.
I would stick with the NULL/COALESCE method over AdHoc Queries, and then test to make sure you don't have performance problems.
If it turns out that you have slow running queries because it's doing a table scan when you're searching on columns that are indexed, you could always supplement the generic search stored procedure with additional specific ones that allow searching on these indexed fields. For instance, you could have a special SP that does searches by CustomerID, or Last/First Name.
Write a procedure to insert all employee data whose name start with A in table??