Найти баланс транзакции каждого клиента, начиная с последнего нулевого баланса до последнего баланса - PullRequest
0 голосов
/ 22 марта 2012

Я использую SQL Server 2005. У меня есть две таблицы -

1. tbl_Customer  
  - CustId INT,  
  - Name VARCHAR(40)  
2. tbl_Transaction  
  - TransId INT,  
  - CustId INT,  
  - TranDate DATETIME  
  - DebitAmount NUMERIC(8,2),  
  - CreditAmount NUMERIC(8,2)  

Данные tbl_Customer

CustId    Name

1        ST  
2        JS  
3        MA

Данные tbl_Transaction

TransId CustId  Tdate       DtAmt   CrAmt
101     1       1/1/2012    250     100
102     1       1/2/2012    0       100
103     1       1/2/2012    0       50
104     2       1/2/2012    400     200
105     2       1/3/2012    0       150
106     2       1/3/2012    0       40
107     2       1/4/2012    0       10
108     1       1/1/2012    350     50
109     1       1/2/2012    0       200
110     1       1/2/2012    0       100
111     2       1/10/2012   500     300
112     2       1/10/2012   0       120

Я хочу найти баланс каждого клиента, начиная с последнего нулевого баланса.Баланс (DebitAmount - CreditAmount) для первой строки, а затем (Balance - CreditAmount) для следующих строк.Таким образом, после запроса результат должен выглядеть следующим образом -

TransId CustId  Transdate   DtAmt   CrAmt      Balance
108     1       1/1/2012    350     50      300
109     1       1/2/2012    0       200     100
110     1       1/2/2012    0       100     0
111     2       1/10/2012   500     300     200
112     2       1/11/2012   0       120     80

Теперь главное, я хочу искать клиентов и отображать последний баланс каждого клиента в результате.Если баланс отсутствует (транзакция недоступна в tbl_transaction), баланс должен быть нулевым.Таким образом, конечный результат должен выглядеть следующим образом.

CustId  Name    Balance
1       ST      0
2       JS      80
3       MA      NULL

Надеюсь, я достаточно четко сформулировал, чего хочу достичь.Пожалуйста, дайте мне указания о том, как этого добиться.Я новичок в SQL и изучения битов и байтов RDBMS.Заранее спасибо.

Ритеш

Ответы [ 2 ]

2 голосов
/ 22 марта 2012

Следующее должно работать, я не на 100% в среднем запросе, чтобы получить каждое промежуточное значение, но я считаю, что это должно работать.

Настройка набора результатов:

SELECT *,
    ROW_NUMBER() OVER (PARTITION BY CustId ORDER BY TransDate) AS TransactionOrderNum
INTO #TransactionOrder
FROM tbl_Transaction

Я полагаю, что это перекрестное применение должно работать, но я признаю, что я не на 100%:

SELECT #TransactionOrder.*, CustomerSum AS Balance
FROM #TransactionOrder
    CROSS APPLY
    (
        SELECT SUM(
                CASE WHEN TransactionOrderNum = 1 THEN DtAmt - CrAmt
                ELSE (CrAmt * -1) END
            ) AS CustomerSum 
        FROM #TransactionOrder AS Summation
        WHERE Summation.TransactionOrderNum<= #TransactionOrder.TransactionOrderNum
            AND Summation.CustId = #TransactionOrder.CustId
        GROUP BY CustId
    ) AS SumValues

Это определенно должно помочь вам получить окончательную сумму:

SELECT tbl_Customer.CustId, tbl_Customer.Name, SUM
(
    CASE WHEN TransactionOrderNum = 1 THEN DtAmt - CrAmt
    ELSE (CrAmt * -1) END
) AS CustomerSum 
FROM tbl_Customer
    LEFT JOIN #TransactionOrder
        ON tbl_Customer.CustId = #TransactionOrder.CustId
GROUP BY tbl_Customer.CustId, tbl_Customer.Name

Вы можете использовать CTE , а также:

WITH TransactionOrder AS
(
    SELECT *,
        ROW_NUMBER() OVER (PARTITION BY CustId ORDER BY TransDate) 
            AS TransactionOrderNum
    FROM tbl_Transaction
)
SELECT TransactionOrder.*, CustomerSum AS Balance
FROM TransactionOrder
    CROSS APPLY
    (
        SELECT SUM(
                CASE WHEN TransactionOrderNum = 1 THEN DtAmt - CrAmt
                ELSE (CrAmt * -1) END
            ) AS CustomerSum 
        FROM TransactionOrder AS Summation
        WHERE Summation.TransactionOrderNum<= TransactionOrder.TransactionOrderNum
            AND Summation.CustId = TransactionOrder.CustId
        GROUP BY CustId
    ) AS SumValues

Теперь, если это один поток данных, вы можете просто сохранить промежуточный выбор во временной таблице и выбрать строку max (TransactionOrderNum) для каждого из

custid

Добавьте это значение между select и from в запросе баланса на ходу

INTO #BalanceAsYouGo

Тогда окончательный запрос будет выглядеть примерно так:

SELECT tbl_Customer.CustId, tbl_Customer.Name, #BalanceAsYouGo.Balance
FROM tbl_Customer
    LEFT JOIN #BalanceAsYouGo
        ON tbl_Customer.CustId = #BalanceAsYouGo.CustId
    LEFT JOIN 
    (
         SELECT CustId, MAX(TransactionOrderNum) AS MaxTransNum
         FROM #BalanceAsYouGo
         GROUP BY CustId
    ) AS FinalBalance
        ON FinalBalance.CustId = #BalanceAsYouGo.CustId
            FinalBalance.MaxTransNum = #BalanceAsYouGo.TransactionOrderNum
GROUP BY tbl_Customer.CustId, tbl_Customer.Name
1 голос
/ 22 марта 2012

Я выполнил это с помощью рекурсивного CTE для расчета текущих сальдо, а затем с помощью внешнего применения для получения окончательного результата.

Из вашего примера вывода я понимаю, что в тот момент, когда баланс пользователя достигает 0, вы начинаете пересчитывать его баланс с помощью дебет-кредит, а не продолжаете формулу предыдущий баланс-кредит.Это то, что выполняет этот CTE.

WITH rk AS (SELECT ROW_NUMBER() OVER (PARTITION BY custid ORDER BY transid) rn, transid, custid, tdate, dtamt, cramt
            FROM   tbl_transaction)
    ,bl AS (SELECT rk.rn, rk.transid, rk.custid, rk.tdate, rk.dtamt, rk.cramt, rk.dtamt - rk.cramt balance
            FROM   rk
            WHERE  rk.rn = 1
            UNION ALL
            SELECT rk2.rn, rk2.transid, rk2.custid, rk2.tdate, rk2.dtamt, rk2.cramt
                  ,CASE WHEN bl.balance = 0 THEN rk2.dtamt - rk2.cramt ELSE bl.balance - rk2.cramt END balance
            FROM   bl, rk rk2
            WHERE  bl.rn = rk2.rn - 1 and bl.custid = rk2.custid)
-- SELECT * FROM bl ORDER BY custid, transid
SELECT cc.custid, cc.NAME, xx.Balance
FROM   tbl_Customer cc
       OUTER APPLY (SELECT TOP 1 bl.Balance
                    FROM   bl
                    WHERE  bl.custid = cc.custid
                    ORDER  BY bl.rn DESC) xx

Некомментированный выбор из CTE возвращает вывод, как определено.Вы можете переключить комментирование после CTE, чтобы увидеть, что этот CTE возвращает промежуточную таблицу точно так, как вы определили.(по состоянию на 2: 35P)

Конечно, сложение и вычитание являются коммутативными.Действительно, на окончательный баланс можно ответить так:

SELECT cc.custid, cc.NAME, xx.Balance
FROM   tbl_Customer cc
       OUTER APPLY (SELECT SUM(tt.dtamt) - SUM(tt.cramt) Balance
                    FROM   tbl_Transaction tt
                    WHERE  tt.custid = cc.custid) xx

Этот тип запроса обязательно будет долгим.Вы можете попытаться создать индексированное представление для агрегата в tbl_Transaction:

CREATE VIEW dbo.dv_TransactionTotals 
WITH SCHEMABINDING
AS
SELECT tt.custid, SUM(tt.dtamt) - SUM(tt.cramt) Balance, COUNT_BIG(*) Transactions
FROM   dbo.tbl_Transaction tt -- assuming schema here
GROUP  BY tt.custid

CREATE CLUSTERED INDEX ixc_TransactionTotals ON dbo.dv_TransactionTotals (custid)

Ожидайте, что этот индекс займет некоторое время для построения, и осознайте, что его существование окажет некоторое влияние на ваши вставки.См. http://technet.microsoft.com/en-us/library/cc917715.aspx для получения дополнительной информации.(Обратите внимание, что поле COUNT_BIG является обязательным для индекса - я всегда забываю об этом, пока не попытаюсь запустить индекс. Также обратите внимание, что таблицы должны быть правильно определены схемой; я предположил, что dbo не предоставлен, а другие).

Благодаря агрегации в SQL 2005 он может автоматически использовать представление, но я никогда не верю в это.Вы также можете превратить запрос в объединение тех, у кого есть транзакции, и тех, у кого нет:

SELECT cc.custid, cc.NAME, tt.Balance
FROM   tbl_Customer cc
       INNER JOIN dv_TransactionTotals tt ON tt.custid = cc.custid
UNION  ALL
SELECT cc.custid, cc.NAME, NULL Balance
FROM   tbl_Customer cc
WHERE  cc.custid NOT IN (SELECT custid FROM dv_TransactionTotals tt WHERE tt.custid = cc.custid)

Я надеюсь, что вы не будете запускать этого монстра на производстве, так как это явно проблема с отчетностью.Действительно, если бы я столкнулся с этой проблемой, я бы создал (или подключился) хранилище данных, настроил репликацию на необходимые таблицы и создал процесс, который бы периодически добавлял эти данные в таблицы фактов.

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