查找从最后一个零余额到最新余额后开始的每个客户的交易余额

时间:2012-03-21 20:48:27

标签: sql sql-server sql-server-2005

我正在使用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的位和字节。提前谢谢。

仅限Ritesh

2 个答案:

答案 0 :(得分:2)

以下应该可行,我不是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

现在,如果这是一个数据流,那么您可以将临时选择存储到临时表中,并为每个custid

选择max(TransactionOrderNum)行。

在余额随需查询中的selectfrom之间添加此内容

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 :(得分:1)

我使用递归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)

我希望你不会在生产中运行这个怪物,因为这显然是一个报道问题。实际上,如果我遇到这个问题,我会创建(或利用)数据仓库,在必要的表上设置复制,并创建一个定期将这些数据添加到事实表的过程。