Skip to Main Content

Breadcrumb

Question and Answer

Connor McDonald

Thanks for the question, Noman.

Asked: June 20, 2017 - 11:25 am UTC

Last updated: October 17, 2018 - 5:32 am UTC

Version: 11.2.0.1.0

Viewed 10K+ times! This question is

You Asked

Hello Tom,

I would like to if there is any way to calculate IRR (internal rate of return) with the help of stored function/ procedure. IRR is normal function available in Excel which allow user to calculate the IRR on base of cashflows.

For Example


Principle Amount -34,178,795.54
Installments
1 4,516,270.00
2 4,516,270.00
3 3,793,666.00
4 3,793,666.00
5 3,793,666.00
6 3,793,666.00
7 3,186,680.00
8 3,186,680.00
9 3,186,680.00
10 3,186,680.00
11 2,676,811.00
12 2,676,811.00
13 2,676,811.00
14 2,676,811.00

and the IRR calculate by Excel is 21.3785% (Formula = =IRR(D5:D19)*4 For quarterly installments

Thanks for your help.

Regards,

and Connor said...

IRR is based around iterating over the data over and over. So I tried to keep this efficient by tackling it from a broad scale, and then narrowing the range (and hence improving the accuracy) over each iteration. I haven't tested it thoroughly for boundary conditions but it should get you started


SQL> @drop t

Y1                      Y2
----------------------- -------------------------
TABLE                   cascade constraints purge

1 row selected.


Table dropped.

SQL> create table t ( id int, amt number );

Table created.

SQL> insert into t values (0,-34178795.54 );

1 row created.

SQL> insert into t values (1, 4516270.00 );

1 row created.

SQL> insert into t values (2, 4516270.00 );

1 row created.

SQL> insert into t values (3, 3793666.00 );

1 row created.

SQL> insert into t values (4, 3793666.00 );

1 row created.

SQL> insert into t values (5, 3793666.00 );

1 row created.

SQL> insert into t values (6, 3793666.00 );

1 row created.

SQL> insert into t values (7, 3186680.00 );

1 row created.

SQL> insert into t values (8, 3186680.00 );

1 row created.

SQL> insert into t values (9, 3186680.00 );

1 row created.

SQL> insert into t values (10, 3186680.00 );

1 row created.

SQL> insert into t values (11, 2676811.00 );

1 row created.

SQL> insert into t values (12, 2676811.00 );

1 row created.

SQL> insert into t values (13, 2676811.00 );

1 row created.

SQL> insert into t values (14, 2676811.00 );

1 row created.

SQL>
SQL> commit;

Commit complete.

SQL> set serverout on
SQL> declare
  2    type nlist is table of number index by pls_integer;
  3    l_values nlist;
  4    l_irr number;
  5    l_principal number;
  6    l_adjusted_amts number := 0;
  7    divisor number;
  8  begin
  9
 10    select amt bulk collect into l_values from t;
 11
 12    l_irr := 0;
 13
 14    for multiplier in 2 .. 8 loop
 15      l_adjusted_amts := 0;
 16      divisor := power(10,multiplier);
 17      l_irr := l_irr - 1/divisor;
 18
 19      loop
 20       exit
 21          when l_adjusted_amts <> 0 And
 22               l_adjusted_amts > l_values(l_values.count) * 5 / divisor  And
 23               l_adjusted_amts < l_values(l_values.count) * ( 1 + 5 / divisor );
 24          l_principal := abs(l_values(1));
 25          l_adjusted_amts := 0;
 26          l_irr := l_irr + 1/divisor;
 27
 28          for idx in 2 .. l_values.count loop
 29            l_adjusted_amts := (l_principal * (1 + l_irr)) - l_values(idx);
 30            l_principal := l_adjusted_amts;
 31          end loop;
 32      l_principal := 0;
 33      end Loop;
 34      dbms_output.put_line('l_irr at scale '||multiplier||'='||l_irr);
 35      l_irr := l_irr - 1/divisor;
 36    end loop;
 37  End;
 38  /
l_irr at scale 2=.06
l_irr at scale 3=.054
l_irr at scale 4=.0535
l_irr at scale 5=.05345
l_irr at scale 6=.053447
l_irr at scale 7=.0534463
l_irr at scale 8=.05344622

PL/SQL procedure successfully completed.

SQL>
SQL>
SQL>
SQL>



For completeness, I also found this one in SQL

http://www.sql.ru/forum/434932/xirr-function-in-pl-sql?hl=irr#4567080

=======================

Addenda:

Here's perhaps a cleaner version

SQL> set serverout on
SQL> declare
  2    type nlist is table of number index by pls_integer;
  3    l_values nlist;
  4
  5    l_threshold number := 0.005;
  6    l_guess number := l_threshold + 1;
  7    l_next_guess number := 2;
  8    l_irr number := 1;
  9
 10  begin
 11    select cash_flow bulk collect into l_values from test_tbl order by id asc;
 12
 13    while abs(l_guess) > l_threshold
 14    loop
 15      l_guess := 0;
 16      l_next_guess := 0;
 17      for i in 1 .. l_values.count
 18      loop
 19        l_guess := l_guess + l_values(i)/power(1+l_irr/100, i-1);
 20        l_next_guess := l_next_guess + -i*l_values(i)/power(1+l_irr/100, i-1);
 21      end loop;
 22      l_irr := l_irr - l_guess/l_next_guess;
 23
 24      --dbms_output.put_line('l_irr='||l_irr);
 25      --dbms_output.put_line('l_guess='||l_guess);
 26      --dbms_output.put_line('l_next_guess='||l_next_guess);
 27    end loop;
 28    dbms_output.put_line('Final l_irr='||l_irr);
 29  end;
 30  /
Final l_irr=.4904087585221242754073170297132463342012

PL/SQL procedure successfully completed.


Rating

  (7 ratings)

Is this answer out of date? If it is, please let us know via a Comment

Comments

redo

A reader, August 08, 2017 - 5:06 pm UTC

I am sure that Chris is working on analytic solution.
Connor McDonald
August 08, 2017 - 11:42 pm UTC

There's a difference between *can* solve in SQL and *should* solve in SQL

A reader, August 10, 2017 - 6:42 am UTC

Hello Connor it's not a blame or reproach. Your are both amazing. Just want to say that analytics are Chris' baby.
Connor McDonald
August 10, 2017 - 8:41 am UTC

You misunderstood what I was saying - I wasn't having a crack at Chris.

I'm saying that solving things solely in SQL is an interesting and enjoyable pastime.

But it can also be a mean of creating very complicated and hard to maintain solutions. Plenty of posts out there along the lines of SQL being Turing complete (ie, you can solve *any* problem with enough SQL).

Simplicity wins most of the time in my book

IRR Calculations

Noman HAQ, August 10, 2017 - 12:41 pm UTC

I have tried, but did not reach to my desire result, as mention in my question the IRR calculate by Excel is 21.3785% for the cashflow.
Connor McDonald
August 12, 2017 - 4:54 am UTC

And 4 * .05344622 is ?

Noman HAQ, August 13, 2017 - 6:06 am UTC

Thanks tom for great answer and the answer is 100% matches with my requirement. Can you please also suggest how it work if i increased # of rows for says instead of 14 to 36 or 48 or 60 etc..

I tried same procedure but it stuck and not responding.

Regards,
Connor McDonald
August 14, 2017 - 1:05 am UTC

As I said:

"I haven't tested it thoroughly for boundary conditions but it should get you started"

if its getting stuck, its in the 'exit when' area, ie, we are never getting to a close enough estimate. (My code was sitting at at 5% estimate either way).

Change the loop to a "for i in 1 .. 100" so that it definitely exits, and add some dbms_output calls to see where it's going astray

Noman HAQ, August 14, 2017 - 8:43 am UTC

Thanks Connor,
Now the solution is 100% matching with little customization in the code and data as bellow:

0 -34178795.54
1 4516270
2 4516270
3 3793666
4 3793666
5 3793666
6 3793666
7 3186680
8 3186680
9 3186680
10 3186680
11 2676811
12 2676811
13 2676811
14 2676811
15 2676811
16 2676811
17 2676811
18 2676811
19 2676811
20 2676811
21 2676811
22 2676811
23 4516270
24 4516270
25 3793666
26 3793666
27 3793666
28 3793666
29 3186680
30 3186680
31 3186680
32 3186680
33 2676811
34 2676811
35 2676811
36 2676811

on excel and function both returning me 9.8618015%

declare
type nlist is table of number index by pls_integer;
l_values nlist;
l_irr number;
l_principal number;
l_adjusted_amts number := 0;
divisor number;
begin

select amt bulk collect into l_values from t;

l_irr := 0;

for multiplier in 2 .. 8 loop
l_adjusted_amts := 0;
divisor := power(100,multiplier);
l_irr := l_irr - 1/divisor;

loop
--dbms_output.put_line(l_values.count);
exit
when l_adjusted_amts <> 0 And
l_adjusted_amts > l_values(l_values.count) * 10 / divisor And
l_adjusted_amts < l_values(l_values.count) * ( 1 + 10 / divisor );
l_principal := abs(l_values(1));
l_adjusted_amts := 0;
l_irr := l_irr + 1/divisor;

for idx in 2 .. l_values.count loop
l_adjusted_amts := (l_principal * (1 + l_irr)) - l_values(idx);
l_principal := l_adjusted_amts;
end loop;
l_principal := 0;
end Loop;
dbms_output.put_line('l_irr at scale '||multiplier||'='||l_irr);
l_irr := l_irr - 1/divisor;
end loop;
dbms_output.put_line(l_irr*100);
End;

Once again thanks for great help.
Connor McDonald
August 16, 2017 - 11:57 am UTC

Nice stuff. Thanks for posting your code back for others to see.

calculation of İRR

Nihad, April 25, 2018 - 3:07 pm UTC

Thanks a lot for your attention.

About İRR calculation I Want to use your script. But oyu script based on 14 months cashflows. I changed structure of your script now how many months I need I can calculate (36months,12months,13months and etc.)

But problem I faced to calculate IRR more than 36 months.
could you please explain me why I can't calculate IRR for 60 months.

Connor McDonald
April 26, 2018 - 10:40 am UTC

We dont have a magic crystal ball

CRYSTAL_BALL

You didnt really give us a lot of information, detail, test case etc...

calculation of İRR

Nihad, April 26, 2018 - 2:34 pm UTC

yeah ))) you are compalately right.

please see below cash flows. I used your function but calculated IRR is not equal excell's IRR.
Excell's IRR is - -0.30196295%

Cash flows :
row CASH_FLOW
NPV -4725
1 62.49
2 65.28
3 68.13
4 71.03
5 73.97
6 76.97
7 80.02
8 83.12
9 86.28
10 89.5
11 92.76
12 96.08
13 99.46
14 102.91
15 106.41
16 109.99
17 113.61
18 117.31
19 121.07
20 124.91
21 128.79
22 132.76
23 136.8
24 140.91
25 145.1
26 149.36
27 153.7
28 158.12
29 162.62
30 167.21
31 171.87
32 176.62
33 181.46
34 186.39
35 191.41
36 196.54

Connor McDonald
May 01, 2018 - 1:20 am UTC

GOOD_BAD

More to Explore

SQL

The Oracle documentation contains a complete SQL reference.