来自行而不是列的TSQL内部收益率(IRR)

时间:2012-08-22 13:49:40

标签: sql tsql sql-server-2005

我有一个这样的数据表:

Group   ID    M0    M1    ...    M20
------------------------------------
    1    1    10    -2    ...     54
    1    2    -8    10    ...    -20
    2    6   -10    12    ...      0
    2    4    45    52    ...      0
    2    5    12   102    ...      0

我按照这样分组我的数据:

SELECT Group
     , round(sum(M0+M1+M2+...+M20)/count(ID),2) as profit
     , round(sum(M0),2) as M0
     , round(sum(M1),2) as M1
     , ...
     , round(sum(M20),2) as M20
FROM
    data_table
GROUP BY
    Group

我需要添加的是基于M0到M20列的IRR。

我找到了一个看起来像这样的IRR函数:

CREATE FUNCTION [dbo].[ufn_IRR]
  (
   @strIDs VARCHAR(8000),
   @guess DECIMAL(30,10)
  )
RETURNS DECIMAL(30, 10)
AS 
  BEGIN
    DECLARE @t_IDs TABLE (
        id INT IDENTITY(0, 1),
        value DECIMAL(30, 10)
    )
    DECLARE @strID VARCHAR(12),@sepPos INT,@NPV DECIMAL(30, 10)
    SET @strIDs = COALESCE(@strIDs + ',', '')
    SET @sepPos = CHARINDEX(',', @strIDs)
    WHILE @sepPos > 0 
      BEGIN
        SET @strID = LEFT(@strIDs, @sepPos - 1)
        INSERT INTO @t_IDs ( value ) SELECT ( CAST(@strID AS DECIMAL(20, 10)) ) WHERE ISNUMERIC(@strID) = 1
        SET @strIDs = RIGHT(@strIDs, DATALENGTH(@strIDs) - @sepPos)
        SET @sepPos = CHARINDEX(',', @strIDs)
      END

    SET @guess = CASE WHEN ISNULL(@guess, 0) <= 0 THEN 0.00001 ELSE @guess END

    SELECT @NPV = SUM(value / POWER(1 + @guess, id)) FROM @t_IDs
    WHILE @NPV > 0 
      BEGIN
        SET @guess = @guess + 0.00001
        SELECT @NPV = SUM(value / POWER(1 + @guess, id)) FROM @t_IDs
      END
    RETURN @guess
  END

示例用法如下:

SELECT  dbo.ufn_IRR('-4000,1200,1410,1875,1050',0.00001)

但在我的情况下,我想避免在我的列中创建字符串,如下所示:

 SELECT  dbo.ufn_IRR(
    cast(round(sum([M0]), 2) AS NVARCHAR)+','
   +cast(round(sum([M1]), 2) AS NVARCHAR)+','
   +cast(round(sum([M2]), 2) AS NVARCHAR)+','
   +cast(round(sum([M3]), 2) AS NVARCHAR)+','
   +cast(round(sum([M4]), 2) AS NVARCHAR)+','
   +...+','
   +cast(round(sum([M20]), 2) AS NVARCHAR)
 ,0.00001)

我认为首先创建一个字符串然后在函数内部将其切换成表格是没有意义的 我想修改IRR功能,因为我可以通过我的列,但我没有想法我应该怎么做:/

4 个答案:

答案 0 :(得分:3)

我已更新代码以更改猜测方法 从增量(0.00001) 对于客人0.1至0.01至0.001

该函数的示例用法是

select [dbo].[fn_IRR]('-21000,0,0,1948.00,1948.00,1948.00,1948.00,1948.00,1948.00,1948.00,1948.00,1948.00,1948.00,1948.00,1948.00',0.0000000001)
希望这有帮助。

ALTER FUNCTION [dbo].[fn_IRR]
(
    @str varchar(max),
    @precision DECIMAL(30,10)
)
RETURNS DECIMAL(30, 10)
AS 
BEGIN
    declare  @AdjValue decimal(30,10)
            ,@guess Decimal(30,10)
            ,@guess_new Decimal(30,10)

    Select @AdjValue = 0.1, @guess=0

    DECLARE @t_IDs TABLE (
        id INT IDENTITY(0, 1),
        value DECIMAL(30, 10)
    )
    Declare @NPV DECIMAL(30, 10)
           ,@iter_cnt int

    INSERT INTO @t_IDs 
        select * from dbo.fn_SplitString(@str,',')

    SET @guess = CASE WHEN ISNULL(@guess, 0) <= 0 THEN 0 ELSE @guess END

    SELECT @NPV = SUM(value / POWER(1 + @guess, id)) FROM @t_IDs
    WHILE ((@NPV > 0 or @AdjValue > @precision) and (isnull(@iter_cnt,0) < 8192))
    BEGIN
        SET @guess_new = @guess + @AdjValue
        SELECT @NPV = SUM(value / POWER(1 + @guess_new, id)) FROM @t_IDs
        set @iter_cnt = isnull(@iter_cnt,0) + 1
        if (@NPV > 0)
            select @guess=@guess_new
        else
            select @AdjValue=@AdjValue/10
    END
    RETURN @guess
END


Create FUNCTION [dbo].[fn_SplitString](
     @str nvarchar(max)
    ,@sep nvarchar(max)
)
RETURNS TABLE
AS
RETURN
    WITH a AS(
        SELECT CAST(0 AS BIGINT) as idx1,CHARINDEX(@sep,@str) idx2
        UNION ALL
        SELECT idx2+1,CHARINDEX(@sep,@str,idx2+1)
        FROM a
        WHERE idx2>0
    )
    SELECT SUBSTRING(@str,idx1,COALESCE(NULLIF(idx2,0),LEN(@str)+1)-idx1) as StrValue
    FROM a

答案 1 :(得分:1)

CREATE FUNCTION [dbo].[ufn_IRR]
  (
   @s0 DECIMAL(30,10),
   @s1 DECIMAL(30,10),
   @s2 DECIMAL(30,10),
   @s3 DECIMAL(30,10),
   @s4 DECIMAL(30,10),
   @s5 DECIMAL(30,10),
   @s6 DECIMAL(30,10),
   @s7 DECIMAL(30,10),
   @s8 DECIMAL(30,10),
   @s9 DECIMAL(30,10),
   @s10 DECIMAL(30,10),
   @s11 DECIMAL(30,10),
   @s12 DECIMAL(30,10),
   @s13 DECIMAL(30,10),
   @s14 DECIMAL(30,10),
   @s15 DECIMAL(30,10),
   @s16 DECIMAL(30,10),
   @s17 DECIMAL(30,10),
   @s18 DECIMAL(30,10),
   @s19 DECIMAL(30,10),
   @s20 DECIMAL(30,10),
   @guess DECIMAL(30,10)
  )
RETURNS DECIMAL(30, 10)
AS 
  BEGIN
    DECLARE @t_IDs TABLE (
        id INT IDENTITY(0, 1),
        value DECIMAL(30, 10)
    )
    Declare @NPV DECIMAL(30, 10)

    INSERT INTO @t_IDs (value) values (@s0);
    INSERT INTO @t_IDs (value) values (@s1);
    INSERT INTO @t_IDs (value) values (@s2);
    INSERT INTO @t_IDs (value) values (@s3);
    INSERT INTO @t_IDs (value) values (@s4);
    INSERT INTO @t_IDs (value) values (@s5);
    INSERT INTO @t_IDs (value) values (@s6);
    INSERT INTO @t_IDs (value) values (@s7);
    INSERT INTO @t_IDs (value) values (@s8);
    INSERT INTO @t_IDs (value) values (@s9);
    INSERT INTO @t_IDs (value) values (@s10);
    INSERT INTO @t_IDs (value) values (@s11);
    INSERT INTO @t_IDs (value) values (@s12);
    INSERT INTO @t_IDs (value) values (@s13);
    INSERT INTO @t_IDs (value) values (@s14);
    INSERT INTO @t_IDs (value) values (@s15);
    INSERT INTO @t_IDs (value) values (@s16);
    INSERT INTO @t_IDs (value) values (@s17);
    INSERT INTO @t_IDs (value) values (@s18);
    INSERT INTO @t_IDs (value) values (@s19);
    INSERT INTO @t_IDs (value) values (@s20);

    SET @guess = CASE WHEN ISNULL(@guess, 0) <= 0 THEN 0.00001 ELSE @guess END

    SELECT @NPV = SUM(value / POWER(1 + @guess, id)) FROM @t_IDs
    WHILE @NPV > 0 
      BEGIN
        SET @guess = @guess + 0.00001
        SELECT @NPV = SUM(value / POWER(1 + @guess, id)) FROM @t_IDs
      END
    RETURN @guess
  END

并使用它

dbo.ufn_IRR(round(sum([M0]), 2),
            round(sum([M1]), 2),
            .....
            round(sum([M20]),2),
            0.00001)

答案 2 :(得分:1)

我建议的方法是......

定义一个独立的table @t_IDs ( term INT, value DECIMAL )。在调用ufn_IRR()之前,请使用table rows手动填充(1, M0), (2, M1) ... (n, M20)。 (错误,我的SQL知识这似乎是一个循环步骤,但也许SQL-Server可以在一个语句中执行此操作?)然后只需使用ufn_IRR()参数调用@guess即可。您首先需要修改ufn_IRR()并丢弃将@strIDs拆分为@t_IDs table的初始位。

这种方法具有处理不同大小数据的优势。

ufn_IRR()使用线性根寻找方法。对于大型或重复性任务,也许可以调查其他方法;关于“内部收益率”的维基百科页面提供了一些建议。

答案 3 :(得分:1)

勾勒出一种方法来做到这一点。

首先,我会编写一个程序来处理这一个表,并执行以下步骤:

  • 创建临时表
  • 使用select... unpivot...查询从基表填充它(将21列转换为1列21行数据集)
  • 针对临时表运行数学

下行是,您只能处理一个表,并且您必须在unpivot查询中硬编码21列的名称,这不是很灵活。

Fancier步骤是使用create #temp表,然后使用动态sql根据编程方式需要处理的任何列(来自任何表)来构建unpivot。

要将其转换为函数,您必须创建一个表变量并将其用作参数。然后,任何想要调用该函数的例程都必须构建并填充#temp表,然后将其传递给函数。

就像我说的那样,它变得相当复杂。为了快速完成工作,我将使用@ valexhome的答案,并担心在需要时更新/升级它。