I recently started developing/improving the way my search algorithm works. There is few different queries in the system that will use this approach. I'm new in stored procedures world and from what I researched they should improve security, performance and save some code redundancy. Here is example of what I have done in one of my stored procedures for Account search:
ALTER PROCEDURE [dbo].[SearchAccounts]
#Status INT = NULL,
#Type INT = NULL,
#FilterBy INT = NULL,
#Username VARCHAR(50) = NULL,
#Email VARCHAR(80) = NULL,
#LastName VARCHAR(50) = NULL,
#FirstName VARCHAR(50) = NULL,
#FullName VARCHAR(100) = NULL
WITH RECOMPILE
AS
DECLARE #AccountStatus INT = #Status;
DECLARE #AccountType INT = #Type;
DECLARE #AccountFilter INT = #FilterBy;
DECLARE #AccountUsername VARCHAR(50) = #Username;
DECLARE #AccountEmail VARCHAR(80) = #Email;
DECLARE #AccountLast VARCHAR(50) = #LastName;
DECLARE #AccountFirst VARCHAR(50) = #FirstName;
DECLARE #AccountFull VARCHAR(10) = #FullName;
SELECT
A.AccountID, A.FirstName, A.LastName, A.Middle, A.Email,
A.IsUser, A.ActiveUser, A.SystemAdmin, A.AccessType,
A.AccessLevel, A.UserName, A.IsStaff, A.ActiveStaff,
A.Position AS PositionCode, M.Name AS Position
FROM
Accounts AS A
LEFT OUTER JOIN
Master AS M ON M.Tblid = 'STAFF_POS'
AND M.Code = A.Position
WHERE
( -- If Account Type is 1 (User)
(#AccountType = 1 AND A.IsUser = 1)
OR -- If Account Type is 2 (Staff)
(#AccountType = 2 AND A.IsStaff = 1)
OR -- Or if Account type is 0 (All accounts)
(#AccountType = 0 AND 1 = 1)
)
AND
(
(-- If account type is user and Status is 1 (Active) or 0 (Inactive) or 2(pull active and inactive)
#AccountType = 1
AND
(#AccountStatus = 0 OR #AccountStatus = 1) AND ActiveUser = #AccountStatus)
OR
(#AccountType = 1 AND #AccountStatus = 2 AND 1 = 1
)
OR
(-- If account type is staff and Status is 1 (Active) or 0 (Inactive) or 2 (pull active and inactive)
#AccountType = 2
AND
(#AccountStatus = 0 OR #AccountStatus = 1) AND ActiveStaff = #AccountStatus)
OR
(#AccountType = 2 AND #AccountStatus = 2 AND 1 = 1
)
OR
(-- If account type is all pull all accounts active and inactive
(#AccountType != 1 AND #AccountType != 2 AND 1 = 1)
)
)
AND
( -- Filter is 1 then check user name.
(#AccountFilter = 1 AND A.UserName LIKE '%'+#AccountUsername+'%')
OR -- Filter is 2 then check email.
(#AccountFilter = 2 AND A.Email = #AccountEmail)
-- Here if filter is 3 then I should check First or Last or Full Name.
-- Still not sure what is the best approach to filter on the name fields.
)
ORDER BY
A.LastName, A.FirstName
As you can see query above has few different filters. First user can choose if they want to search Users, Staff or pull All account types. Then to choose if they want to pull Active, Inactive or all records. Last thing is to pick the filter. I give them an option to search Username, Email or Name. Each Account has First, Last name. What would be a good option to search for those names? Check just one full name or I have to check first and last separately? First and Last name allow this set of characters in the Accounts form : A-Z, space, dash, apostrophe, period, comma - no other special characters
There are probably dozens of ways to accomplish such a goal. But one way I would prefer (if I was using a query like the one presented) would be like this:
AND A.LNAME LIKE
CASE WHEN Len(#AccountLast) > 0 THEN
'%' + #AccountLast + '%'
ELSE '%'
END
AND A.FNAME LIKE
CASE WHEN Len(#AccountFirst) > 0 THEN
'%' + #AccountFirst + '%'
ELSE '%'
END
Related
I have a WHERE clause that has a nested OR statement, as seen here:
-- Declaration of variables
DECLARE
#PageSize INT,
#PageNumber INT,
#SearchPhraseOne VARCHAR(20),
#SearchPhraseTwo VARCHAR(20),
#FilterCategory VARCHAR(30),
#FilterStatus TINYINT,
#NeedsFollowUp TINYINT,
#NeedsTraining TINYINT,
#NeedsInitialVacc TINYINT;
SET #PageNumber = 1;
SET #PageSize = 100;
SET #SearchPhraseOne = null;
SET #SearchPhraseTwo = null;
SET #FilterCategory = 'High Exposure';
SET #FilterStatus = null;
SET #NeedsFollowUp = 1;
SET #NeedsTraining = null;
SET #NeedsInitialVacc = null;
select * from(
select
vel.fullName,
vel.EecEmpNo,
vel.EecLocation,
vel.EecDateOfLastHire,
job.JbcDesc,
vei.eiInitialBBPDate,
vei.eiVCGivenDate,
iif(jv.verTypeName is null, 'Low Risk', jv.verTypeName) as vaccineCategory,
vel.eecEmplStatus,
count(distinct vh.vhID) as vaccCount,
max(isnull(vh.vhNextDateScheduled, null)) as maxNextDateScheduled,
max(cast(vh.vhSeriesComplete as int)) as seriesComplete,
iif(vel.eecEmplStatus = 'T', null,
coalesce(iif(max(cast(vh.vhSeriesComplete as int)) = 1, null, max(isnull(vh.vhNextDateScheduled, null))), -- check if the vaccine items have a SeriesComplete of 1, otherwise use NextDateScheduled
iif(vei.eiInitialBBPDate is not null, null, vel.EecDateOfLastHire), -- check if the InitialBBPDate is not null, if it isn't use DateOfLastHire
iif(vei.eiVCGivenDate is not null, null, vel.EecDateOfLastHire), null)) as actionDate -- check if the OrientationDate is not null, if it isn't use DateOfLastHire
-- if all three of these values are null then there's no ActionDate
-- Terminated employees will not have an action date assigned even if there's a match
from dbo.vaccEmpList vel
left join dbo.vaccEmployeeInfo vei on vei.eiEmployeeNo = vel.EecEmpNo
left join dbo.vaccVaccinationHistory vh on vh.vhEmployeeNo = vel.EecEmpNo
left join dbo.vaccVaccineTypeLookup vt on vh.vhVaccinationTypeID = vt.vtlVaccineTypeID and vt.vtIsActive = 1 -- Only get active vaccination types
join dbo.U_JobCode job on vel.EecJobCode = job.JbcJobCode
left join dbo.JobVerficationXref jv on vel.EecJobCode = jv.JobCode and jv.verName = 'Vaccination Category'
group by vel.fullName, vel.EecEmpNo, job.JbcDesc, jv.verTypeName, vel.EecLocation, vel.eecEmplStatus, vei.eiInitialBBPDate, vei.eiVCGivenDate, vel.EecDateOfLastHire
) as searchResults
where (
(
#SearchPhraseOne is null
or searchResults.fullName like #SearchPhraseOne + '%'
or searchResults.EecEmpNo = #SearchPhraseOne
)
and (
#SearchPhraseTwo is null
or searchResults.fullName like #SearchPhraseTwo + '%'
or searchResults.EecEmpNo = #SearchPhraseTwo
) -- Employee Name/ID
and (
#FilterStatus is null
or (searchResults.eecEmplStatus = 'A' or searchResults.eecEmplStatus = 'L')
) -- Employee Status
and (
#FilterCategory is null
or searchResults.vaccineCategory = #FilterCategory
) -- Employee Vaccination Category
and ( -- ISSUES OCCUR HERE
(#NeedsTraining is null
or (searchResults.actionDate is not null
and (searchResults.eiInitialBBPDate is null or searchResults.eiVCGivenDate is null))
) -- Needs Training if either of these two date values are null
or (#NeedsInitialVacc is null
or (searchResults.actionDate is not null
and (searchResults.vaccCount = 0))
-- Needs Initial Vaccination if there are no vaccine records
)
or (#NeedsFollowUp is null
or (searchResults.actionDate is not null
and ((searchResults.seriesComplete is null or searchResults.seriesComplete = 0) and searchResults.maxNextDateScheduled is not null))
-- Needs a follow-up date if no series complete was detected
)
)
)
The #NeedsFollowUp, #NeedsInitialVacc, and #NeedsTraining variables are all set by the variables above. When one or more of these are set to "1", the query should return employee entries that match the criteria inside their related statements. For example, if the "NeedsFollowUp" and "NeedsTraining" values are set to "1" then the query should return employees that need a follow-up or employees that need training.
Right now, when I set all three to "1" I receive the combined results I'm looking for, but if any of them are set to null, then the query doesn't return the correct results.
EDIT: Here's a reproducible example of what I'm seeing.
I think the way the clauses are set up is causing an issue, but I'm not really sure how to fix this. How can I get the OR statements to work in the way I described above?
I was able to make the OR clauses work correcting by switching from is null to is not null in my where clauses. Using the minimal example, it would look like this:
select * from AGENTS
where (
(#NeedsName is not null and AGENTS.AGENT_NAME is null)
or
(#NeedsCountry is not null and AGENTS.COUNTRY is null)
or
(#NeedsCountry is null and #NeedsName is null)
)
Be sure to include an additional clause for when all options are NULL, so that you can return the appropriate number of rows.
Here's a working version.
I have two fields (#EmployeeId,#SSOId) out of which one value can come or both can come, but when i am applying OR condition it is not giving me correct output. What i am doing wrong ?
ALTER PROCEDURE [dbo].[usp_User_GetDetails] (
#UserId INT = NULL
,#ADSId NVARCHAR(32) = NULL
,#EmployeeId NVARCHAR(32) = NULL
,#SSOId NVARCHAR(32) = NULL
,#UserName NVARCHAR(100) = NULL
)
AS
*/
SET NOCOUNT ON;
BEGIN
SELECT [USER_ID] AS UserId
,[FIRST_NM] AS FirstName
,[LST_NM] AS LastName
,[FULL_NM] AS FullName
,[ADS_USER_ID] AS ADSId
,[SEG_ID] AS SegmentId
,[PHONE_NO] AS PhoneNo
,[FAX_NO] AS FaxNo
,[EMP_ID] AS EmployeeId
,[EMAIL_AD_TX] AS Email
,[SSO_ID] AS SSOId
,[SFDC_IN] AS IsSFDC
,[USER_SFDC_ID] AS UserSFDCId
,[MGR_SFDC_ID] AS ManagerSFDCId
,[ACT_IN] AS IsActive
,[SYS_USER_IN] AS IsSystemUser
,[PORFOLIO_OWN_IN] AS CanHavePortfolio
,[MGR_ID] AS ManagerId
,[LST_LOG_IN_TS] AS LastLoginDate
,[EMP_BAND_TX] AS Band
,[CREAT_TS] AS CreatedDate
,[CREAT_BY_USER_ID] AS CreatedBy
,[LST_UPDT_TS] AS UpdatedDate
,[LST_UPDT_BY_USER_ID] AS UpdatedBy
FROM [dbo].[USER] WITH (NOLOCK)
WHERE ([EMP_ID] = ISNULL(#EmployeeId, [EMP_ID])OR [SSO_ID] = ISNULL(#SSOId, [SSO_ID])
AND [ADS_USER_ID] = ISNULL(#ADSId, [ADS_USER_ID])
AND [USER_ID] = ISNULL(#UserId, [USER_ID])
AND [FULL_NM] LIKE CASE
WHEN #UserName IS NOT NULL
THEN '%' + #UserName + '%'
ELSE [FULL_NM]
END
END
I don't think the parentheses are balanced correctly. In any case, I would write this without the ISNULL():
WHERE ((#EmployeeId IS NULL OR EMP_ID = #EmployeeId) OR
(#SSOId IS NULL OR SSO_ID = #SSOId)
) AND
(#ADSId IS NULL OR ADS_USER_ID = #ADSId) AND
(#UserId IS NULL OR USER_ID = #UserId) AND
(#UserName IS NULL OR FULL_NM LIKE '%' + #UserName + '%')
I am guessing that the OR is for the first two conditions. This is where the parens don't seem to line up in the query in the question.
I prefer this construct for two reasons. First, it handles NULL values in the column values as well as the parameter values. And second -- because it is more general -- it is one of the standard two ways I use to handle optional parameters (the other is to use dynamic SQL which can make use of indexes).
Query seems to be okay .Are you passing DBNull from you C# code or empty text
WHERE (#EmployeeId IS NULL OR (EMP_ID = #EmployeeId))
AND (#SSOId IS NULL OR (SSO_ID = #SSOId))
AND [ADS_USER_ID] = ISNULL(#ADSId, [ADS_USER_ID])
AND [USER_ID] = ISNULL(#UserId, [USER_ID])
AND [FULL_NM] LIKE CASE
WHEN #UserName IS NOT NULL
THEN '%' + #UserName + '%'
ELSE [FULL_NM]
Used this script
WHERE EMP_ID = CASE WHEN ISNULL(#EmployeeId,0) > 0 THEN #EmployeeId ELSE EMP_ID END AND SSO_ID = CASE WHEN ISNULL(#SSOId,0) > 0 THEN #SSOId ELSE SSO_ID END
I'm simply unable to to find a solution for this stored procedure query.
I have Person and a Student table. In Person table I store a bool value isFootballPlayer and Student table has an FK PersonId that links it to the Person table.
I have a web app where there is a search functionality that includes checkbox for Student, Person, and Football Player, in order to filter the results. So when only Person is selected, it will return all which are not Players nor Students.
My stored procedure looks like this:
"SP stuff"
#IncludePerson bit,
#IncludePlayer bit,
#IncludeStudent bit
AS
BEGIN
WITH i (Id)
SELECT
p.PersonId as Id
FROM
Person p
LEFT OUTER JOIN
Student s ON s.PersonId = p.PersonId
WHERE
(s.PersonId IS NULL OR #IncludeStudent = 1) AND
(p.IsFootballPlayer = 0 OR #IncludePlayer = 1) AND
((s.PersonId > 0) OR #IncludePerson = 1)
)
SELECT i.Id
FROM i
GROUP BY i.Id
END
The issue here is that some of the students can also be football players, and so when only the student checkbox is checked, the result excludes the students that are also football players, unless both checkboxes are checked.
Can anyone help me in the right direction and give me some tip on how I can modify the stored procedure to manage to show students that are also football players, without having to check both checkboxes?
Thanks
"SP stuff"
#IncludePerson bit,
#IncludePlayer bit,
#IncludeStudent bit
AS
BEGIN
Declare #Sql NVARCHAR(MAX);
SET #Sql = N'SELECT DISTINCT p.PersonId as Id
FROM Person p
WHERE 1 = 1 '
-- When only Person checkbox is checked
+ CASE WHEN (#IncludePerson = 1 AND #IncludeStudent = 0 AND #IncludePlayer = 0)
THEN N' AND p.IsFootballPlayer = 0
AND NOT EXISTS (SELECT 1 FROM Student s
WHERE s.PersonId = p.PersonId)' ELSE N'' END
-- When only Student checkbox is checked
+ CASE WHEN (#IncludePerson = 0 AND #IncludeStudent = 1 AND #IncludePlayer = 0)
THEN N' AND EXISTS (SELECT 1 FROM Student s
WHERE s.PersonId = p.PersonId)' ELSE N'' END
-- When only Player checkbox is checked
+ CASE WHEN (#IncludePerson = 0 AND #IncludeStudent = 0 AND #IncludePlayer = 1)
THEN N' AND p.IsFootballPlayer = 1 ' ELSE N'' END
-- And you can add as many as combinations you want
-- Finally execute the dynamically built sql query
Exec sp_executesql #Sql
END
Why don't you set the variable #IncludePlayervalue = 1 inside your stored procedure when #IncludeStudent = 1 like mentioned below -
"SP stuff"
#IncludePerson bit,
#IncludePlayer bit,
#IncludeStudent bit
AS
BEGIN
if #IncludeStudent = 1
begin
set #IncludePlayer = 1
end
WITH i (Id)
SELECT
p.PersonId as Id
FROM
Person p
LEFT OUTER JOIN
Student s ON s.PersonId = p.PersonId
WHERE
(s.PersonId IS NULL OR #IncludeStudent = 1) AND
(p.IsFootballPlayer = 0 OR #IncludePlayer = 1) AND
((s.PersonId > 0) OR #IncludePerson = 1)
)
SELECT i.Id
FROM i
GROUP BY i.Id
END
I have a stored procedure like this:
ALTER PROCEDURE [dbo].[hr_SearchVacanciesAdmin]
#SearchText NVARCHAR(50) = NULL,
#CompanyName NVARCHAR(100) = NULL,
#DutyStationID INT = NULL,
#VacancyCategoryIDs VARCHAR(1000) = NULL,
#Status INT,
#Language INT = 1
BEGIN
SELECT * from table
where
deleted = 0
and status = #status
and catname = &searchtext
..
..
..
END
I want to add case statement in where condition so that if #status = 4 then delete = is not checked. I tried following but not working
WHERE
1 = CASE
WHEN #Status = 4
THEN #status
WHEN dbo.hr_Vacancies.Deleted = 0
--Search Criteria
AND dbo.hr_Vacancies.Status = #Status
THEN #Status
END
You don't need a CASE statement to acheive this functionality. The following will do fine:
SELECT *
from table
where (deleted = 0 OR #status=4)
and status = #status
and catname = #searchtext
Note also that your code refers to &searchtext in the where clause, but this should be #searchtext as per this answer (ie prefix with # not &).
#RB is correct you don't need a case for that but you can use a case with this trick, which is useful in other scenarios as well:
Where deleted = case #status when 4 then deleted else 0 end
You may use this construct for the SELECT query to start with:
Select * from table
WHERE
((1 = CASE WHEN #Status = 4 THEN 1 ELSE 0 END)
OR
dbo.hr_Vacancies.Deleted = 0)
AND
dbo.hr_Vacancies.Status = #Status
Looking for an elegant way to workaround this...
DECLARE #ZIP INT
SET #ZIP = 55555
IF #ZIP = ALL(SELECT ZIP FROM PEOPLE WHERE PERSONTYPE = 1)
PRINT 'All people of type 1 have the same zip!'
ELSE
PRINT 'Not All people of type 1 have the same zip!'
The issue is that, if (SELECT ZIP FROM PEOPLE WHERE PERSONTYPE = 1) returns no records, then the above IF evaluates to true. I'm looking for a way to make this evaluate to false when there are no records returned by the ALL's subquery.
My current solution:
DECLARE #ZIP INT
SET #ZIP = 55555
DECLARE #ALLZIPS TABLE (INT ZIP)
INSERT INTO #ALLZIPS
SELECT ZIP FROM PEOPLE WHERE PERSONTYPE = 1
IF EXISTS(SELECT TOP 1 * FROM #ALLZIPS) AND (#ZIP = ALL (SELECT ZIP FROM #ALLZIPS))
PRINT 'All people of type 1 have the same zip!'
ELSE
PRINT 'Not All people of type 1 have the same zip!'
Use:
IF EXISTS(SELECT NULL
FROM PEOPLE p
WHERE p.persontype = 1
HAVING MIN(p.zip) = #Zip
AND MAX(p.zip) = #Zip)
PRINT 'All people of type 1 have the same zip!'
ELSE
PRINT 'Not All people of type 1 have the same zip!'
Jumping in:
IF (SELECT SUM(CASE WHEN ZIP = #ZIP THEN 0 ELSE 1 END)
FROM PEOPLE WHERE PERSONTYPE = 1) = 0
PRINT 'All people of type 1 have the same zip!'
ELSE
PRINT 'Not All people of type 1 have the same zip!'
Consider using EXISTS as well.
IF #ZIP = ALL(SELECT ZIP FROM PEOPLE WHERE PERSONTYPE = 1)
AND EXISTS(SELECT 1 FROM PEOPLE WHERE PERSONTYPE = 1)