2010-11-16 53 views
1

我有一張表,用於存儲許多資產的到期收入時間表。
該表格給出了新收入金額生效的日期以及該日收入金額。沒有光標的情況下,日常收入的總和?

我想計算兩個日期之間到期的總收入。

這裏的表結構和樣本數據:

DECLARE @incomeschedule 
TABLE (asset_no int, start_date datetime, amt decimal(14,2), 
     PRIMARY KEY (asset_no, start_date)) 
/* 
-- amt is the amount of daily income 
-- start_date is the effective date, from when that amt starts to be come in 
*/ 

INSERT INTO @incomeschedule (asset_no, start_date, amt) 
VALUES (1, '1 Jan 2010', 3) 
INSERT INTO @incomeschedule (asset_no, start_date, amt) 
VALUES (1, '1 Jul 2010', 4) 
INSERT INTO @incomeschedule (asset_no, start_date, amt) 
VALUES (1, '1 Oct 2010', 5) 
INSERT INTO @incomeschedule (asset_no, start_date, amt) 
VALUES (2, '1 Jan 2010', 1) 
INSERT INTO @incomeschedule (asset_no, start_date, amt) 
VALUES (2, '1 Jan 2012', 2) 
INSERT INTO @incomeschedule (asset_no, start_date, amt) 
VALUES (2, '1 Jan 2014', 4) 
INSERT INTO @incomeschedule (asset_no, start_date, amt) 
VALUES (2, '1 Jan 2016', 5) 

因此,對於資產1,有$ 3收入每天從1月1日,從1上升到$ 4從7月1日,5 $十月

爲計算2010年1月1日至2020年12月31日期間的總收入,以資產1爲例,我們有
- 181天爲3美元(2010年1月1日至2010年6月30日)= 543美元
- 加上92天4美元(2010年7月1日至2010年9月30日)= 368美元
- 加3744天在$ 5(2010年10月1日至12月31日2020年)= $一萬八千七百二十
- 總計$一九六三一

所以[同樣地,資產2爲$ 14242進來] 2010年1月1日至12月31日的輸入範圍2020年,我希望以下的輸出:

asset_no total_amt 
    1  19631.00 
    2  14242.00 

我一直在使用光標[我需要知道以前的行值執行Calcs(計算)],但我想知道是否有可能產生這樣寫這些結果使用基於集合的技術。

這是基於光標的代碼,以防萬一。

DECLARE @date_from datetime, 
     @date_to datetime 

SET @date_from = '1 Jan 2010' 
SET @date_to = '31 Dec 2020' 

/*-- output table to store results */ 
DECLARE @incomeoutput TABLE (asset_no int PRIMARY KEY, total_amt decimal(14,2)) 

/*-- cursor definition */ 
DECLARE c CURSOR FAST_FORWARD FOR 
SELECT asset_no, start_date, amt 
FROM @incomeschedule 
UNION 
/* insert dummy records to zeroise from @date_from, 
    in case this is earlier than initial start_date per asset */ 
SELECT DISTINCT asset_no, @date_from, 0 
FROM @incomeschedule 
WHERE NOT EXISTS (SELECT asset_no, start_date FROM @incomeschedule WHERE start_date <= @date_from) 
ORDER BY asset_no, start_date 

/*-- initialise loop variables */ 
DECLARE @prev_asset_no int, @dummy_no int 
SET @dummy_no = -999 /* arbitrary value, used to detect that we're in the first iteration */ 
SET @prev_asset_no = @dummy_no 

DECLARE @prev_date datetime 
SET @prev_date = @date_from 

DECLARE @prev_amt decimal(14,2) 
SET @prev_amt = 0 

DECLARE @prev_total decimal(14,2) 
SET @prev_total = 0 

DECLARE @asset_no int, @start_date datetime, @amt decimal(14,2) 

/*-- read values from cursor */ 
OPEN c 
FETCH NEXT FROM c INTO @asset_no, @start_date, @amt 
WHILE @@FETCH_STATUS = 0 
    BEGIN 
     /*-- determine whether we're looking at a new asset or not */ 
     IF @prev_asset_no = @asset_no -- same asset: increment total and update loop variables 
      BEGIN 
       SET @prev_asset_no = @asset_no 
       SET @prev_total = @prev_total + (@prev_amt * DATEDIFF(d, @prev_date, @start_date)) 
       SET @prev_date = @start_date 
       SET @prev_amt = @amt 
      END 
     ELSE /*-- new asset: output record and reset loop variables */ 
      BEGIN 
       IF @prev_asset_no <> @dummy_no /*-- first time round, we don't need to output */ 
        BEGIN 
         SET @prev_total = @prev_total + (@prev_amt * DATEDIFF(d, @prev_date, @date_to)) 
         INSERT INTO @incomeoutput (asset_no, total_amt) VALUES (@prev_asset_no, @prev_total) 
        END 
       SET @prev_asset_no = @asset_no 
       SET @prev_total = 0 
       SET @prev_date = @start_date 
       SET @prev_amt = @amt 
      END 

     FETCH NEXT FROM c INTO @asset_no, @start_date, @amt 
    END 

SET @prev_total = @prev_total + (@prev_amt * DATEDIFF(d, @prev_date, @date_to)) 
INSERT INTO @incomeoutput (asset_no, total_amt) VALUES (@prev_asset_no, @prev_total) 

CLOSE c 
DEALLOCATE c 

SELECT asset_no, total_amt 
FROM @incomeoutput 

n.b.我確實考慮過發佈基於光標的解決方案作爲答案,以避免使問題膨脹......但是,我說出了問題的方式,我需要一個基於非遊標的答案,所以這感覺就像是更好的方法。請評論這是不是正確的禮儀。

+0

+1發佈DDL,我不會沒有它回答它。 – RedFilter 2010-11-16 15:08:00

+0

@RedFilter - 非常感謝,我喜歡發佈DDL,因爲我無法承擔賞金。 :) – richaux 2010-11-16 16:37:06

回答

2
select i1.asset_no, 
    sum(i1.amt * cast(isnull(i2.start_date, '2020-12-31') - i1.start_date as int)) as total_amt 
from @incomeschedule i1 
left outer join @incomeschedule i2 on i1.asset_no = i2.asset_no 
    and i2.start_date = (
     select MIN(start_date) 
     from @incomeschedule 
     where start_date > i1.start_date 
      and asset_no = i1.asset_no 
    ) 
group by i1.asset_no 
+0

我建議將日期時間轉換爲float而不是int,因爲int會自動舍入並可能產生不需要的結果。 – Shagglez 2010-11-16 15:21:02

+0

@Shagglez - 在這種情況下感覺像'int'強制轉換是安全的,因爲日期永遠不會指定時間元素(我應該真的使用'date'數據類型,而不是'datetime')。 – richaux 2010-11-16 16:30:23

+0

@Shagglez你應該永遠不要考慮使用float ina calulation,除非你想舍入錯誤。如果你需要指定小數位。 – HLGEM 2010-11-16 16:41:40

1

爲什麼使用CTE?

declare @EndDate datetime 
set @EndDate = '202' 

select t1.asset_no,SUM(DATEDIFF(day,t1.start_date,COALESCE(t2.start_date,@EndDate))*t1.amt) 
from 
    @incomeschedule t1 
     left join 
    @incomeschedule t2 
     on 
      t1.asset_no = t2.asset_no and 
      t1.start_date < t2.start_date 
     left join 
    @incomeschedule t3 
     on 
      t1.asset_no = t3.asset_no and 
      t1.start_date < t3.start_date and 
      t3.start_date < t2.start_date 
where 
    t3.asset_no is null 
group by t1.asset_no 

如果有一些資產,不具有初始入口與您的範圍的開始日期,查詢稍微複雜一些(但不是太糟糕了)

(的加盟該表是第三次(t3),空檢查是爲了確保t1和t2之間的匹配行是連續的)

+0

關於CTE的好處。我的意思是基於集合而不是基於遊標的。我會編輯這個問題來澄清。 – richaux 2010-11-16 16:25:40

相關問題