I’m running a stored procedure with multiple joins with one table that contains over 600,000 records. The problem is the procedure is very slow and can take minutes to execute. We have indexed the relevant tables columns and still no luck.
What can we do to help improve performance? The query is posted below.
Thanks
with CTE
as
(
select * from
(
select distinct c.ContactId, c.FirstName, c.LastName,
(select top 1 ce.Email from dbo.ContactEmails as ce where ce.ContactId = c.ContactId and ce.IsPrimary = 1) as Email,
comp.CompanyName, j.JobName, c.MobileNumber, c.OfficeNumber, cse.DateSent, MAX(cse.DateSent) over(partition by ce.email) as maxdate
from dbo.ContactSentEmails as cse
join dbo.ContactEmails as ce on cse.ContactId = ce.ContactId
join dbo.Contacts as c on ce.ContactId = c.ContactId
left join dbo.Jobs as j on c.JobId = j.JobId
left join dbo.Companies as comp on c.CompanyId = comp.CompanyId
join dbo.StaffProjects as sp on cse.StaffProjectId = sp.StaffProjectId
join dbo.Staff as s on sp.StaffId = s.StaffId
join dbo.Projects as p on sp.ProjectId = p.ProjectId
where (@ContactSourceId = -1 or c.ContactSourceId = @ContactSourceId)
and (@FirstName = '' OR c.FirstName LIKE '%' + @FirstName + '%')
and (@LastName = '' OR c.LastName LIKE '%' + @LastName + '%')
and (@EmailAddress = '' OR ce.Email LIKE '%' + @EmailAddress + '%')
and (@StaffId = -1 or sp.StaffId = @StaffId)
and (@ProjectId = -1 or sp.ProjectId = @ProjectId)
and (@OfficeId = -1 or p.OfficeId = @OfficeId)
and cse.DateSent between CONVERT(datetime, @startDate) and CONVERT(datetime, @endDate)
group by c.ContactId, c.FirstName, c.LastName, Email,comp.CompanyName, j.JobName, c.MobileNumber, c.OfficeNumber, cse.DateSent
) as tbContacts
)
select ContactId, FirstName, LastName, Email, CompanyName, JobName, MobileNumber, OfficeNumber from CTE where cte.DateSent = CTE.maxdate order by CTE.Email
Many defects!
Defect 1: No plan can be chosen without looking at supplied values.
If @ContactSourceId is -1 you have one query execution plan. If ContactSourceId is not -1, you have another query execution plan. If you want the correct execution plan to be chosen, you’ll need to provide a query that knows what it is supposed to be filter without looking at the values of the variables.
Since you’ve used this criteria construct 7 times, there are 2^7 potential plans and your odds of getting the right one is 1/2^7 = 1/128 < 1%
You need to break this query text into 128 different queries – the query optimizer will only do this for you badly.
Defect 2: Failure to use SARGable search criteria
One of those 128 queries works like this: Suppose that @FirstName was supplied and the other variables aren’t. In this case, @FirstName is the primary criteria to access the Contacts table.
If you wrote a query with just this criteria on the Contacts table – there would be no index you can add to the Contacts table that would be used. You are doomed to table scan. Learn about SARGable search criteria.
Defect 3: Operations on each row, yields same result for each row.
Why are you converting a variable to DateTime on each row? Do those convert before running the query.
Defect 4: 90% of the time Distinct is a crutch
So many “give me just one” operators in a query means the query author was just trying stuff and seeing what sticks. Simplify to the actual intent. My guess is the distinct is not needed. If you add distinct when you don’t need it – you still pay for it!