Oracle(10)SQL:根据两个字段计算最终总和

时间:2010-04-09 13:30:15

标签: sql oracle sum running-total

首先是免责声明:我从未在学校学过任何编程,只需处理各种SQL问题(也是)。

所以现在我有两个表,TABLE1:

ACCNO BAL1 BAL2
11111   20   10

和TABLE2(当然有ACCNO键)相关的行为'11111':

DATENUM AMT
1       -5
2       -10
3       8
4       -23
5       100
6       -120
7       140

现在我必须使用以下规则找到新的BAL1和BAL2:

  1. BAL1 AMT必须从BAL1减去或添加到BAL1,直到BAL1 == 0(和BAL2> 0)
  2. 如果BAL1达到0,那么BAL1的(如果有的话)剩余部分必须从BAL2减去
  3. 如果BAL2也达到0,则从那时起只应修改BAL1。
  4. 所以使用以上数据:

    DATENUM AMT   BAL1 BAL2
    0       0     20   10   /*starting record*/
    1       -5    15   10   
    2       -10   5    10
    3       8     13   10
    4       -23   0    0
    5       100   100  0
    6       -120  -20  0
    7       140   120   0
    

    我需要最后两个BAL1和BAL2。

    如何使用(Oracle 10)SQL计算它们?

3 个答案:

答案 0 :(得分:2)

我想我会用PL / SQL做到这一点:

DECLARE
  v_bal1  table1.bal1%TYPE;
  v_bal2  table1.bal2%TYPE;
  v_accno table1.accno%TYPE;
BEGIN
  v_accno := 11111;
  SELECT bal1, bal2
  INTO v_bal1, v_bal2  
  FROM table1
  WHERE accno = v_accno;

  FOR c IN ( SELECT amt
             FROM table2
             WHERE accno = v_accno
             ORDER BY datenum )
  LOOP
    v_bal1 := v_bal1 + c.amt;
    IF( v_bal1 < 0 AND v_bal2 > 0 ) THEN
      v_bal2 := v_bal2 + v_bal1;  --# v_bal1 < 0, so "add" to v_bal2
      IF( v_bal2 < 0 ) THEN
        v_bal1 := v_bal1 + v_bal2; --# "remove" remainder
        v_bal2 := 0;
      ELSE
        v_bal1 := 0;
      END IF;
    END IF;
  END LOOP;
  dbms_output.put_line( v_bal1 || ', ' || v_bal2 );
END;

此输出

  

120,0

看起来您的上一行有误,添加了40而不是140

答案 1 :(得分:1)

如果你有一个BALANCE列,这在SQL中很容易做到。我们可以使用分析SUM()来生成AMT的滚动总数,并将其应用于每行中的BAL1 ......

SQL> select accno
  2         , bal1
  3         , datenum
  4         , amt
  5         , rolling_amt
  6         , bal1 + rolling_amt as rolling_bal1
  7  from (
  8      select t1.accno
  9               , t2.datenum
 10               , t2.amt
 11               , t1.bal1
 12               , sum ( t2.amt) over
 13                         ( partition by t2.accno
 14                           order by t2.datenum rows unbounded preceding )
 15                                           as rolling_amt
 16      from t1 join t2 on (t2.accno = t1.accno)
 17      where t1.accno = 11111
 18      order by t2.datenum
 19  )
 20  /

     ACCNO       BAL1    DATENUM        AMT ROLLING_AMT ROLLING_BAL1
---------- ---------- ---------- ---------- ----------- ------------
     11111         20          1         -5          -5           15
     11111         20          2        -10         -15            5
     11111         20          3          8          -7           13
     11111         20          4        -23         -30          -10
     11111         20          5        100          70           90
     11111         20          6       -120         -50          -30
     11111         20          7        140          90          110

7 rows selected.

SQL>

但是你的要求是两列并且在行之间传递一些算法,这要复杂得多。有可能用MODEL()条款来做,但考虑到这一点总是让我的前额流血。

答案 2 :(得分:1)

除了带游标的简单(无聊)解决方案之外,您可以通过创建聚合函数(或者更确切地说,2个聚合函数,一个用于计算余额1和一个用于计算余额2)来实现此目的。问题是你只能将一个参数用于聚合函数,因此该参数必须是复合类型。在伪代码中(我没有使用Oracle这么久):

CREATE TYPE tuple_type(amt number, bal1 number, bal2 number);

CREATE FUNCTION calc_bal1(arg IN tuple_type) RETURN number AGGREGATE USING some_implementing_type;
CREATE FUNCTION calc_bal2(arg IN tuple_type) RETURN number AGGREGATE USING some_implementing_type;

然后您可以使用分析函数查询它们。如果您只对每个帐户的最终价值感兴趣,可以执行以下操作:

SELECT t1.acct_no,
       calc_bal1(tuple_type(t2.amt, t1.bal1, t1.bal2)) OVER (PARTITION BY t1.acct_no ORDER BY t2.datenum),
       calc_bal2(tuple_type(t2.amt, t1.bal1, t1.bal2)) OVER (PARTITION BY t1.acct_no ORDER BY t2.datenum)
  FROM table1 t1
  JOIN (SELECT acct_no, datenum, amt FROM table2
        UNION ALL
        SELECT acct_no, 0, 0) t2
    ON t1.acct_no = t2.acct_no;
 WHERE t1.datenum = 0;

如果你想要每次转换,请执行:

SELECT t1.acct_no,
       calc_bal1(tuple_type(t2.amt, t1.bal1, t1.bal2))
                 OVER (PARTITION BY t1.acct_no
                 ORDER BY t2.datenum
                 ROWS BETWEEN UNBOUNDED PRECEEDING AND CURRENT ROW),
       calc_bal1(tuple_type(t2.amt, t1.bal1, t1.bal2))
                 OVER (PARTITION BY t1.acct_no
                 ORDER BY t2.datenum
                 ROWS BETWEEN UNBOUNDED PRECEEDING AND CURRENT ROW)
  FROM table1 t1
  JOIN (SELECT acct_no, datenum, amt FROM table2
        UNION ALL
        SELECT acct_no, 0, 0) t2
    ON t1.acct_no = t2.acct_no;

你也可以使用游标而不是聚合(这很可能具有可怕的性能):

CREATE FUNCTION calc_bal1(c IN sys.ref_cursor, bal1 IN number, bal2 IN number) RETURN number AS ...;
CREATE FUNCTION calc_bal2(c IN sys.ref_cursor, bal1 IN number, bal2 IN number) RETURN number AS ...;

如果您想要所有行:

SELECT t1.acct_no,
       calc_bal1(CURSOR(SELECT amt FROM table2 x WHERE x.acct_no = t1.acct_no AND x.datenum <= t2.datenum ORDER BY x.datenum), t1.bal1, t1.bal2),
       calc_bal2(CURSOR(SELECT amt FROM table2 x WHERE t2.acct_no = t1.acct_no AND x.datenum <= t2.datenum ORDER BY t2.datenum), t1.bal1, t1.bal2)
  FROM table1 t1
  JOIN (SELECT acct_no, datenum, amt FROM table2
        UNION ALL
        SELECT acct_no, 0, 0) t2
    ON t1.acct_no = t2.acct_no;

如果您只想要最终值:

SELECT t1.acct_no,
       calc_bal1(CURSOR(SELECT amt FROM table2 t2 WHERE t2.acct_no = t1.acct_no ORDER BY t2.datenum), t1.bal1, t1.bal2),
       calc_bal2(CURSOR(SELECT amt FROM table2 t2 WHERE t2.acct_no = t1.acct_no ORDER BY t2.datenum), t1.bal1, t1.bal2)
  FROM table1 t1;