我为一家为我们的客户提供多种套餐的电信公司工作。其中一些套餐包括每月可以使用的一定数量的免费长途分钟。
目前,免费分钟套餐存在缺陷,允许客户获得比套餐真正允许的更多免费分钟。同时,当前系统有时可以防止客户以正确的顺序用尽所有免费分钟。
我已经想出如何修改流程以确保将包正确分配给客户的通话记录。现在,我试图改变我们处理这些记录的方式,以防止他们使用超过他们的免费分钟包。
我尝试使用CTE,窗口函数和更新语句的组合来解决此问题。这个想法是,拥有免费分钟套餐的客户可以使用该套餐,如借记卡,这样如果通话时间小于他们离开的分钟数,它将覆盖通话并减去金额剩余的分钟数。可用的免费分钟数;但如果持续时间超过可用的免费分钟数,则它不会覆盖通话。我几乎把它全部搞清楚了,但我被卡住了,因为我无法弄清楚如何通过窗口函数查找前一行计算的值。
以下是我使用测试表获得的结果示例(注意:持续时间以秒为单位,因此会转换为分钟数):
CustID Duration FrMinsAvailable NewAvMins MinutesBilled PrevAvMins
---------------------------------------------------------------------------
14000 250000 4250.2 83.5 4166.7 NULL
14000 9000 4250.2 -66.5 150 83.5
14000 4800 4250.2 -146.5 80 -66.5
14000 450 4250.2 -154 7.5 -146.5
14000 335 4250.2 -159.6 5.6 -154
14000 200 4250.2 -162.9 3.3 -159.6
14000 65 4250.2 -164 1.1 -162.9
14000 45 4250.2 -164.7 0.8 -164
14000 32 4250.2 -165.3 0.5 -164.7
14000 25 4250.2 -165.7 0.4 -165.3
14000 21 4250.2 -166 0.4 -165.7
14000 5 4250.2 -166.1 0.1 -166
以下是我想获得的结果:
CustID Duration FrMinsAvailable NewAvMins MinutesBilled PrevAvMins
-------------------------------------------------------------------------------
14000 250000 4250.2 83.5 4166.7 NULL
14000 9000 4250.2 83.5 150 83.5
14000 4800 4250.2 3.5 80 83.5
14000 450 4250.2 3.5 7.5 3.5
14000 335 4250.2 3.5 5.6 3.5
14000 200 4250.2 0.2 3.3 3.5
14000 65 4250.2 0.2 1.1 0.2
14000 45 4250.2 0.2 0.8 0.2
14000 32 4250.2 0.2 0.5 0.2
14000 25 4250.2 0.2 0.4 0.2
14000 21 4250.2 0.2 0.4 0.2
14000 5 4250.2 0.1 0.1 0.2
最后,这是我正在使用的测试代码:
DECLARE @testDuration TABLE (CustID INT, Duration INT)
INSERT INTO @testDuration(CustID, Duration)
VALUES (14005, 65), (14005, 200), (14005, 4800), (14005, 25),
(14005, 5), (14005, 450), (14005, 21), (14005, 32),
(14005, 335), (14005, 45), (14005, 9000), (14005, 250000);
WITH my_cte AS
(
SELECT
d.CustID,
d.Duration,
fm.FrMinsAvailable,
ROUND((fm.FrMinsAvailable-SUM(CAST(d.Duration AS FLOAT) / 60)
OVER (PARTITION BY d.CustID ORDER BY d.Duration DESC)), 1) NewAvMins,
ROUND((CAST(d.Duration AS FLOAT) / 60), 1) BillMins
FROM
(SELECT '14000' CustID, '4250.2' FrMinsAvailable) fm
INNER JOIN
@testDuration ON fm.CustID = d.SerialNoID
GROUP BY
d.CustID, d.Duration, fm.FrMinsAvailable
)
SELECT
my_cte.*,
(LAG(my_cte.NewAvMins) OVER (PARTITION BY my_cte.CustID ORDER BY my_cte.Duration DESC)) PrevAvMins
FROM
my_cte
我最终打算做的是使用这些结果来设置一个值,该值允许客户获得MinutesBilled< = PrevAvMins的该通话的免费分钟数。如果您问我为什么不创建一堆表来实现这一目标,那么我真的想通过最小化读取来减少运行此过程的服务器上的负载并写道,因为处理每天收到的所有记录的整个过程已涉及大量记录,需要几个小时才能完成,服务器还有其他程序要运行。
解决方案不必涉及窗口函数和CTE,但这是我提出的最佳解决方案。我非常感谢一些好的反馈! :)
谢谢!
P.S。我正在使用SQL Server 2014。
答案 0 :(得分:0)
您需要使用SUM OVER()
来获得累积总数,请参阅下面的代码处理:
DECLARE @testDuration TABLE (CustID INT, Duration decimal (18,0))
INSERT INTO @testDuration(CustID, Duration)
VALUES
(14005, 65)
, (14005, 200)
, (14005, 4800)
, (14005, 25)
, (14005, 5)
, (14005, 450)
, (14005, 21)
, (14005, 32)
, (14005, 335)
, (14005, 45)
, (14005, 9000)
, (14005, 250000);
if object_id('tempdb..#callRecords') is not null
drop table #callRecords;
select td.CustID, Duration,4250.2 as FrAvailableMinutes
into #callRecords
from @testDuration as td;
with cte as (
select cr.CustID
, cr.Duration
, cr.FrAvailableMinutes
, row_number() over (partition by cr.CustID order by duration asc) as CallDateKey
from #callRecords as cr
)
select cte.CustID
, cte.Duration
, cte.FrAvailableMinutes
, cte.CallDateKey
, round(sum(Duration) over (order by CallDateKey rows between unbounded preceding and current row)/60,2) as CumulativeDUration
, cte.FrAvailableMinutes - round(sum(Duration) over (order by CallDateKey rows between unbounded preceding and current row)/60,2) as MinutesLeft
from cte
order by CallDateKey desc
修改:在下面的评论中,我还为您的业务需求提供了循环解决方案:
declare @testDuration table
(
CustID int
, Duration decimal(18, 0)
, CallDateKey int
);
insert into @testDuration
(
CustID
, Duration
, CallDateKey
)
values
(14005, 65, 1)
, (14005, 200, 2)
, (14005, 4800, 3)
, (14005, 25, 4)
, (14005, 5, 5)
, (14005, 450, 6)
, (14005, 21, 7)
, (14005, 32, 8)
, (14005, 335, 9)
, (14005, 45, 10)
, (14005, 9000, 11)
, (14005, 250000, 12)
, (14005, 500, 13);
if object_id('tempdb..#Billing') is not null
drop table #Billing;
create table #Billing
(
CustId int
, Duration decimal(18, 0)
, FrAvailableSeconds decimal(18, 2)
, CallDateKey int
, CumulativeDuration decimal(18, 2)
, SecondsLeft decimal(18, 2)
);
with CallRecords
(CustID, DUration, CallDateKey, FrAvailableSeconds)
as (
select td.CustID
, Duration
, td.CallDateKey
, 4250.20 * 60
from @testDuration as td
)
insert into #Billing
(
CustId
, Duration
, FrAvailableSeconds
, CallDateKey
, CumulativeDuration
, SecondsLeft
)
select cr.CustID
, cr.DUration
, cr.FrAvailableSeconds
, cr.CallDateKey
, round( sum(DUration) over (order by
CallDateKey
rows between unbounded preceding and current row
) / 60
, 2
)
, cr.FrAvailableSeconds
from CallRecords as cr;
declare @a int = (
select min(CustId) from #Billing as b
);
declare @b int = (
select max(CustId) from #Billing as b
);
declare @x int;
declare @y int;
while @a <= @b
begin
set @x = (
select min(b.CallDateKey) from #Billing as b where b.CustId = @a
);
set @y = (
select max(b.CallDateKey) from #Billing as b where b.CustId = @a
);
while @x <= @y
begin
update b
set b.SecondsLeft = case
when isnull(ps.SecondsLeft, b.FrAvailableSeconds) - b.Duration < 0 then
isnull(ps.SecondsLeft, b.FrAvailableSeconds)
else
isnull(ps.SecondsLeft, b.FrAvailableSeconds) - b.Duration
end
from #Billing as b
left join (
select b.CustId
, SecondsLeft
from #Billing as b
where
b.CallDateKey = @x - 1
and b.CustId = @a
) as ps
on b.CustId = ps.CustId
where
b.CustId = @a
and b.CallDateKey = @x;
set @x += 1;
end;
set @a += 1;
end;
select b.CustId
, b.Duration
, b.FrAvailableSeconds
, b.FrAvailableSeconds / 60 as FrAvailableMinutes
, b.CallDateKey
, b.CumulativeDuration
, b.SecondsLeft
, b.SecondsLeft / 60 as MinutesLeft
from #Billing as b
order by
b.CallDateKey desc;