Убийцей здесь является вычисление PaidForPast
(и производное PaidForPresent
), которое, кажется, пытается определить, полностью ли арендатор заплатил свой баланс (если я правильно понимаю - это нелегко). Это вычисление буквально должно выполняться для каждой отдельной платежной транзакции и зависит от другого агрегата, полученного из всей истории арендатора, который гарантирует операцию O (n ^ 2).
Это жестокая, жестокая вещь, которую нужно сделать с вашей базой данных, и, хотя я мог бы сделать ваш запрос более привлекательным, у вас нет возможности вырваться из проблемы с производительностью, если вы вынуждены создавать эту проблему. информация, основанная на конкретном наборе доступных данных, которые подразумевает этот запрос.
То, что у вас есть, это не проблема рефакторинга / оптимизации, а серьезная проблема дизайна. На самом деле у вас есть две проблемы. Меньшая проблема заключается в том, что вы кодируете бизнес-логику в свой уровень данных; большая проблема заключается в том, что система не была разработана для отслеживания всей информации, которая ей действительно необходима.
В основном вся информации, которую вы генерируете в этом запросе, должна храниться в некоторой таблице истории A / R, которая записывает эти агрегаты / статистику для каждой транзакции. Эта таблица может поддерживаться самим приложением или триггером в вашей таблице tblTransaction
.
Возможно, это не тот ответ, который вы ищете, но на самом деле я был на полпути к рефакторингу этого, когда понял, что удалить гнездо PaidForXYZ
невозможно.
На самом деле самый быстрый способ получения этих результатов, вероятно, будет с курсором, потому что тогда вы можете работать с частичными агрегатами и превратить его в операцию O (n). Но вы действительно хотите, чтобы курсор работал над всей таблицей транзакций без фильтра?
Обновление:
Если вы действительно не заботитесь о производительности и просто хотите очистить ее, чтобы ее было легче читать / поддерживать, вот лучшее, что я мог бы придумать, используя некоторые CTE:
;WITH Transactions_CTE AS
(
SELECT
TenantID,
TransactionCode,
Amount,
CASE
WHEN Amount > 0 AND TransactionDate BETWEEN @BeginDate AND @EndDate
THEN Amount
ELSE 0
END AS AmountPaid,
CASE
WHEN Amount < 0 AND TransactionDate BETWEEN @BeginDate AND @EndDate
THEN Amount
ELSE 0
END AS AmountOwed,
FROM tblTransaction
),
Summary_CTE AS
(
SELECT
t.PropertyID,
tr.TransactionCode,
SUM(tr.Amount) AS CumulativeBalance,
SUM(tr.AmountPaid) AS CurrentPaid,
SUM(tr.AmountOwed) AS CurrentOwed
FROM Transactions_CTE tr
INNER JOIN tblTenant t ON tr.TenantID = t.ID
GROUP BY t.PropertyID, tr.TransactionCode
),
Past_CTE AS
(
SELECT
PropertyID, TransactionCode,
CumulativeBalance, CurrentPaid, CurrentOwed,
CASE
WHEN CurrentPaid <
ABS(CumulativeBalance - CurrentPaid) + CurrentOwed
THEN CurrentPaid
ELSE ABS(CumulativeBalance - CurrentPaid) + CurrentOwed
END AS PaidForPast
FROM Summary_CTE
),
Present_CTE AS
(
SELECT
PropertyID, TransactionCode,
CumulativeBalance, CurrentPaid, CurrentOwed,
PaidForPast,
CASE
WHEN (CurrentPaid - PaidForPast) < ABS(CurrentOwed)
THEN CurrentPaid - PaidForPast
ELSE ABS(CurrentOwed)
END AS PaidForPresent
FROM Past_CTE
)
SELECT
ISNULL(p.OwnerName, 'UNKNOWN') AS OwnerName,
c.[Description],
CumulativeBalance, CurrentPaid, CurrentOwed,
CumulativeBalance - CurrentPaid AS CumulativeOwed,
PaidForPast, PaidForPresent,
CurrentPaid - PaidForPast - PaidForPresent AS PaidForFuture,
[Description]
FROM Present_CTE s
LEFT JOIN tblProperty p ON p.ID = s.PropertyID
LEFT JOIN tblTenantTransCode c ON c.ID = s.TransactionCode
Вы можете исключить два последних CTE, полностью выписав PaidForXYZ
выражений, в отличие от создания одного поверх другого, но IMO это в итоге сделает его менее читабельным, и я вполне уверен, что оптимизатор выяснит это и отобразит все это в выражения вместо подзапросов.
Кроме того, я должен сказать, что все эти ABS
блоки вызывают у меня подозрения. Я не знаю подробностей того, что здесь происходит, но такое ощущение, что они используются вместо оператора отрицания, возможно, такого, который следовало бы использовать в последующем (т. Е. Для преобразования дебетов или кредитов в отрицательные суммы) и это может привести к небольшим ошибкам в будущем.