I’m making some modifications to improve the performance of an old report query written 1000 years ago. In the process of modify it to do a UNION ALL, I’ve replaced one of the column selection values with NULL. As soon as I do that the query changes from taking 1-2 seconds to execute to 30 second to execute. I’ve looked at the actual execution plans for the two versions and they look identical. It makes no sense to me that selecting literal NULL could be slower than reading a row value. I’ve also tried explicitly casting NULL to the former type (nvarchar) as well as selecting ” instead of NULL with no difference.
The query and schema is very complex so this will probably take some Q&A troubleshooting. The column slowing it down when selecting NULL is “OtherComments” near the bottom. You can see the original version, that runs fast, commented above it. Just to keep us on track, I’m only looking to understand why changing that column makes it run slowly, not other ways to improve the query (I know there are lots). Here’s an abbreviated version:
SELECT @Date Parameter,
fml.FirstName + ' ' + fml.LastName ParentName,
(
SELECT TOP 1 p.PhoneNum
FROM tbl_Phone p, tbl_PhoneTypes pt, tbl_FamilyPhone fp
WHERE p.fk_PhoneTypeID = pt.pk_PhoneTypeID
AND p.pk_PhoneID = fp.fk_PhoneID
AND fp.fk_FamilyID = fml.pk_familyID
AND pt.Type = 'home'
AND fp.IsDeleted = 0
ORDER BY fp.CreatedDate DESC
) PhoneNo,
fml.Comments FamilyComments,
std.FirstName + ' ' + std.LastName StudentName,
(
SELECT
...
) ClassDescription,
(
SELECT
...
) TestClassDescription,
CASE
WHEN (sce.pk_StudentEnrollmentID IS NOT NULL) THEN
(
SELECT emp.FirstName + ' ' + emp.LastName
...
)
WHEN (st.pk_StudentTestID IS NOT NULL) THEN
(
SELECT emp.FirstName + ' ' + emp.LastName
...
)
ELSE
NULL
END InstructorName,
CASE
WHEN (st.pk_StudentTestID IS NOT NULL) THEN
(
SELECT emp.FirstName + ' ' + emp.LastName
...
)
ELSE
NULL
END TestInstructorName,
st.TestDate TestDate,
tr.Description TestResult,
CASE
WHEN (
SELECT COUNT(ClassDate) --Select absent attendances from yesterday
...
) >= 1
THEN
CAST(1 AS BIT)
ELSE
CAST(0 AS BIT)
END
MissedYesterdaysClass,
CASE
WHEN (datediff(day, CONVERT(varchar(11),fml.InquiryDate,102), @Date) =3 and
(fml.CurrentMembershipDate IS NULL) AND ((fml.WebCreated = 0) OR (fml.WebCreated = NULL)) AND
((
SELECT count(*)
...
) <= 0) AND
((
SELECT count(*)
...
) <= 0) AND
((
SELECT count(*)
...
) <= 0) AND
((
SELECT count(*)
...
) <= 0) AND
((
SELECT count(*)
...
) <= 0)) THEN
CAST(1 AS BIT)
WHEN (((fml.InquiryDate + 3) = @Date) AND (std.pk_StudentID IS NULL)) THEN
CAST(1 AS BIT)
ELSE
CAST(0 AS BIT)
END InquiredButDidnotSchedule,
CASE
WHEN --(((st.TestDate + 2) = @Date) AND
(datediff(day, CONVERT(varchar(11),st.testdate,102), @Date) =2 and
(tr.Description = 'Not Enrolled') AND
(st.IsDeleted = 0) AND
(st.IsCancelled = 0)) THEN
CAST(1 AS BIT)
ELSE
CAST(0 AS BIT)
END AttendedButHavenotEnrolled,
CASE
WHEN --(((st.TestDate + 1) = @Date) AND
(datediff(day, CONVERT(varchar(11),st.testdate,102), @Date) =1 and
(tr.Description = 'No Show') AND
(st.IsDeleted = 0) AND
(st.IsCancelled = 0)) THEN
CAST(1 AS BIT)
ELSE
CAST(0 AS BIT)
END PeopleNoShowed,
CASE
WHEN ---(((st.TestDate - 1) = @Date) AND
(datediff(day, CONVERT(varchar(11),st.testdate,102), @Date) =-1 and
(tr.Description = 'Scheduled') AND
(st.IsDeleted = 0) AND
(st.IsCancelled = 0)) THEN
CAST(1 AS BIT)
ELSE
CAST(0 AS BIT)
END PeopleHaveTest,
CAST(0 AS BIT) ShowOtherComments,
CASE
WHEN
((
SELECT count(*)
...
) > 0) AND
((
SELECT EventDate
...
)
BETWEEN
(
DateAdd(day, DateDiff(day, 0, @Date), 0)
)
AND
(
(DateAdd(day, DateDiff(day, 0, @Date), 0) + 6)
)
)
AND
datename(weekday, @Date) = 'Wednesday'
AND
bb.IsDeleted = 0
AND
bb.IsCancelled = 0 THEN
CAST(1 AS BIT)
ELSE
CAST(0 AS BIT)
END ShowUpcomingBookingss,
(
SELECT EventDate
...
) BookingDate,
bb.ChildTurningAge Age,
std.pk_StudentID StudentID,
fml.pk_familyID FamilyID,
CASE
WHEN (datediff(day, CONVERT(varchar(11),fml.InquiryDate,102), @Date) =1 and
(fml.MembershipDate IS NULL) AND (fml.WebCreated = 1) AND
((
SELECT count(*)
...
) <= 0) AND
((
SELECT count(*)
...
) <= 0) AND
((
SELECT count(*)
...
) <= 0) AND
((
SELECT count(*)
...
) <= 0) AND
((
SELECT count(*)
...
) <= 0)) THEN
CAST(1 AS BIT)
WHEN (((fml.InquiryDate + 1) = @Date) AND (std.pk_StudentID IS NULL)) THEN
CAST(1 AS BIT)
ELSE
CAST(0 AS BIT)
END InquiredButDidnotScheduleOnline,
-- Commenting this out and replacing with NULL slows it down from 2 sec to 30 sec
-- fml.OtherComment OtherComments,
NULL OtherComments,
FROM tbl_Family fml
LEFT OUTER JOIN tbl_Student std on fml.pk_FamilyID = std.fk_FamilyID
LEFT OUTER JOIN tbl_StudentEnrollment sce on std.pk_StudentID = sce.fk_StudentID
LEFT OUTER JOIN tbl_StudentTest st on std.pk_StudentID = st.fk_StudentID
LEFT OUTER JOIN tbl_Booking bb on std.pk_StudentID = bb.fk_StudentID
LEFT JOIN tbl_TestResult tr on st.fk_TestResultID = tr.pk_TestResultID
WHERE fml.fk_FacilityID = @FacilityID
AND (fml.IsDeleted = 0 OR fml.IsDeleted IS NULL)
AND (std.IsDeleted = 0 OR std.IsDeleted IS NULL)
I compared the actual XML of the query plans and noticed the memory grant was higher for the fast sproc than the slow sproc. This was explained to me in the MS forums and probably being because when fml.OtherComment is in the query, it is expecting a larger row size. I don’t know a solution if you were run into this with a well written query, but in my case, this spoc was so inefficient (selecting the final records in a round about way) that I rewrote it from the ground up and it’s running fast now.