SQL Server:借记卡条件交易

时间:2018-06-08 14:04:43

标签: sql-server tsql common-table-expression window-functions

我为一家为我们的客户提供多种套餐的电信公司工作。其中一些套餐包括每月可以使用的一定数量的免费长途分钟。

目前,免费分钟套餐存在缺陷,允许客户获得比套餐真正允许的更多免费分钟。同时,当前系统有时可以防止客户以正确的顺序用尽所有免费分钟。

我已经想出如何修改流程以确保将包正确分配给客户的通话记录。现在,我试图改变我们处理这些记录的方式,以防止他们使用超过他们的免费分钟包。

我尝试使用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。

1 个答案:

答案 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;