2012-08-22 52 views
2

我有這樣一個數據表:來自行而不是列返回(IRR)的TSQL內部收益率

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函數所以比我可以通過我的專欄,但我沒有想法,我應該怎麼做:/

+0

我這樣做的方式,它變得非常複雜,Unpivot和可能的表變量。你必須能夠處理任何表中的任何值,或者你是否只能處理那些特定表中的特定列? –

+0

現在我需要處理該表的最後21列 - 從Mo到M20。我將擔心未來的通用功能。現在只有這一個:) – Misiu

+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) 
+0

這是我的想法。我試圖避免這樣的聲明,因爲如果我需要80列,那麼函數將需要81個參數。但我認爲這是我唯一的選擇:) – Misiu

+0

我會堅持使用這個解決方案。這是最簡單的實現,我總是可以添加額外的參數。最好是具有通用功能,但現在這個選項對我很有好處。謝謝! – Misiu

1

我的建議方法是...

定義一個獨立的table @t_IDs (term INT, value DECIMAL)。在調用ufn_IRR()之前,請用rows(1, M0), (2, M1) ... (n, M20)手動填充table。 (Err,與我的SQL知識,這似乎是一個循環的步驟,但也許SQL-Server可以在一個聲明?)然後簡單地與@guess參數調用ufn_IRR()。首先需要修改ufn_IRR()並丟棄將@strIDs劃分爲@t_IDs table的起始位。

這種方法的優點是處理不同大小的數據。

ufn_IRR()使用線性查找方法。對於大型或重複性任務,可能需要調查其他方法;關於「內部收益率」的維基百科頁面暗示了一些。

1

勾畫出一種方法來做到這一點。

首先,我會寫一個程序與這一個表的工作,有以下步驟:

  • 創建臨時表
  • 使用的select... unpivot...查詢的基表中填充它(轉換21列插入1列21行數據集)
  • 運行對臨時表的數學

壞處就是,你只能處理一個表,你必須硬編碼未轉換查詢中的21列的名稱,這不太靈活。

Fancier步驟是使用創建#temp表,然後使用動態sql基於任何列(無論從哪個表中)編程detrmine需要進行建立unpivot。

要將它變成函數,您必須創建一個表變量並將其用作參數。然後,任何想要調用該函數的例程都必須構建並填充#temp表,然後將其傳遞給該函數。

就像我說的,它變得非常複雜。爲了快速完成工作,我會使用@ valexhome的答案,並在需要時再擔心更新/升級它。

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 @[email protected]_new 
     else 
      select @[email protected]/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 
+0

這是一個有點老的問題,但我會檢查你的修改空閒時間,感謝您的幫助:) – Misiu

+0

工程太棒了!爲我節省了很多時間......謝謝! – CiucaS

+0

@AlphaAU爲什麼這個函數不會返回負IRR?爲什麼它對於負IRR返回0? – CiucaS