T-SQL 銀行家

發佈日期: 4/1/2004 | 更新日期: 4/1/2004

針對某些 財務問題的基於集合 (set-based) 的解決方案

Itzik Ben-Gan

最近,在我講授一節 SQL Server 編程課程時,有幾個學生給我提出了一個難題。在他們的生產系統中,使用了各種必須向貨幣值(如存款)應用利率或指數的財務應用程序。這些學生有一個與該系統有關的具體問題,想知道對於這個問題是否有基於集的 T-SQL 解決方案。答案當然是肯定的!讓我們研究一下我的學生所提出的這個一般性問題,以便您可以根據自己的具體需要來調整解決方案。

指數化貨幣值

貨幣價值是隨着時間的推移而發生變化的。財務應用程序很少會以其輸入數據庫時的原始形式來顯示貨幣值的。財務機構需要向儲蓄帳戶存款應用利率、向損益報表中的貨幣值應用指數率(如消費價格指數 - CPI)、向欠外國供應商的債務應用貨幣兌換率等。

通常,貨幣值是與生效日期一起輸入到財務應用程序的數據庫中的。財務應用程序將向本金應用一系列調整,並計算截止到之後某個日期(如“今天”或“2002 年 12 月 31 日)的結果值。例如,假設 2002 年 9 月 7 日您向系統中輸入金額 $1,300。您需要將該金額與 CPI(CPI 於每月的 15 日進行更新)進行鏈接,並顯示截止到 2002 年 12 月 31 日的值。CPI 包含月利率,這些月利率反映貨幣值根據消費者平均購買力而發生的變化。諸如 CPI 之類的指數能夠以增長百分比或比例係數來表示。消費者通常關心增長百分比,而會計通常使用指數率進行計算。

在本例中,假設指數率在 9 月到 12 月期間分別增長了 0.3%、0.6%、0.4% 和 0.9%。本金金額則需要進行如下調整:

$1,300.0000 * (1 + 0.3%) = $1,303.9000 
$1,303.9000 * (1 + 0.6%) = $1,311.7234 
$1,311.7234 * (1 + 0.4%) = $1,316.9703 
$1,316.9703 * (1 + 0.9%) = $1,328.8230

在向儲蓄帳戶存款應用利率時,將使用類似的計算(假設在帳戶條款中指定的是非固定的利率),但是利率變化的間隔可能會有所不同。

現在,讓我們看一下這個一般性的問題。運行清單 1中的腳本,創建 Amounts 和 IndexRates 表,然後在其中填入示例數據。Amounts 表包含貨幣值及其生效日期,IndexRates 表包含 CPI 的月增長百分比。您的任務是編寫一個查詢,讓該查詢能夠通過調整金額來反映將來的一個給定日期(在 @givendate 變量中提供)。例如,第 18 頁上的 表 1顯示了這個特定日期爲 2002 年 12 月 31 日時所需的結果。

模仿聚合 PRODUCT() 的解決方案

我們首先寫出要應用於每個金額的公式:

indexed_value =  amt_value * (1+1st idx_growth) * 
(1+2nd idx_growth) * ... * (1+last idx_growth)

其中 1st、2nd(等)比率分別對應於金額的生效日期和其間的幾個時段,last 比率則是在這個特定日期生效的比率。

將相應的指數增長與每個金額相關聯是這個問題較爲簡單的部分。可以通過使用下面的 JOIN 條件,將 Amounts 表與 IndexRates 表進行聯接:

ON IR.idx_date BETWEEN amt_date AND @givendate

對於每個金額您將獲取多個指數行,這些行代表着上述公式中的多個指數變化。使用左外部聯接,可以確保生效日期晚於最後一個指數率更改日期的金額(例如,Amounts 中生效日期爲 2002 年 12 月 24 日的行)也會顯示在輸出中。

在此處,更棘手的問題是根據公式的要求計算返回的所有指數變化的乘積。如果 T-SQL支持聚合 PRODUCT() 函數(該函數將計算一組值的乘積,這與 SUM() 函數計算一組值的和非常相似)的話,就可以使用 清單 2中顯示的僞查詢了。(不過,因爲 T-SQL 不支持聚合 PRODUCT() 函數,所以您無法運行該查詢。)我在僞查詢的 SELECT 列表中使用了 ISNULL(),這樣當其生效日期晚於最後一個指數率變化日期時,也會返回本金金額。

在 2001 年 5 月發表的“Adding Performance”(InstantDoc ID 20131) 一文中,我討論了一個數學方法,此方法可通過在以下公式中使用 LOG10 函數來模仿聚合 PRODUCT() 函數:

PRODUCT() = POWER(10., 
 SUM(LOG10()))

如果您將上面的公式應用於此問題的查詢,則會得到如下表達式:

ISNULL(POWER(CAST(10 AS FLOAT), 
 SUM(LOG10(1+idx_growth))), 
 amt_value) * amt_value

POWER() 函數的返回值與其第一個參數的數據類型相同,因此該代碼會將數字 10 顯式轉換爲 FLOAT,以強制輸出 FLOAT 結果。最後,將上面的表達式轉換爲貨幣數據類型即可完成任務。您可以通過運行 清單 3顯示的最後一個查詢來獲取 Table 1 顯示的所需結果。

使用各自指數率係數的解決方案

解決該問題的另一種方法是使用 CPI 的不同表示形式。您可以存儲比率係數(該係數表示某個月的指數率與預定義的基月指數率的比值),而不是存儲指數率相對於上一個月的變化百分比。例如,可以將 2001 年 12 月確定爲基月,並將它的比率係數值設置爲 1。1 月的指數增長率爲 0.1%,因此 1 月的指數率係數將爲 1 * (1+0.1%) = 1.001。2 月的指數增長率爲 0.2%,因此 2 月的指數率係數將爲 1.001 * (1+0.2%),依此類推。請記住,每個指數率係數都包括自基礎日期以來所有的累積指數變化。因此,要向 amt_value 應用從 from_month 到 to_month 期間的指數變化,可以使用下面的公式:

amt_value * (to_index_rate / from_index_rate)

就這麼簡單。您可以存儲指數率係數(而非指數增長),也可以添加另一列,以便可以在所執行的各種財務計算中使用最適合您的需求的係數。甚至還可以在 INSERT 語句中僅指定一個值,並且讓觸發器爲您計算另一個值。對您來說,一個重要的好處是,例如 IndexRates 這樣的表將沒有更新或刪除,而且對於每個期段(例如,每月),只需向該表中插入一行。

運行第 20 頁上 清單 4中的代碼,可以用另一列 idx_rate 重新創建 IndexRates 表。該代碼還會創建一個插入觸發器,該觸發器會基於存儲在 idx_growth 列中的指數增長值來計算指數率係數,並用指數變化重新填充該表。該觸發器運行一個 UPDATE 語句將 IndexRates 表和已插入表聯接起來,以便查找需要進行更新的新行。SET 子句使用子查詢來檢索上個月的指數率係數,並將其與 (1 + current_month's_index_growth) 相乘。當在該表中輸入第一行時,整個表達式將生成 NULL。因此,在這種情況下,該代碼使用 ISNULL() 返回 1。

接着,您需要編寫一個查詢,以便對於每個金額查找該金額的生效日期和特定日期的指數率係數;您將在本節前面提供的簡單公式中使用這些日期。在編寫該查詢時有一個問題,那就是 amt_date 和 @givendate 值不一定是當月的第 15 日。可以使用 CASE 表達式來檢查該日期的日期值是否小於 15,如果小於 15,則生效日期是上個月的第 15 日;否則,則是當月的第 15 日。因此,將按如下方式計算特定日期的生效日期:

DECLARE @givendate AS SMALLDATETIME 
SET @givendate = '20021231' 
DECLARE @effective_givendate AS SMALLDATETIME 
SET @effective_givendate = 
  CASE 
    WHEN DAY(@givendate) < 15 
      THEN CONVERT(CHAR(6), DATEADD(month, -1, 
       @givendate), 112) 
          + '15' 
    ELSE CONVERT(CHAR(6), @givendate, 112) + '15' 
  END

現在,可以使用類似的技術來計算該金額的生效日期,然後在 Amounts 表和 IndexRates 表的兩個實例之間使用一個三向聯接。一個實例檢索該金額生效日期的指數率係數;另一個實例檢索該特定日期的指數率係數。 清單 5顯示了這個完整的查詢。該代碼看上去比上一個解決方案中的代碼稍長,但是,它的執行速度會更快,因爲它無需在每個金額的所需期段內查找所有的指數變化,而只是查找第一個和最後一個指數率係數。

學以致用

通常,用 T-SQL爲財務問題提供解決方案看上去較爲複雜。但是,當您擁有一個包含許多 T-SQL 技巧(如模仿聚合 PRODUCT() 函數)的工具箱時,就可以解決問題。嘗試爲該問題提出幾個解決方案,在找到一個有效的解決方案時,不要滿足。當您找到多個解決方案時,可以從中選擇一個此時最能滿足需求的解決方案,並使用已掌握的技術爲下一個任務設計解決方案。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章