2010-11-16 54 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

相关问题