Помогите мне рефакторинг этого монстра запроса - PullRequest
1 голос
/ 22 декабря 2009

Это один огромный монстр, он входит в 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

Это то, что он делает. Он посещает всех Арендаторов и получает то, что они заплатили за этот месяц, и все, что они должны. Затем он рассчитывает, что выплачивается за предыдущие платежи, в этом месяце и будущие платежи. Затем он суммирует их на основе описания платы и имени владельца недвижимости.

Это выглядит ужасно, и я хочу знать, есть ли какие-то ярлыки, которые мне не хватает, которые можно использовать.

Ответы [ 3 ]

7 голосов
/ 22 декабря 2009

Убийцей здесь является вычисление 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 блоки вызывают у меня подозрения. Я не знаю подробностей того, что здесь происходит, но такое ощущение, что они используются вместо оператора отрицания, возможно, такого, который следовало бы использовать в последующем (т. Е. Для преобразования дебетов или кредитов в отрицательные суммы) и это может привести к небольшим ошибкам в будущем.

1 голос
/ 22 декабря 2009

Сначала научитесь форматировать SQL, чтобы вы (и я) могли его прочитать:

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;

Во-вторых, убедитесь, что у вас есть правильные индексы - для этого вам нужно будет прочитать SQL.

0 голосов
/ 22 декабря 2009

Есть ли у вас мнение в схеме? В настоящее время похоже, что вы пытаетесь создать отчет из оперативного хранилища данных, которое носит транзакционный характер. Во многих сценариях большого объема нередко создается менее нормализованная схема, известная как база данных поддержки принятия решений, в которую ваши транзакционные данные копируются / суммируются в определенный интервал. Затем вы можете написать довольно упрощенные запросы к DSS, в то время как ваши сильно нормализованные ODS продолжают пыхтеть.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...