【问题标题】:Find the balance of transaction of each customer starting after last zero balance to latest balance查找从最后一个零余额到最新余额的每个客户的交易余额
【发布时间】:2012-03-21 20:48:27
【问题描述】:

我使用的是 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

【问题讨论】:

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


    【解决方案1】:

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

    在 balance-as-you-go 查询中将其添加到 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
    

    【讨论】:

    • 我需要创建临时表#TransactionOrder吗?对性能有好处吗?目前 tbl_Customer 有大约 1000 条记录,而 tbl_Transaction 有超过 2000000 条记录。对我来说,最后一个结果集现在最重要。谢谢您的帮助。我会试试你的代码并告诉你。
    • 您可以使用 CTE,但我不知道它会在性能上获得多少。我将添加如何完成
    • 这已更新为使用 CTE,并指出如果您在同一 SQL 流中运行这一切,则无需再次重新计算 SUM。这应该会有所帮助
    【解决方案2】:

    我通过递归 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:35)

    当然,加法和减法是可交换的。真的,最后的平衡可以这样回答:

    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)
    

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

    【讨论】:

    • 我尝试了查询,但它执行的时间很长,大约一分钟后,它会导致“Exhaustion”错误。我在 Transactions 表中有超过 200 万条记录。有没有什么优化的方法>
    • 谢谢你们救了我的脖子。 :)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-11-09
    • 1970-01-01
    • 2019-03-28
    • 1970-01-01
    • 1970-01-01
    • 2022-01-11
    相关资源
    最近更新 更多