2

最初の免責事項: 私は学校でプログラミングを学んだことがなく、さまざまな SQL の問題に対処する必要があります (これも)。

これで、TABLE1 という 2 つのテーブルができました。

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 == 0 (および BAL2 > 0) になるまで、BAL1 から減算または追加する必要があります。
  2. BAL1 が 0 に達した場合、BAL1 の残り (ある場合) を BAL2 から差し引く必要があります。
  3. BAL2 も 0 に達した場合は、BAL1 のみを変更する必要があります。

上記のデータを使用すると、次のようになります。

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

そして、最後の 2 つの BAL1 と BAL2 が必要です。

(Oracle 10) SQL を使用してそれらを計算するにはどうすればよいですか?

4

3 に答える 3

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

于 2010-04-09T13:54:11.393 に答える
1

BALANCE 列が 1 つしかない場合、これは 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>

ただし、要件は2つの列をジャグリングし、行間でいくつかの算術演算を渡しますが、これははるかに複雑です。MODEL() 句でそれを行うことは可能かもしれませんが、それについて考えるといつも額が出血します。

于 2010-04-09T16:32:50.670 に答える
1

カーソルを使用した単純な (=退屈な) ソリューションに加えて、集計関数 (または、残高 1 を計算するための 1 つと残高 2 を計算するための 2 つの集計関数) を作成することで、おそらくこれを行うことができます。問題は、集約関数に対して 1 つの引数しか使用できないため、その引数は複合型でなければならないことです。擬似コード (私は 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;
于 2010-04-09T16:55:57.260 に答える