SQL Server: условные транзакции по дебетовым картам - PullRequest
0 голосов
/ 08 июня 2018

Я работаю в телекоммуникационной компании, которая предлагает несколько пакетов для наших клиентов.Некоторые из этих пакетов включают в себя установленное количество бесплатных минут междугородной связи, которые они могут использовать каждый месяц.

В настоящее время в пакетах бесплатных минут есть недостаток, который позволяет клиенту получить гораздо больше бесплатных минут, чем это реально позволяет пакет.В то же время текущая система может иногда препятствовать тому, чтобы клиент мог использовать все свои свободные минуты в правильном порядке.

Я уже выяснил, как изменить процесс, чтобы убедиться, что пакеты правильно назначаются в записи вызовов клиента.Теперь я пытаюсь изменить способ обработки этих записей, чтобы они не могли использовать больше, чем в пакете бесплатных минут.

Я пытаюсь решить эту проблему, используя комбинацию 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.Если вы спрашиваете, почему я не просто создаю кучу таблиц для достижения этой цели, то на самом деле я пытаюсь уменьшить нагрузку на сервер, на котором выполняется этот процесс, путем минимизации операций чтения и записи, поскольку вся процедура для обработки всехЗаписи, получаемые каждый день, уже включают в себя множество записей, на их завершение уходит несколько часов, и на сервере также должны выполняться другие процедуры.</p>

Решение не должно включать оконные функции и CTE, но это было лучшее решение, которое я придумал.Буду очень признателен за хороший отзыв!:)

Спасибо!

PS Я использую SQL Server 2014.

1 Ответ

0 голосов
/ 08 июня 2018

Вам необходимо использовать 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;
...