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