帮我重构这个查询的怪物

Mal*_*ist 1 t-sql sql-server

这是一个巨大的怪物,它进入SP所以变量是可用的:

SELECT OwnerName, SUM(AmountPaid) AS Paid, SUM(AmountOwedComplete) AS Owed, SUM(AmountOwedThisMonth) AS OwedMonth,
    SUM(PaidForPast) AS PaidPast, SUM(PaidForPresent) AS PaidPresent, SUM((AmountPaid - PaidForPast - PaidForPresent)) AS PaidFuture, [Description] FROM (
    SELECT OwnerName, AmountPaid, AmountOwedComplete, AmountOwedThisMonth, PaidForPast, [Description],
        (SELECT CASE WHEN (AmountPaid - PaidForPast) < ABS(AmountOwedThisMonth) THEN AmountPaid - PaidForPast
            ELSE ABS(AmountOwedThisMonth) END) AS PaidForPresent
    FROM (
        SELECT OwnerName, AmountPaid, AmountOwedTotal - AmountPaid AS AmountOwedComplete,
            AmountOwedThisMonth, 
            (SELECT CASE WHEN (AmountPaid < ABS((AmountOwedTotal - AmountPaid)) + AmountOwedThisMonth)
                THEN AmountPaid ELSE ABS((AmountOwedTotal - AmountPaid)) + AmountOwedThisMonth END) AS PaidForPast, 
            Description, TransactionDate
         FROM (
            SELECT DISTINCT t.TenantName, p.PropertyName, ISNULL(p.OwnerName, 'Uknown') AS OwnerName, (
                SELECT SUM(Amount) FROM tblTransaction WHERE 
                    Amount > 0 AND TransactionDate >= @StartDate AND TransactionDate <= @EndDate
                    AND TenantID = t.ID AND TransactionCode = trans.TransactionCode
            ) AS AmountPaid, (
                SELECT SUM(Amount) FROM tblTransaction WHERE 
                    tblTransaction.TransactionCode = trans.TransactionCode AND tblTransaction.TenantID = t.ID
            )  AS AmountOwedTotal, (
                SELECT SUM(Amount) FROM tblTransaction WHERE  tblTransaction.TransactionCode = trans.TransactionCode AND tblTransaction.TenantID = t.ID
                    AND Amount < 0 AND TransactionDate >= @StartDate AND TransactionDate <= @EndDate
            ) AS AmountOwedThisMonth, code.Description, trans.TransactionDate FROM tblTransaction trans 
            LEFT JOIN tblTenantTransCode code ON code.ID = trans.TransactionCode
            LEFT JOIN tblTenant t ON t.ID = trans.TenantID
            LEFT JOIN tblProperty p ON t.PropertyID  = p.ID
            WHERE trans.TransactionDate >= @StartDate AND trans.TransactionDate <= @EndDate AND trans.Amount > 0
        ) q
    ) q2
)q3
GROUP BY OwnerName, Description
Run Code Online (Sandbox Code Playgroud)

这就是它的作用.它会访问所有租户并获得他们本月支付的费用,以及他们所欠的一切.然后计算以前的费用,本月和未来费用的支付额.然后根据收费说明和业主名称对它们进行汇总.

它看起来很可怕,我想知道是否有一些我可以使用的捷径.

Aar*_*ght 7

这里的杀手是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
Run Code Online (Sandbox Code Playgroud)

您可以通过完整地写出整个PaidForXYZ表达式来消除最后两个CTE,而不是在另一个上面构建一个,但是IMO这最终会使它不那么可读,而且我很确定优化器会解决它并将其全部映射到表达式而不是子查询.

此外,我不得不说所有这些ABS块都让我怀疑.我不知道这里发生了什么的细节,但感觉就像那些被用来代替否定运算符,可能是一个应该在上游进一步使用(即将借方或贷方转换为负数)和这可能会导致一些微妙的错误.