没有光标可以汇总每日收入吗?
我有一个表,用于存储多种资产的到期收入表。
该表给出了新收入金额生效的日期以及每日收入金额。
我想计算出两个日期之间的总收入。
表结构和示例数据如下:
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,从 1 月 1 日起每天有 3 美元收入,从 7 月 1 日起升至 4 美元,从 10 月 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 美元
-- 加上 5 美元的 3744 天(2010 年 10 月 1 日至 2020 年 12 月 31 日)= 18720 美元
-- 总计 19631 美元
[同样,资产 2 的价格为 14242 美元]
因此,对于 2010 年 1 月 1 日至 2020 年 12 月 31 日的输入范围,我期望得到以下输出:
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
nb我确实考虑过发布基于光标的解决方案作为答案,以避免问题膨胀......但是我表达问题的方式我需要一个非基于光标的答案,所以这感觉是更好的方法。如果这不是正确的礼仪,请发表评论。
I have a table that stores the schedule of income due, for a number of assets.
That table gives the date that a new income amount becomes effective, together with that daily income amount.
I want to work out the total income due between 2 dates.
Here's the table structure and sample data:
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)
So for Asset 1, there is $3 income daily from 1 Jan, rising to $4 from 1 Jul, to $5 from 1 Oct.
For the calculation of total income between 1 Jan 2010 and 31 Dec 2020, using Asset 1 as an example, we have
-- 181 days at $3 (1 Jan 2010 to 30 Jun 2010) = $543
-- plus 92 days at $4 (1 Jul 2010 to 30 Sep 2010) = $368
-- plus 3744 days at $5 (1 Oct 2010 to 31 Dec 2020) = $18720
-- total $19631
[Similarly, Asset 2 comes in at $14242]
So for an input range of 1 Jan 2010 to 31 Dec 2020, I expect the following output:
asset_no total_amt
1 19631.00
2 14242.00
I have written this using a cursor [as I need to know the previous rows values to perform the calcs] but I would like to know whether it is possible to produce these results using set-based techniques.
Here's the cursor based code, in case that helps.
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. I did consider posting the cursor-based solution as an answer, to avoid bloating the question ... but the way I've phrased the question I need a non-cursor based answer, so this feels like the better approach. Please comment if this isn't the correct etiquette.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
为什么要使用 CTE?
如果某些资产没有与范围的开始日期相同的初始条目,则查询会稍微复杂一些(但还不错)
(第三次连接到表(t3)并且空值检查是为了确保t1和t2之间的匹配行是连续的)
Why use a CTE?
If there are some assets which don't have an initial entry the same as the start date of your range, the query is slightly more complex (but not too bad)
(The join to the table a 3rd time (t3) and the null check is to ensure that the matching rows between t1 and t2 are consecutive)