Please bear with me — I know this is complex.
I have a table that contains apartments, and another that contains leases for those apartments. My task is to select the “most relevant” lease from the list. In general, that means the most recent lease, but there are a few quirks that make it more complex than just ordering by date.
That has led me to create this common table expression query inside a View, which I then JOIN with a number of others inside a Stored Procedure to get the results I need:
WITH TempTable AS (
SELECT l.BuildingID, l.ApartmentID, l.LeaseID, l.ApplicantID,
ROW_NUMBER() OVER (PARTITION BY l.ApartmentID ORDER BY s.Importance DESC, MovedOut, MovedIN DESC, LLSigned DESC, Approved DESC, Applied DESC) AS 'RowNumber'
FROM dbo.NPleaseapplicant AS l INNER JOIN
dbo.NPappstatus AS s ON l.BuildingID = s.BuildingID AND l.AppStatus = s.Code
)
SELECT BuildingID, ApartmentID, LeaseID, ApplicantID
FROM TempTable
WHERE RowNumber = 1
This works and returns the correct result. The challenge I’m facing is very slow performance.
As a test, I created a temp table inside the Stored Procedure instead of using View, and got much, much better performance:
CREATE TABLE #Relevant (
BuildingID int,
ApartmentID int,
LeaseID int,
ApplicantID int,
RowNumber int
)
INSERT INTO #Relevant (BuildingID, ApartmentID, LeaseID, ApplicantID, RowNumber)
SELECT l.BuildingID, l.ApartmentID, l.LeaseID, l.ApplicantID,
ROW_NUMBER() OVER (PARTITION BY l.ApartmentID ORDER BY s.Importance DESC, MovedOut, MovedIN DESC, LLSigned DESC, Approved DESC, Applied DESC) AS 'RowNumber'
FROM dbo.NPleaseapplicant AS l INNER JOIN
dbo.NPappstatus AS s ON l.BuildingID = s.BuildingID AND l.AppStatus = s.Code
WHERE (l.BuildingID = @BuildingID)
DROP TABLE #Relevant
At first glance this doesn’t add up to me. I’ve heard that temp tables are notoriously bad for performance. The wrinkle is that I’m able to better limit the query in the Temp Table with a WHERE clause that I can’t in the View. With over 10,000 leases across 16 buildings in the table, the ability to filter with the WHERE may drop the rows affected by 90% – 95%.
Bearing all that in mind, is there anything glaring that I’m missing here? Am I doing something wrong with the View that might cause the dreadful performance, or is it just a matter of a smaller result set in the Temp Table beating the unrestricted result set in the CTE?
EDIT: I should add that this business logic of selecting the “most relevant lease” is key to many reports in the system. That’s why it was placed inside a View to begin with. The View gives us “Write Once, Use Many” capabilities whereas a Temp Table in a Stored Procedure would need to be recreated for every other Stored Proc in the system. Ugly.
EDIT #2: Could I use a Table Based Function instead of a view? Would that allow me to limit the rows affected up front, and still use the resulting dataset in a JOIN with other tables? If it works — and has decent performance — this would allow me to keep the business logic in one place (the function) instead of duplicating it in dozens of Stored Procedures.
Just to put a bow on this one, here’s what I ended up doing:
Instead of using a View to join all possible rows from 2 or 3 tables, I created a Table Based Function that makes the same basic query. As one of the parameters I pass in the Building ID, and use that in a WHERE clause, like so:
The result is that it drastically reduces the number of joins required, and it speeds up the query immensely.
I then change all the stored procedures that rely on the View to use the Function instead, and bingo — huge performance gains.