我有一张表,用于存储许多资产的到期收入时间表。
该表格给出了新收入金额生效的日期以及该日收入金额。没有光标的情况下,日常收入的总和?
我想计算两个日期之间到期的总收入。
这里的表结构和样本数据:
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.我确实考虑过发布基于光标的解决方案作为答案,以避免使问题膨胀......但是,我说出了问题的方式,我需要一个基于非游标的答案,所以这感觉就像是更好的方法。请评论这是不是正确的礼仪。
+1发布DDL,我不会没有它回答它。 – RedFilter 2010-11-16 15:08:00
@RedFilter - 非常感谢,我喜欢发布DDL,因为我无法承担赏金。 :) – richaux 2010-11-16 16:37:06