Forum Stats

  • 3,815,621 Users
  • 2,259,062 Discussions
  • 7,893,186 Comments

Discussions

Query challenge

2»

Comments

  • Rob van Wijk
    Rob van Wijk Member Posts: 5,856 Bronze Trophy
    Hi Jens,

    This is also an amazing solution, although as mentioned a bit cheated.

    I started this thread thinking: "I know a bit of SQL and since I cannot come up with a non model clause solution I truly wonder if it is possible at all". And here I have two other solutions! You've got to love this forum ...

    Thanks!

    Regards,
    Rob.
  • MichaelS
    MichaelS Member Posts: 8,424 Bronze Crown
    You've got to love this forum ...
    I feel a XML solution was still missing ;)
    Let's keep this forum rock:
    SQL> SELECT VALUE(t).EXTRACT('ROW/CUSTOMER/text()').getnumberval() customer,
      2         VALUE(t).EXTRACT('ROW/AMOUNT/text()').getnumberval() amount,
      3         TO_DATE(VALUE(t).EXTRACT('ROW/STARTDATE/text()').getstringval(),'DD-MON-RR') startdate,
      4         VALUE(t).EXTRACT('ROW/PERCENTAGE/text()').getnumberval() percentage,
      5         ROUND(VALUE(t).EXTRACT('ROW/BALANCE_AT_END_OF_YEAR/text()').getnumberval(),2) balance_at_end_of_year
      6    FROM XMLTABLE ('declare function local:baeoy($cust)
      7                    {
      8                      if($cust[1]) then
      9                        $cust[count($cust)]/PERCENTAGE * ($cust[count($cust)]/AMOUNT + local:baeoy($cust[position() <= count($cust) - 1]))
     10                      else (0)
     11                    }; (: end of baeoy :)
     12                    for $c in distinct-values(/ROWSET/ROW/CUSTOMER)
     13                    let $cust := /ROWSET/ROW[CUSTOMER = $c]
     14                    for $i at $row in $cust
     15                    return <ROW>
     16                           {$i/CUSTOMER}
     17                           {$i/AMOUNT}
     18                           {$i/STARTDATE}
     19                           {$i/PERCENTAGE}
     20                           <BALANCE_AT_END_OF_YEAR> {local:baeoy($cust[position() <= $row])} </BALANCE_AT_END_OF_YEAR>
     21                            </ROW>' PASSING XMLTYPE(CURSOR(SELECT customer, amount, startdate, (1 + percentage / 100) percentage, 0 balance_at_end_of_year
     22                                                            FROM deposits, interest_rates
     23                                                           WHERE startdate = deposit_date
     24                                                           ORDER BY customer, startdate))) t
     25  /
    
      CUSTOMER     AMOUNT STARTDATE PERCENTAGE BALANCE_AT_END_OF_YEAR
    ---------- ---------- --------- ---------- ----------------------
             1       1000 01-JAN-03       1.05                   1050
             1        200 01-JAN-04      1.032                   1290
             1        500 01-JAN-05      1.041                1863.39
             1        100 01-JAN-06      1.058                2077.27
             1        800 01-JAN-07      1.049                3018.25
             2         20 01-JAN-03       1.05                     21
             2        150 01-JAN-04      1.032                 176.47
             2         60 01-JAN-05      1.041                 246.17
             2        100 01-JAN-06      1.058                 366.25
             2        100 01-JAN-07      1.049                 489.09
    
    10 rows selected.
    
    Regards,
    Michael
  • Rob van Wijk
    Rob van Wijk Member Posts: 5,856 Bronze Trophy
    Let's keep this forum rock:
    It sure does this way: a third amazing solution!

    Although I don't understand it (yet) and I get a ORA-00904 on my local 10.2.0.1.0 database:
    SQL> SELECT VALUE(t).EXTRACT('ROW/CUSTOMER/text()').getnumberval() customer,
    2 VALUE(t).EXTRACT('ROW/AMOUNT/text()').getnumberval() amount,
    3 TO_DATE(VALUE(t).EXTRACT('ROW/STARTDATE/text()').getstringval(),'DD-MON-RR') startdate,
    4 VALUE(t).EXTRACT('ROW/PERCENTAGE/text()').getnumberval() percentage,
    5 ROUND(VALUE(t).EXTRACT('ROW/BALANCE_AT_END_OF_YEAR/text()').getnumberval(),2) balance_at_end_of_year
    6 FROM XMLTABLE ('declare function local:baeoy($cust)
    7 {
    8 if($cust[1]) then
    9 $cust[count($cust)]/PERCENTAGE * ($cust[count($cust)]/AMOUNT + local:baeoy($cust[position() <= count($cust)
    - 1]))
    10 else (0)
    11 }; (: end of baeoy :)
    12 for $c in distinct-values(/ROWSET/ROW/CUSTOMER)
    13 let $cust := /ROWSET/ROW[CUSTOMER = $c]
    14 for $i at $row in $cust
    15 return <ROW>
    16 {$i/CUSTOMER}
    17 {$i/AMOUNT}
    18 {$i/STARTDATE}
    19 {$i/PERCENTAGE}
    20 <BALANCE_AT_END_OF_YEAR> {local:baeoy($cust[position() <= $row])} </BALANCE_AT_END_OF_YEAR>
    21 </ROW>' PASSING XMLTYPE(CURSOR(SELECT customer, amount, startdate, (1 + percentage / 100) percentage, 0
    balance_at_end_of_year
    22 FROM deposits, interest_rates
    23 WHERE startdate = deposit_date
    24 ORDER BY customer, startdate))) t
    25 /
    ROUND(VALUE(t).EXTRACT('ROW/BALANCE_AT_END_OF_YEAR/text()').getnumberval(),2) balance_at_end_of_year
    *
    FOUT in regel 5:
    .ORA-00904: "T": ongeldige ID
    Do you know why?

    As cd already suggested in another thread, I am also looking forward to some "Introduction on XML in SQL" written by you ... :-)

    Regards,
    Rob.
  • MichaelS
    MichaelS Member Posts: 8,424 Bronze Crown
    .. and I get a ORA-00904 on my local 10.2.0.1.0 database:
    Pitty, hope I did not run in any bug here. My tested version is
    SQL> select * from v$version;
    
    BANNER
    ----------------------------------------------------------------
    Oracle Database 10g Enterprise Edition Release 10.2.0.3.0 - Prod
    PL/SQL Release 10.2.0.3.0 - Production
    CORE    10.2.0.3.0      Production
    TNS for 32-bit Windows: Version 10.2.0.3.0 - Production
    NLSRTL Version 10.2.0.3.0 - Production
    I've rewritten some part, since there was some issue with nls_numeric_characters as well. If you want, give this one a try too:
    michaels>  ALTER SESSION SET nls_numeric_characters='.,'
    Session altered.
    
    michaels>  SELECT EXTRACT (COLUMN_VALUE,'ROW/CUSTOMER/text()').getnumberval() customer,
           EXTRACT (COLUMN_VALUE,'ROW/AMOUNT/text()').getnumberval() amount,
           TO_DATE(EXTRACT (COLUMN_VALUE, 'ROW/STARTDATE/text()').getstringval (),'DD-MON-RR') startdate,
           EXTRACT (COLUMN_VALUE, 'ROW/PERCENTAGE/text()').getnumberval() percentage,
           ROUND(EXTRACT (COLUMN_VALUE, 'ROW/BALANCE_AT_END_OF_YEAR/text()').getnumberval(),2) balance_at_end_of_year
      FROM XMLTABLE ('declare function local:baeoy($cust)  
                      {
                        if($cust[1]) then  
                          (0.01 * $cust[count($cust)]/PERCENTAGE + 1) * ($cust[count($cust)]/AMOUNT + local:baeoy($cust[position() <= count($cust) - 1])) 
                        else (0) 
                      }; (:-:)
                      for $c in distinct-values(/ROWSET/ROW/CUSTOMER)
                      let $cust := /ROWSET/ROW[CUSTOMER = $c]
                      for $i at $row in $cust
                       return <ROW> 
                              {$i/CUSTOMER}
                              {$i/AMOUNT}
                              {$i/STARTDATE}
                              {$i/PERCENTAGE}
                              <BALANCE_AT_END_OF_YEAR> {local:baeoy($cust[position() <= $row])} </BALANCE_AT_END_OF_YEAR>
                              </ROW>' PASSING XMLTYPE(CURSOR(SELECT customer, amount, startdate, percentage, 0 balance_at_end_of_year 
                                                               FROM deposits, interest_rates
                                                              WHERE startdate = deposit_date 
                                                           ORDER BY customer, startdate)))
    /
      CUSTOMER     AMOUNT STARTDATE PERCENTAGE BALANCE_AT_END_OF_YEAR
    ---------- ---------- --------- ---------- ----------------------
             1       1000 01-JAN-03          5                   1050
             1        200 01-JAN-04        3,2                   1290
             1        500 01-JAN-05        4,1                1863,39
             1        100 01-JAN-06        5,8                2077,27
             1        800 01-JAN-07        4,9                3018,25
             2         20 01-JAN-03          5                     21
             2        150 01-JAN-04        3,2                 176,47
             2         60 01-JAN-05        4,1                 246,17
             2        100 01-JAN-06        5,8                 366,25
             2        100 01-JAN-07        4,9                 489,09
    
    
    10 rows selected.
    Regards,
    Michael
  • Rob van Wijk
    Rob van Wijk Member Posts: 5,856 Bronze Trophy
    Michaels, I tested again.

    First a:
    FOUT in regel 3:
    .ORA-01843: Geen geldige maand.
    And after setting my nls_date_format to DD-MON-RR a:
    FOUT in regel 1:
    .ORA-19112: fout bij evaluatie: oracle.xquery.XQException: XP0021: Dit is een dynamische fout die optreedt als een waarde in een
    CAST-uitdrukking niet naar het vereiste type kan worden geconverteerd.
    Detail: FORG0001: Ongeldige waarde voor conversie/constructor.
    And most of all, this is s l o w . . . ., unbelievable for just 10 records.

    Regards,
    Rob.
  • Is this problem really different from "Hierarchical weighted total" -- hierarchy in this case collapsed to a linearly ordered sequence of dates? We have "part assembly"

    grandchild(qty=1000) ----> child(qty=200) -----> parent(qty=500) ---->...

    The links are weighed with interest rates, so that amended picture is:

    grandchild(qty=1000) --1.05--> child(qty=200) ---1.032--> parent(qty=500) ---->...

    The "Hierarchical weighted total" problem is to calculate aggregated qty at the root, and the "connect by" really shines for that kind of queries. Yet, the recursive with gives even more elegant answer.

    ================= Extract from "SQL Design patterns" ===============

    Hierarchical Weighted Total

    Before exploring aggregation on graphs, let’s have a quick look to aggregation on trees. Aggregation on trees is much simpler and has a designated name: hierarchical total. A typical query is
    "Find combined salary of all the employees under (direct and indirect) supervision of King."
    This query, however, has no inherent complexity, and effectively reduces to familiar task of finding a set all node’s descendants.

    In graph context, the hierarchical total query is commonly referred to as bill of materials (BOM). Consider bicycle parts assembly

    Part SubPart Quantity
    Bicycle Wheel 2
    Wheel Reflector 2
    Bicycle Frame 1
    Frame Reflector 2

    Parts with the same name are assumed to be identical. They are modeled as nodes in an (acyclic directed) graph. The edges specify the construction flow:
    1. Assemble each wheel from the required parts (including reflectors).
    2. Assemble frame from the required components (including reflectors).
    3. Assemble bicycle from the required parts (including frame and wheels).

    Suppose we want to order the total list of parts for bike assembly. How do we calculate each part quantity? Unlike hierarchical total for a tree we can’t just add the quantities, as they multiply along each path.

    Figure 6.11: Bicycle assembly graph. The total number of reflector parts should be calculated as the sum of parts aggregated along each path. The path /Bicycle/Wheel/Reflector contributes to 2*2=4 parts: bicycle has 2 wheels, each wheel has 2 reflectors. Likewise, the path /Bicycle/Frame/Reflector contribute to 1*2=2 more parts.

    Therefore, there are two levels of aggregation here, multiplication of the quantities along each path, and summation along each alternative path.

    ----------------- Aggregation on Graphs (soapbox) -----------------
    Two levels of aggregation fit naturally into in graphs queries. Consider finding a shortest path between two nodes. First, we add the distances along each path, and then we choose a path with minimal length.

    Double aggregation is not something unique to graph queries, however. Consider
    "Find the sum of the salaries grouped by department. Select maximum of them."
    When expressing such a query in SQL, we accommodate the first sentence as an inner subquery inside the outer query corresponding to the second sentence

    select max(salaryExpences) from (
    select deptno, sum(sal) salaryExpenses
    from Emp
    group by dept
    )
    ------------------------(end of soapbox)-------------------------

    Hierarchical weighted total query has the same structure. The first level of aggregation where we join edges into paths is analogous to the group by subquery from the salary expense query. Formally, it is a transitive closure, which is enhanced with additional aggregates, or generalized transitive closure.

    Figure 6.12: Generalized transitive closure. There are several aggregates naturally associated with each path: the first edge, the last edge, the path length, or any aggregate on the edge weights.

    Let’s suggest some hypothetical SQL syntax for generalized transitive closure
    select distinct first(Part), last(SubPart), product (Quantity)
    from AssemblyEdges
    connect by prior Part = later SubPart
    Unfamiliar syntax requires clarification:
    § The product is a non-standard aggregate from chapter 4.
    § The first and last refer to the first and last edges in the path, correspondingly. These aggregates are unique to ordered structures such as (directed) graphs.
    § Although we didn’t use it in the example, concatenating edge labels into a string is one more natural aggregation function, which is unique to graphs. The list aggregate function is a standard way to accommodates it.
    § The later keyword is just a syntactic sugar fixing apparent asymmetry caused by the prior keyword.
    § There is no start with clause, which is, in fact, redundant. It is an outer query where we’ll restrict paths to those originated in the ‘Bicycle’ node.

    The generalized transitive closure query is enveloped with second level of aggregation, which is accomplished by standard means
    select leaf, sum(factoredQuantity) from (
    select product(Quantity) factoredQuantity,
    first(Part) root, last(SubPart) leaf
    from AssemblyEdges
    connect by prior Part = later SubPart
    ) where root = ‘Bicycle’
    group by leaf

    Enough theory, what do we do in real world to implement an aggregated weighted total query? Let’s start with Oracle, because the proposed syntax for generalized transitive closure resembles Oracle connect by.

    First, we have to be able to refer to the first and the last edges in the path

    select connect_by_root(Part) root, SubPart leaf
    from AssemblyEdges
    connect by prior Part = SubPart

    Unlike our fictional syntax, Oracle treats edges in the path asymmetrically. Any column from the AssemblyEdges table is assumed to (implicitly) refer to the last edge. This design dates back to version 7. The connect_by_root function referring to the first edge has been added in version 10. It is remarkable how this, essentially ad-hock design proved to be successful in practice.

    Next, Oracle syntax has several more path aggregate functions:
    1. level – the length of the path.
    2. sys_connect_by_path – which is essentially the list aggregate in our fictitious syntax.
    3. connect_by_is_leaf – an indicator if there is no paths which contain the current path as a prefix.
    4. connect_by_is_cycle – an indicator if the first edge is adjacent to the last.

    Unfortunately, we need an aggregate which is a product of edge weights, and not a string which is a concatenation of weights produced by sys_connect_by_path. You might be tempted to hack a function which accepts a string of the edge weights, parses it, and returns the required aggregate value.

    Nah, too easy! Can we solve the problem without coding a function (even a simple one)? Yes we can, although this solution would hardly be more efficient. The critical idea is representing any path in the graph as a concatenation of three paths:

    § a prefix path from the start node to some intermediate node i
    § a path consisting of a single weighted edge (i,j)
    § a postfix path from the node i to the end node

    Figure 6.13: A path from node x to node y is a composition of the path from node x to node i, the edge (i, j), and the path from j to y. The edge (i, j) can be positioned anywhere along the path from x to y.
    Then, we freeze the path from x to y, while interpreting the edge (i,j) as a variable. All we need to do is aggregating the weights of all those edges.

    Let’s assemble the solution piece by piece. First, all the paths in the graphs are expressed as
    with TClosure as (
    select distinct connect_by_root(Part) x, SubPart y,
    sys_connect_by_path('['||Part||','||SubPart||'>',' ') path
    from AssemblyEdges
    connect by nocycle Part=prior SubPart
    union
    select Part, Part, '' from AssemblyEdges
    union
    select SubPart, SubPart, '' from AssemblyEdges
    ) …
    This is essentially reflexive transitive closure relation enhanced with the path column. Paths are strings of concatenated edges; each edge is sugarcoated into '['||Part||','||SubPart||'>', which helps visual perception, but is inessential for the solution.

    Next, we join two paths and intermediate edge together, and group by paths
    … , PathQuantities as (
    select t1.x, t2.y,
    t1.p||' ['||e.Part||','||e.SubPart||'>'||t2.p,
    product(Quantity) Quantity
    from TClosure t1, AssemblyEdges e, TClosure t2
    where t1.y = e.Part and e.SubPart = t2.x
    group by t1.x, t2.y, t1.p||' ['||e.Part||','||e.SubPart||'>'||t2.p
    ) …
    There we have all the paths with quantities aggregated along them. Let’s group the paths by the first and last node in the path while adding the quantities
    select x, y, sum(Quantity)
    from PathQuantities
    group by x, y
    This query is almost final, as it needs only a minor touch: restricting the node x to ‘Bicycle’ and interpreting the y column as a Part in the assembly
    select y Part, sum(Quantity)
    from PathQuantities
    where x = ‘Bicycle’
    group by x, y

    Let’s move on to recursive SQL solution. It turns out to be quite satisfactory
    with TCAssembly as (
    select Part, SubPart, Quantity AS factoredQuantity
    from AssemblyEdges
    where Part = ‘Bicycle’
    union all
    select te.Part, e.SubPart, e.Quantity * te.factoredQuantity
    from TCAssembly te, AssemblyEdges e
    where te.SubPart = e.Part
    ) select SubPart, sum(Quantity) from TCAssembly
    group by SubPart

    The most important, it accommodated the inner aggregation with non-standard aggregate effortlessly! Second, the cycle detection issue that plagued recursive SQL in the section on transitive closure is not a problem for directed acyclic graphs.
  • Rob van Wijk wrote:
    Hi Jens,

    This is also an amazing solution, although as mentioned a bit cheated.
    It could be done without any cheating.
    create or replace type two as object (x number, y number);
    /
    
    create or replace type mul_type as object
    (
      total number,
    
      static function ODCIAggregateInitialize(sctx IN OUT mul_type)
        return number,
    
      member function ODCIAggregateIterate(self IN OUT mul_type, value IN two)
        return number,
    
      member function ODCIAggregateTerminate(self        IN mul_type,
                                             returnValue OUT varchar2,
                                             flags       IN number)
        return number,
    
      member function ODCIAggregateMerge(self IN OUT mul_type,
                                         ctx2 IN mul_type) return number
    );
    /
    
    create or replace type body mul_type is
    
      static function ODCIAggregateInitialize(sctx IN OUT mul_type) return number is
      begin
        sctx := mul_type(0);
        return ODCIConst.Success;
      end;
    
      member function ODCIAggregateIterate(self IN OUT mul_type, value IN two)
        return number is
      begin
        self.total := round((self.total + value.x) * value.y, 2);
        return ODCIConst.Success;
      end;
    
      member function ODCIAggregateTerminate(self        IN mul_type,
                                             returnValue OUT varchar2,
                                             flags       IN number) return number is
      begin
        returnValue := self.total;
        return ODCIConst.Success;
      end;
    
      member function ODCIAggregateMerge(self IN OUT mul_type,
                                         ctx2 IN mul_type) return number is
      begin
        return ODCIConst.Success;
      end;
    
    end;
    /
    
    CREATE OR REPLACE FUNCTION mulagg(input two) RETURN number
      AGGREGATE USING mul_type;
    /
    SQL> select d.*,
      2         i.*,
      3         mulagg(two(d.amount, 1 + i.percentage / 100)) over(partition by d.customer order by d.deposit_date) balance_at_end_of_year
      4    from deposits d, interest_rates i
      5   where d.deposit_date = i.startdate
      6  /
     
      CUSTOMER     AMOUNT DEPOSIT_DATE STARTDATE   PERCENTAGE BALANCE_AT_END_OF_YEAR
    ---------- ---------- ------------ ----------- ---------- ----------------------
             1       1000 01.01.2003   01.01.2003           5                   1050
             1        200 01.01.2004   01.01.2004         3,2                   1290
             1        500 01.01.2005   01.01.2005         4,1                1863,39
             1        100 01.01.2006   01.01.2006         5,8                2077,27
             1        800 01.01.2007   01.01.2007         4,9                3018,26
             2         20 01.01.2003   01.01.2003           5                     21
             2        150 01.01.2004   01.01.2004         3,2                 176,47
             2         60 01.01.2005   01.01.2005         4,1                 246,17
             2        100 01.01.2006   01.01.2006         5,8                 366,25
             2        100 01.01.2007   01.01.2007         4,9                  489,1
     
    10 rows selected
  • Another one solution. Just for fun. (self join makes this solution unattractive)
    create or replace type xyz as object (x number, y number, z number)
    /
    create or replace type xyz_tbl as table of xyz
    /
    create or replace function mul(p in xyz_tbl) return number is
      Result number := 0;
    begin
      for i in (select x, y from table(p) order by z) loop
        result := round((result + i.x) * i.y, 2);
      end loop;
      return result;
    end mul;
    /
    SQL> select d.*,
      2         i.*,
      3         mul(cast(multiset(select xyz(d1.amount,
      4                              1 + i1.percentage / 100,
      5                              row_number() over(order by i1.startdate))
      6                     from deposits d1, interest_rates i1
      7                    where i1.startdate = d1.deposit_date
      8                      and d1.deposit_date <= d.deposit_date
      9                      and d1.customer = d.customer) as xyz_tbl)) balance_at_end_of_year
     10    from deposits d, interest_rates i
     11   where d.deposit_date = i.startdate
     12  /
     
      CUSTOMER     AMOUNT DEPOSIT_DATE STARTDATE   PERCENTAGE BALANCE_AT_END_OF_YEAR
    ---------- ---------- ------------ ----------- ---------- ----------------------
             1       1000 01.01.2003   01.01.2003           5                   1050
             1        200 01.01.2004   01.01.2004         3,2                   1290
             1        500 01.01.2005   01.01.2005         4,1                1863,39
             1        100 01.01.2006   01.01.2006         5,8                2077,27
             1        800 01.01.2007   01.01.2007         4,9                3018,26
             2         20 01.01.2003   01.01.2003           5                     21
             2        150 01.01.2004   01.01.2004         3,2                 176,47
             2         60 01.01.2005   01.01.2005         4,1                 246,17
             2        100 01.01.2006   01.01.2006         5,8                 366,25
             2        100 01.01.2007   01.01.2007         4,9                  489,1
     
    10 rows selected
    SQL> select customer,
      2         amount,
      3         deposit_date,
      4         startdate,
      5         percentage,
      6         mul(cast(collect(tmp) as xyz_tbl)) balance_at_end_of_year
      7    from (select d.*,
      8                 i.*,
      9                 xyz(d1.amount,
     10                     1 + i1.percentage / 100,
     11                     row_number() over(order by i1.startdate)) tmp
     12            from deposits d, interest_rates i, deposits d1, interest_rates i1
     13           where d.deposit_date = i.startdate
     14             and i1.startdate = d1.deposit_date
     15             and d1.deposit_date <= d.deposit_date
     16             and d1.customer = d.customer)
     17   group by customer, amount, deposit_date, startdate, percentage
     18   order by customer, startdate
     19  /
     
      CUSTOMER     AMOUNT DEPOSIT_DATE STARTDATE   PERCENTAGE BALANCE_AT_END_OF_YEAR
    ---------- ---------- ------------ ----------- ---------- ----------------------
             1       1000 01.01.2003   01.01.2003           5                   1050
             1        200 01.01.2004   01.01.2004         3,2                   1290
             1        500 01.01.2005   01.01.2005         4,1                1863,39
             1        100 01.01.2006   01.01.2006         5,8                2077,27
             1        800 01.01.2007   01.01.2007         4,9                3018,26
             2         20 01.01.2003   01.01.2003           5                     21
             2        150 01.01.2004   01.01.2004         3,2                 176,47
             2         60 01.01.2005   01.01.2005         4,1                 246,17
             2        100 01.01.2006   01.01.2006         5,8                 366,25
             2        100 01.01.2007   01.01.2007         4,9                  489,1
     
    10 rows selected
  • Aketi Jyuuzou
    Aketi Jyuuzou Member Posts: 1,072 Bronze Badge
    Hi,4 years old members :-)
    Hehehe,I like recursive with clause B-)
    with tmp as(
    select a.customer,a.amount,a.deposit_date,b.percentage,
    Row_Number() over(partition by customer order by a.deposit_date) as rn
      from deposits a,interest_rates b
     where a.deposit_date=b.startdate),
    rec(customer,amount,deposit_date,PERCENTAGE,rn,BALANCE) as(
    select customer,amount,deposit_date,percentage,rn,amount*(1+percentage/100)
      from tmp a
     where rn=1
    union all
    select b.customer,b.amount,b.deposit_date,b.percentage,b.rn,
    (a.BALANCE+b.amount)*(1+b.percentage/100)
      from rec a,tmp b
     where a.rn+1=b.rn
       and a.customer = b.customer)
    select*from rec order by customer,deposit_date;
    
    customer  amount  deposit_  percentage  rn    balance
    --------  ------  --------  ----------  --  ---------
           1    1000  03-01-01           5   1       1050
           1     200  04-01-01         3.2   2       1290
           1     500  05-01-01         4.1   3    1863.39
           1     100  06-01-01         5.8   4  2077.2666
           1     800  07-01-01         4.9   5  3018.2527
           2      20  03-01-01           5   1         21
           2     150  04-01-01         3.2   2    176.472
           2      60  05-01-01         4.1   3  246.16735
           2     100  06-01-01         5.8   4  366.24506
           2     100  07-01-01         4.9   5  489.09107
This discussion has been closed.