Oracle (10) SQL: вычисление итоговой суммы в зависимости от двух полей - PullRequest
2 голосов
/ 09 апреля 2010

Во-первых, отказ от ответственности: я никогда не учился программированию в школе, и мне просто приходится иметь дело с различными проблемами 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 == 0 (и BAL2> 0)
  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

И мне нужны последние два BAL1 и BAL2.

Как их вычислить, используя (Oracle 10) SQL?

Ответы [ 3 ]

2 голосов
/ 09 апреля 2010

Я думаю, я бы сделал это с 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

Похоже, ваша последняя строка неверна, вместо 140 добавлено 40.

1 голос
/ 09 апреля 2010

В дополнение к простому (= скучному) решению с курсором, вы, вероятно, можете сделать это, создав агрегатную функцию (точнее, 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;
1 голос
/ 09 апреля 2010

Если бы у вас был один столбец 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 (), но размышления об этом всегда вызывают у меня кровь на лбу.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...