Sql server SQL Server:借记卡条件交易

Sql server SQL Server:借记卡条件交易,sql-server,tsql,common-table-expression,window-functions,Sql Server,Tsql,Common Table Expression,Window Functions,我在一家电信公司工作,该公司为我们的客户提供多种套餐。其中一些套餐包括每月提供一定数量的免费长途分钟 目前,免费分钟套餐中存在一个缺陷,允许客户获得比套餐实际允许的更多的免费分钟。同时,当前的系统有时会阻止客户以正确的顺序使用他们所有的空闲时间 我已经知道了如何修改流程,以确保将包正确分配给客户的通话记录。现在,我正试图改变我们处理这些记录的方式,以防止它们使用的时间超过其免费分钟包中的时间 我正在尝试使用CTE、窗口函数和update语句的组合来解决这个问题。其理念是,拥有免费分钟套餐的客户可

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

目前,免费分钟套餐中存在一个缺陷,允许客户获得比套餐实际允许的更多的免费分钟。同时,当前的系统有时会阻止客户以正确的顺序使用他们所有的空闲时间

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

我正在尝试使用CTE、窗口函数和update语句的组合来解决这个问题。其理念是,拥有免费分钟套餐的客户可以像借记卡一样使用该套餐,这样,如果通话持续时间少于他们剩余的分钟数,它将覆盖通话,并从可用的免费分钟数中减去剩余的分钟数;但如果通话持续时间超过可用的空闲分钟数,则不包括该通话。我几乎已经把它全部弄明白了,但是我被卡住了,因为我无法弄清楚如何查找由窗口函数在前一行计算的值

下面是我使用测试表得到的结果示例注意:持续时间以秒为单位,因此它被转换为分钟:

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
我最终打算做的是使用这些结果设置一个值,允许客户获得该通话的免费分钟数,如果分钟数计费您需要使用总和来获得累计总数,请参阅下面的代码工作:

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;

选择“14005”CustID,“4250.2”frmins可用,也可以在fm.CustID=d.CustID上查看超前和滞后函数:@TJB我在代码末尾使用滞后来获取PrevAvMins。我可以在CTE中使用它,但问题是它显然无法访问由窗口函数计算的字段;例如,它不允许我在分区上按d执行LAGNewAvMins,CustID ORDER按d执行。Duration DESC。您知道我可以通过什么方式获得LAG来访问该字段吗?@edze实际上,那里的SELECT语句实际上替换了我使用的表名。我这样做是为了有人可以简单地复制粘贴我的代码,让它在他们的端工作,而不需要表格。lolAp0ologies所以你有,我已经发布了一个解决方案,作为你的答案,使用的总和超过这并没有完全完成我所寻找的。问题是,当通话持续时间小于或等于剩余分钟数时,我只需要从剩余分钟数中减去通话持续时间。如果通话持续时间大于剩余分钟数,它应该等于之前计算的剩余分钟数基本上,剩余分钟数不会改变,或者我需要能够从窗口计算中排除该记录。数据集有多大,因为您可能正在为此寻找某种循环?我添加了一个循环解决方案。目前,我们在大多数时间内处理大约500000到700000条记录。不过,我认为您可能是对的-通过脚本的这一部分处理它们的最佳方法可能是简单地通过一个代码块循环每个记录。我希望我能以更好的方式解决它。我担心循环会对性能产生很大影响,因为它涉及的I/O活动比窗口函数多得多,但我们可能别无选择。您将无法使用窗口函数,因为它不根据前一行上下文的结果计算,而是在自己的行上下文中计算,因此,循环是必要的,您可以在我编辑的答案中尝试循环。