TSQL Comma Delimited fields on same row - tsql

Imagine you have a User table with FirstName, MiddleName, LastName.
For the sake of this sample only FirstName is mandatory.
What's the fastest way to concatenate these 3 fields and separate them with a "," when their value is NOT null, so:
If only FirstName is NOT null, we should get "John" as result.
If only FirstName and LastName are not null, we should have "John,Wayne" as result.
If FirstName, MiddleName and LastName are not null, we should have "John,M,Wayne" as result.
Thanks in advanced.

Maybe something like this:
FirstName +(CASE WHEN MiddleName IS NULL THEN '' ELSE ','+MiddleName END)
+(CASE WHEN LastName IS NULL THEN '' ELSE ','+LastName END)

2 different methods
--syntax
select Firstname + isnull(',' +Middlename, '') + coalesce(',' +LastName, '')
from
--testdata
(select 'John' firstname, 'W' middlename, 'Wayne' lastname
union all select 'John' firstname, null middlename, 'Wayne' lastname
union all select 'John' firstname, 'W' middlename, null lastname) a

Related

SQL Query that returns "Hello" [column_name]

How do I update this query:
SELECT firstname, lastname FROM users
To return: 'Hello, [firstname] [lastname]!'
Use concat() function which adds two or more expressions together.
SELECT concat('Hello, ', firstname, ' ', lastname, '!') FROM users;

filtering any columns of a database table client T-SQL select from case when then where

I would like to be able filter through the columns of a datagrid but don't know how to fix the select statement this is as far as I could get
SELECT
ClientID, FirstName, LastName, BirthDate, StreetName,
City, State, ZipCode, CellPhone
FROM
dbo.Client
CASE WHEN #ColumnName = 'ClientID' THEN WHERE #ColumnName = #Filter END
CASE WHEN #ColumnName = 'FirstName' THEN WHERE #ColumnName LIKE #Filter END
CASE WHEN #ColumnName = 'LastName' THEN WHERE #ColumnName LIKE #Filter END
CASE WHEN #ColumnName = 'BirthDate' THEN WHERE #ColumnName = #Filter END
CASE WHEN #ColumnName = 'StreetName' THEN WHERE #ColumnName LIKE #Filter END
CASE WHEN #ColumnName = 'City' THEN WHERE #ColumnName LIKE #Filter END
CASE WHEN #ColumnName = 'State' THEN WHERE #ColumnName LIKE #Filter END
CASE WHEN #ColumnName = 'ZipCode' THEN WHERE #ColumnName LIKE #Filter END
CASE WHEN #ColumnName = 'CellPhone' THEN WHERE #ColumnName LIKE #Filter END;
Thank you for your time
Try this query:
SELECT ClientID, FirstName, LastName, BirthDate, StreetName, City, State,
ZipCode, CellPhone
FROM dbo.Client
WHERE
CASE WHEN #ColumnName = 'ClientID' AND ClientID = #Filter THEN 1
WHEN #ColumnName = 'FirstName' AND FirstName LIKE '%' + #Filter + '%' THEN 1
WHEN #ColumnName = 'LastName' AND LastName LIKE '%' + #Filter + '%' THEN 1
WHEN #ColumnName = 'BirthDate' AND CASE WHEN ISDATE(#Filter) = 1 THEN CONVERT(DATETIME, #Filter, 101) ELSE NULL END = BirthDate THEN 1
WHEN #ColumnName = 'StreetName' AND StreetName LIKE '%' + #Filter + '%' THEN 1
WHEN #ColumnName = 'City' AND City LIKE '%' + #Filter + '%' THEN 1
WHEN #ColumnName = 'State' AND State LIKE '%' + #Filter + '%' THEN 1
WHEN #ColumnName = 'ZipCode' AND ZipCode LIKE '%' + #Filter + '%' THEN 1
WHEN #ColumnName = 'CellPhone' AND CellPhone LIKE '%' + #Filter + '%' THEN 1
WHEN #ColumnName = '' THEN 1
ELSE 0
END = 1
Query Explanation:
When you pass any column name and filter which matches to any records and per column name, it will return those records.
When column name matches and no record matches as per column name it fallback to last ELSE part so it won't return any records as expected.
All the filters apart from ClientID and BirthDate has wild card syntax, which will help for partial match.
In one special case when you don't mention any column name i.e. #ColumnName = '' then all rows will be returned as you didn't want to filter. You can easily change this additional behavior by removing this line WHEN #ColumnName = '' THEN 1
I think or in the where is cleaner here
SELECT ClientID, FirstName, LastName, BirthDate, StreetName, City, State,
ZipCode, CellPhone
FROM dbo.Client
where (#ColumnName = 'ClientID' and ClientID = #Filter)
or (#ColumnName = 'FirstName' and FirstName LIKE #Filter)
or (#ColumnName = 'LastName' and LastName LIKE #Filter)
...;
You should try to refer to the actual column in your where clause, as #ColumnName contains name, rather than referring to the column value. By "WHERE #ColumnName = #Filter", we compare the name of the column. By "WHERE ClientID = #Filter", we compare the value of the column. Maybe, you should do it this way:
SELECT ClientID, FirstName, LastName, BirthDate, StreetName, City, State,
ZipCode, CellPhone
FROM dbo.Client
CASE
WHEN #ColumnName = 'ClientID' THEN ClientID
WHEN #ColumnName = 'FirstName' THEN FirstName
WHEN #ColumnName = 'LastName' THEN LastName
WHEN #ColumnName = 'BirthDate' THEN BirthDate
WHEN #ColumnName = 'StreetName' THEN StreetName
WHEN #ColumnName = 'City' THEN City
WHEN #ColumnName = 'State' THEN State
WHEN #ColumnName = 'ZipCode' THEN ZipCode
WHEN #ColumnName = 'CellPhone' THEN CellPhone
END
LIKE '%' + #Filter + '%';

Parse Prefix First Middle Last Suffix from full name

I need to parse a full name in the format, prefix first middle last suffix, but not all parts may be included. I have the prefix first middle and last working, but Jr gets stuffed in with the last name. How do I get the suffix to come out in a suffix column? Example includes data.
SELECT
FIRST_NAME.INPUT_DATA
,FIRST_NAME.PREFIX
,FIRST_NAME.FIRST_NAME
,CASE WHEN 0 = CHARINDEX(' ',FIRST_NAME.REMAINING)
THEN NULL --no more spaces found, consider remaining to be last name
ELSE SUBSTRING(
FIRST_NAME.REMAINING
,1
,CHARINDEX(' ',FIRST_NAME.REMAINING)-1
)
END AS MIDDLE_NAME
,SUBSTRING(
FIRST_NAME.REMAINING
,1 + CHARINDEX(' ',FIRST_NAME.REMAINING)
,LEN(FIRST_NAME.REMAINING)
) AS LAST_NAME
FROM
(
SELECT
PREFIX.PREFIX
,CASE WHEN 0 = CHARINDEX(' ',PREFIX.REMAINING)
THEN PREFIX.REMAINING --no space found, return the entire string
ELSE SUBSTRING(
PREFIX.REMAINING
,1
,CHARINDEX(' ',PREFIX.REMAINING)-1
)
END AS FIRST_NAME
,CASE WHEN 0 = CHARINDEX(' ',PREFIX.REMAINING)
THEN NULL --no spaces found, consider to be first name
ELSE SUBSTRING(
PREFIX.REMAINING
,CHARINDEX(' ',PREFIX.REMAINING)+1
,LEN(PREFIX.REMAINING)
)
END AS REMAINING
,PREFIX.INPUT_DATA
FROM
(
SELECT --CLEAN_DATA
--if first three characters match list,
--parse as a "PREFIX". else return NULL for PREFIX.
CASE WHEN SUBSTRING(CLEAN_DATA.FULL_NAME,1,3) IN ('MR ','MS ','DR ','MRS')
THEN LTRIM(RTRIM(SUBSTRING(CLEAN_DATA.FULL_NAME,1,3)))
ELSE NULL
END AS PREFIX
,CASE WHEN SUBSTRING(CLEAN_DATA.FULL_NAME,1,3) IN ('MR ','MS ','DR ','MRS')
THEN LTRIM(RTRIM(SUBSTRING(CLEAN_DATA.FULL_NAME,4,LEN(CLEAN_DATA.FULL_NAME))))
ELSE LTRIM(RTRIM(CLEAN_DATA.FULL_NAME))
END AS REMAINING
,CLEAN_DATA.INPUT_DATA
FROM
(
SELECT
--trim leading & trailing spaces to prepare for processing
--replace extra spaces in name
REPLACE(REPLACE(LTRIM(RTRIM(FULL_NAME)),' ',' '),' ',' ') AS FULL_NAME
,FULL_NAME AS INPUT_DATA
FROM
(
--test with test data, or table
--table
--SELECT CONTACT AS FULL_NAME
--FROM CONTACT
--test data
--/*
SELECT 'Andy D Where' AS FULL_NAME
UNION SELECT 'Cathy T Landers' AS FULL_NAME
UNION SELECT 'Ms Annie Wint There' AS FULL_NAME
UNION SELECT 'Frank Fields' AS FULL_NAME
UNION SELECT 'Howdy U Pokes Jr.' AS FULL_NAME
--*/
) SOURCE_DATA
) CLEAN_DATA
) PREFIX
) FIRST_NAME
--credits to JStyons of course
Hope this helps. I have only added Generational SUFFIX titles(Sr, Jr), If more are needed you could add to the Case statement as needed. I am also assuming that your Db is case insensitive.
Assumption (Business Rules):
First Name has no spaces
Middle Name has no spaces
Last name has no spaces
Prefix's are only of the form 'MR ','MS ','DR ','MRS' with no period "."
Suffix's are only of the form 'Sr', 'Jr', 'Sr.', 'Jr.'
The Database is case insensitive
IF OBJECT_ID('tempdb..#cte_SpaceFix') IS NOT NULL
DROP TABLE #cte_SpaceFix
;WITH cte_OriginalData (FullName)
AS (
SELECT 'Andy D Where'
UNION
SELECT 'Cathy T Landers'
UNION
SELECT 'Ms Annie Wint There'
UNION
SELECT 'Ms Annie Wint There Jr'
UNION
SELECT 'Mrs Annie There Jr'
UNION
SELECT 'Frank Fields'
UNION
SELECT 'Howdy U Pokes Jr.'
UNION
SELECT 'Howdy U Pokes Sr.'
UNION
SELECT 'Cathy T Landers Jr'
UNION
SELECT 'Landers Jr'
)
,cte_FullNameRemoveTail AS
(
SELECT LTRIM(RTRIM(FullName)) AS FullName
FROM cte_OriginalData
)
,cte_Parse_Prefix(Prefix,FullFirst_Prefix,FullName) AS
(
SELECT CASE
WHEN SUBSTRING(FullName, 1, 3) IN ('MR ','MS ','DR ','MRS')
THEN LTRIM(RTRIM(SUBSTRING(FullName, 1, 3)))
ELSE NULL
END AS Prefix,
CASE
WHEN SUBSTRING(FullName, 1, 3) IN ('MR ','MS ','DR ','MRS')
THEN LTRIM(RTRIM(SUBSTRING(FullName, 4, 8000)))
ELSE LTRIM(RTRIM(FullName))
END AS FullFirst_Prefix,
FullName
FROM cte_FullNameRemoveTail
)
,cte_Parse_Suffix(Prefix,FullFirst_Prefix_Suffix,Suffix,FullName) AS
(
SELECT Prefix,
CASE
WHEN RIGHT(FullFirst_Prefix,3) = ' JR' THEN LTRIM(RTRIM(SUBSTRING(FullFirst_Prefix,1,LEN(FullFirst_Prefix)-3)))
WHEN RIGHT(FullFirst_Prefix,4) = ' JR.' THEN LTRIM(RTRIM(SUBSTRING(FullFirst_Prefix,1,LEN(FullFirst_Prefix)-4)))
WHEN RIGHT(FullFirst_Prefix,3) = ' SR' THEN LTRIM(RTRIM(SUBSTRING(FullFirst_Prefix,1,LEN(FullFirst_Prefix)-3)))
WHEN RIGHT(FullFirst_Prefix,4) = ' SR.' THEN LTRIM(RTRIM(SUBSTRING(FullFirst_Prefix,1,LEN(FullFirst_Prefix)-4)))
ELSE LTRIM(RTRIM(FullFirst_Prefix))
END AS FullFirst_Prefix_Suffix,
CASE
WHEN RIGHT(FullFirst_Prefix,3) = ' JR'
OR RIGHT(FullFirst_Prefix,4) = ' JR.'
THEN 'Jr'
WHEN RIGHT(FullFirst_Prefix,3) = ' SR'
OR RIGHT(FullFirst_Prefix,4) = ' SR.'
THEN 'Sr'
ELSE NULL
END AS Suffix,
FullName
FROM cte_Parse_Prefix
)
,cte_SpaceFix(Prefix, FullFirst_Prefix_Suffix, Suffix, FullName) AS
(
SELECT Prefix,
CASE
WHEN LEN(FullFirst_Prefix_Suffix) - LEN(REPLACE(FullFirst_Prefix_Suffix, ' ', '')) > 2 THEN REPLACE(REPLACE(REPLACE(REPLACE(FullFirst_Prefix_Suffix,SPACE(5), SPACE(1)),SPACE(4), SPACE(1)),SPACE(3), SPACE(1)),SPACE(2), SPACE(1))
ELSE FullFirst_Prefix_Suffix
END AS FullFirst_Prefix_Suffix,
Suffix,
FullName
FROM cte_Parse_Suffix
)
SELECT * INTO #cte_SpaceFix
FROM cte_SpaceFix
;WITH cte_Parse_FirstName(Prefix, FirstName, Suffix, FullFirst_Prefix_Suffix_FirstName, FullName) AS
(
SELECT Prefix,
CASE
WHEN FullFirst_Prefix_Suffix IS NULL THEN NULL
WHEN LEN(FullFirst_Prefix_Suffix) - LEN(REPLACE(FullFirst_Prefix_Suffix, ' ', '')) >= 1 THEN LEFT(FullFirst_Prefix_Suffix,CHARINDEX(' ',FullFirst_Prefix_Suffix))
WHEN LEN(FullFirst_Prefix_Suffix) - LEN(REPLACE(FullFirst_Prefix_Suffix, ' ', '')) = 0 THEN FullFirst_Prefix_Suffix
ELSE NULL
END AS FirstName,
Suffix,
CASE
WHEN FullFirst_Prefix_Suffix IS NULL THEN NULL
WHEN LEN(FullFirst_Prefix_Suffix) - LEN(REPLACE(FullFirst_Prefix_Suffix, ' ', '')) >= 1 THEN LTRIM(RTRIM(REPLACE(FullFirst_Prefix_Suffix,LEFT(FullFirst_Prefix_Suffix,CHARINDEX(' ',FullFirst_Prefix_Suffix)),'')))
WHEN LEN(FullFirst_Prefix_Suffix) - LEN(REPLACE(FullFirst_Prefix_Suffix, ' ', '')) = 0 THEN NULL
ELSE NULL
END AS FullFirst_Prefix_Suffix_FirstName,
FullName
FROM #cte_SpaceFix
)
,cte_Parse_LastName(Prefix, FirstName, LastName, Suffix, MiddleName, FullName) AS
(
SELECT Prefix,
FirstName,
CASE
WHEN FullFirst_Prefix_Suffix_FirstName IS NULL THEN NULL
WHEN LEN(FullFirst_Prefix_Suffix_FirstName) - LEN(REPLACE(FullFirst_Prefix_Suffix_FirstName, ' ', '')) >= 1 THEN SUBSTRING(FullFirst_Prefix_Suffix_FirstName,CHARINDEX(' ',FullFirst_Prefix_Suffix_FirstName)+1,8000)
WHEN LEN(FullFirst_Prefix_Suffix_FirstName) - LEN(REPLACE(FullFirst_Prefix_Suffix_FirstName, ' ', '')) = 0 THEN FullFirst_Prefix_Suffix_FirstName
ELSE NULL
END AS LastName,
Suffix,
CASE
WHEN FullFirst_Prefix_Suffix_FirstName IS NULL THEN NULL
WHEN LEN(FullFirst_Prefix_Suffix_FirstName) - LEN(REPLACE(FullFirst_Prefix_Suffix_FirstName, ' ', '')) >= 1 THEN LEFT(FullFirst_Prefix_Suffix_FirstName,CHARINDEX(' ',FullFirst_Prefix_Suffix_FirstName))
ELSE NULL
END AS MiddleName,
FullName
FROM cte_Parse_FirstName
)
SELECT Prefix, FirstName, MiddleName, LastName, Suffix--, FullName
FROM cte_Parse_LastName
IF OBJECT_ID('tempdb..#cte_SpaceFix') IS NOT NULL
DROP TABLE #cte_SpaceFix

How to get substring with T-sql built-in function

Suppose I have a string like "firstname lastname" stored in Mytable column Name.
then I want to attract firstname and lastname in SQL like
select FirstName = substring(Name, ..), LastName=substring(Name, ...) from Mytable
How to resolve this issue?
Leverage PARSENAME?
SELECT
PARSENAME(REPLACE(name, ' ', '.'), 2),
PARSENAME(REPLACE(name, ' ', '.'), 1)
FROM
MyTable;
EDIT working example:
DECLARE #MyTable table (name varchar(25))
INSERT #MyTable
SELECT 'Joe Smith'
UNION SELECT 'Bill Jones'
UNION SELECT 'Billy Bob Braxton'
UNION SELECT 'Mark'
SELECT
PARSENAME(REPLACE(name, ' ', '.'), 2),
PARSENAME(REPLACE(name, ' ', '.'), 1)
FROM
#MyTable;
Output:
------ --------
Bill Jones
Bob Braxton
Joe Smith
NULL Mark
(4 row(s) affected)
You could use charindex to find the location of the space:
select FirstName = substring(Name, 1, charindex(' ', Name)),
LastName = substring(Name, charindex(' ', Name) + 1, len(Name))
from MyTable
This is littered with assumptions, e.g. there's exactly one space (what if my name is Billy Bob Harris, or Madonna?)
Rather than do it the way you're asking though, you might be better off using PARSENAME (as another comment says!).
You can also think about splitting string using space as seperator and then use splitted items as firstname and lastname

How can I use optional parameters in a T-SQL stored procedure?

I am creating a stored procedure to do a search through a table. I have many different search fields, all of which are optional. Is there a way to create a stored procedure that will handle this? Let's say I have a table with four fields: ID, FirstName, LastName and Title. I could do something like this:
CREATE PROCEDURE spDoSearch
#FirstName varchar(25) = null,
#LastName varchar(25) = null,
#Title varchar(25) = null
AS
BEGIN
SELECT ID, FirstName, LastName, Title
FROM tblUsers
WHERE
FirstName = ISNULL(#FirstName, FirstName) AND
LastName = ISNULL(#LastName, LastName) AND
Title = ISNULL(#Title, Title)
END
This sort of works. However it ignores records where FirstName, LastName or Title are NULL. If Title is not specified in the search parameters I want to include records where Title is NULL - same for FirstName and LastName. I know I could probably do this with dynamic SQL but I would like to avoid that.
Dynamically changing searches based on the given parameters is a complicated subject and doing it one way over another, even with only a very slight difference, can have massive performance implications. The key is to use an index, ignore compact code, ignore worrying about repeating code, you must make a good query execution plan (use an index).
Read this and consider all the methods. Your best method will depend on your parameters, your data, your schema, and your actual usage:
Dynamic Search Conditions in T-SQL by by Erland Sommarskog
The Curse and Blessings of Dynamic SQL by Erland Sommarskog
If you have the proper SQL Server 2008 version (SQL 2008 SP1 CU5 (10.0.2746) and later), you can use this little trick to actually use an index:
Add OPTION (RECOMPILE) onto your query, see Erland's article, and SQL Server will resolve the OR from within (#LastName IS NULL OR LastName= #LastName) before the query plan is created based on the runtime values of the local variables, and an index can be used.
This will work for any SQL Server version (return proper results), but only include the OPTION(RECOMPILE) if you are on SQL 2008 SP1 CU5 (10.0.2746) and later. The OPTION(RECOMPILE) will recompile your query, only the verison listed will recompile it based on the current run time values of the local variables, which will give you the best performance. If not on that version of SQL Server 2008, just leave that line off.
CREATE PROCEDURE spDoSearch
#FirstName varchar(25) = null,
#LastName varchar(25) = null,
#Title varchar(25) = null
AS
BEGIN
SELECT ID, FirstName, LastName, Title
FROM tblUsers
WHERE
(#FirstName IS NULL OR (FirstName = #FirstName))
AND (#LastName IS NULL OR (LastName = #LastName ))
AND (#Title IS NULL OR (Title = #Title ))
OPTION (RECOMPILE) ---<<<<use if on for SQL 2008 SP1 CU5 (10.0.2746) and later
END
The answer from #KM is good as far as it goes but fails to fully follow up on one of his early bits of advice;
..., ignore compact code, ignore worrying about repeating code, ...
If you are looking to achieve the best performance then you should write a bespoke query for each possible combination of optional criteria. This might sound extreme, and if you have a lot of optional criteria then it might be, but performance is often a trade-off between effort and results. In practice, there might be a common set of parameter combinations that can be targeted with bespoke queries, then a generic query (as per the other answers) for all other combinations.
CREATE PROCEDURE spDoSearch
#FirstName varchar(25) = null,
#LastName varchar(25) = null,
#Title varchar(25) = null
AS
BEGIN
IF (#FirstName IS NOT NULL AND #LastName IS NULL AND #Title IS NULL)
-- Search by first name only
SELECT ID, FirstName, LastName, Title
FROM tblUsers
WHERE
FirstName = #FirstName
ELSE IF (#FirstName IS NULL AND #LastName IS NOT NULL AND #Title IS NULL)
-- Search by last name only
SELECT ID, FirstName, LastName, Title
FROM tblUsers
WHERE
LastName = #LastName
ELSE IF (#FirstName IS NULL AND #LastName IS NULL AND #Title IS NOT NULL)
-- Search by title only
SELECT ID, FirstName, LastName, Title
FROM tblUsers
WHERE
Title = #Title
ELSE IF (#FirstName IS NOT NULL AND #LastName IS NOT NULL AND #Title IS NULL)
-- Search by first and last name
SELECT ID, FirstName, LastName, Title
FROM tblUsers
WHERE
FirstName = #FirstName
AND LastName = #LastName
ELSE
-- Search by any other combination
SELECT ID, FirstName, LastName, Title
FROM tblUsers
WHERE
(#FirstName IS NULL OR (FirstName = #FirstName))
AND (#LastName IS NULL OR (LastName = #LastName ))
AND (#Title IS NULL OR (Title = #Title ))
END
The advantage of this approach is that in the common cases handled by bespoke queries the query is as efficient as it can be - there's no impact by the unsupplied criteria. Also, indexes and other performance enhancements can be targeted at specific bespoke queries rather than trying to satisfy all possible situations.
You can do in the following case,
CREATE PROCEDURE spDoSearch
#FirstName varchar(25) = null,
#LastName varchar(25) = null,
#Title varchar(25) = null
AS
BEGIN
SELECT ID, FirstName, LastName, Title
FROM tblUsers
WHERE
(#FirstName IS NULL OR FirstName = #FirstName) AND
(#LastNameName IS NULL OR LastName = #LastName) AND
(#Title IS NULL OR Title = #Title)
END
however depend on data sometimes better create dynamic query and execute them.
Five years late to the party.
It is mentioned in the provided links of the accepted answer, but I think it deserves an explicit answer on SO - dynamically building the query based on provided parameters. E.g.:
Setup
-- drop table Person
create table Person
(
PersonId INT NOT NULL IDENTITY(1, 1) CONSTRAINT PK_Person PRIMARY KEY,
FirstName NVARCHAR(64) NOT NULL,
LastName NVARCHAR(64) NOT NULL,
Title NVARCHAR(64) NULL
)
GO
INSERT INTO Person (FirstName, LastName, Title)
VALUES ('Dick', 'Ormsby', 'Mr'), ('Serena', 'Kroeger', 'Ms'),
('Marina', 'Losoya', 'Mrs'), ('Shakita', 'Grate', 'Ms'),
('Bethann', 'Zellner', 'Ms'), ('Dexter', 'Shaw', 'Mr'),
('Zona', 'Halligan', 'Ms'), ('Fiona', 'Cassity', 'Ms'),
('Sherron', 'Janowski', 'Ms'), ('Melinda', 'Cormier', 'Ms')
GO
Procedure
ALTER PROCEDURE spDoSearch
#FirstName varchar(64) = null,
#LastName varchar(64) = null,
#Title varchar(64) = null,
#TopCount INT = 100
AS
BEGIN
DECLARE #SQL NVARCHAR(4000) = '
SELECT TOP ' + CAST(#TopCount AS VARCHAR) + ' *
FROM Person
WHERE 1 = 1'
PRINT #SQL
IF (#FirstName IS NOT NULL) SET #SQL = #SQL + ' AND FirstName = #FirstName'
IF (#LastName IS NOT NULL) SET #SQL = #SQL + ' AND FirstName = #LastName'
IF (#Title IS NOT NULL) SET #SQL = #SQL + ' AND Title = #Title'
EXEC sp_executesql #SQL, N'#TopCount INT, #FirstName varchar(25), #LastName varchar(25), #Title varchar(64)',
#TopCount, #FirstName, #LastName, #Title
END
GO
Usage
exec spDoSearch #TopCount = 3
exec spDoSearch #FirstName = 'Dick'
Pros:
easy to write and understand
flexibility - easily generate the query for trickier filterings (e.g. dynamic TOP)
Cons:
possible performance problems depending on provided parameters, indexes and data volume
Not direct answer, but related to the problem aka the big picture
Usually, these filtering stored procedures do not float around, but are being called from some service layer. This leaves the option of moving away business logic (filtering) from SQL to service layer.
One example is using LINQ2SQL to generate the query based on provided filters:
public IList<SomeServiceModel> GetServiceModels(CustomFilter filters)
{
var query = DataAccess.SomeRepository.AllNoTracking;
// partial and insensitive search
if (!string.IsNullOrWhiteSpace(filters.SomeName))
query = query.Where(item => item.SomeName.IndexOf(filters.SomeName, StringComparison.OrdinalIgnoreCase) != -1);
// filter by multiple selection
if ((filters.CreatedByList?.Count ?? 0) > 0)
query = query.Where(item => filters.CreatedByList.Contains(item.CreatedById));
if (filters.EnabledOnly)
query = query.Where(item => item.IsEnabled);
var modelList = query.ToList();
var serviceModelList = MappingService.MapEx<SomeDataModel, SomeServiceModel>(modelList);
return serviceModelList;
}
Pros:
dynamically generated query based on provided filters. No parameter sniffing or recompile hints needed
somewhat easier to write for those in the OOP world
typically performance friendly, since "simple" queries will be issued (appropriate indexes are still needed though)
Cons:
LINQ2QL limitations may be reached and forcing a downgrade to LINQ2Objects or going back to pure SQL solution depending on the case
careless writing of LINQ might generate awful queries (or many queries, if navigation properties loaded)
Extend your WHERE condition:
WHERE
(FirstName = ISNULL(#FirstName, FirstName)
OR COALESCE(#FirstName, FirstName, '') = '')
AND (LastName = ISNULL(#LastName, LastName)
OR COALESCE(#LastName, LastName, '') = '')
AND (Title = ISNULL(#Title, Title)
OR COALESCE(#Title, Title, '') = '')
i. e. combine different cases with boolean conditions.
This also works:
...
WHERE
(FirstName IS NULL OR FirstName = ISNULL(#FirstName, FirstName)) AND
(LastName IS NULL OR LastName = ISNULL(#LastName, LastName)) AND
(Title IS NULL OR Title = ISNULL(#Title, Title))