SQL потребляет значения по строкам - PullRequest
0 голосов
/ 27 июня 2019

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

Существует три сценария для владений и групп одного и того же счета:

  1. Запасы заполняют ведра равномерно - это сумма всех запасов = сумма всех ведер

  2. Запасы не заполняют все сегменты - в этом случае переходите к следующему аккаунту и игнорируйте оставшиеся сегменты для предыдущего аккаунта

  3. Запасы переполняют сегменты - в этом сценарии мы игнорируем оставшиеся запасы

Каждое владение должно быть помечено, какому ведру он назначен и сколько применяется к каждому ведру. Пример с некоторыми комментариями ниже:

Buckets
----------------------------------------
Bucket     BucketAccount     TotalAmount
1          GB111             30
2          GB111             50
3          GB222             100
4          GB333             150


Holdings                    (before execution)
------------------------------------------------------------------------------
ID       Account       Amount      Bucket      AmountApplied       
1        GB111          50         null            null 
2        GB111          40         null            null
3        GB222          30         null            null
4        GB222          40         null            null    
5        GB333           5         null            null
6        GB333         145         null            null
7        GB333          50         null            null
If(OBJECT_ID('tempdb..#buckets') Is Not Null)
Begin    
    Drop Table #buckets
End

CREATE TABLE #buckets         
(
    Bucket int,
    BucketAccount nvarchar(10),
    TotalAmount Decimal
);

insert into #buckets values 
    (1, 'GB111', 30),
    (2, 'GB111', 50),
    (3, 'GB222', 100),
    (4, 'GB333', 150)



If(OBJECT_ID('tempdb..#holdings') Is Not Null)
Begin    
    Drop Table #holdings
End

CREATE TABLE #holdings      
(
    ID int,
    Account nvarchar(10),
    Amount decimal,
    Bucket int null,
    TotalAmount decimal null    
);

insert into #holdings (ID, Account, Amount, Bucket, TotalAmount) 
values
    (1, 'GB111', 50, null, null),
    (2, 'GB111', 40, null, null),
    (3, 'GB222', 30, null, null),
    (4, 'GB222', 40, null, null),
    (5, 'GB333', 5, null, null),
    (6, 'GB333', 145, null, null),
    (7, 'GB333', 50, null, null)


select *
from
    (select 
        hold.Account,   maxIds.ID as SubTotalId, sum(hold.Amount) as PartAmount
    from #holdings hold
        inner join #holdings maxIds
            on hold.Account = maxIds.Account
            and hold.Id <= maxIds.ID            
    group by hold.Account, maxIds.Id) partHoldings

    right join 
        (select buckets.BucketAccount, subBuckets.Bucket, sum(buckets.TotalAmount) as PartAmount
        from #buckets buckets
            inner join #buckets subBuckets 
                on buckets.BucketAccount = subBuckets.BucketAccount
                and buckets.Bucket <= subBuckets.Bucket
        group by buckets.BucketAccount, subBuckets.Bucket) partBuckets

        on partHoldings.Account = partBuckets.BucketAccount
        and partHoldings.PartAmount >= partBuckets.PartAmount


select 
    -- * ,
    BucketAccount, Bucket, ID as holdingId,
    case 
        when MinHoldingCoveringBucket < Id and Id < MaxHoldingCoveringBucket then Amount
        when MinHoldingCoveringBucket = Id and Id = MaxHoldingCoveringBucket then PartAmount - prevTotalPartAmount
        when MinHoldingCoveringBucket = Id and Id <> MaxHoldingCoveringBucket then holdPartAmount - prevTotalPartAmount
        when MinHoldingCoveringBucket <> Id and Id = MaxHoldingCoveringBucket then PartAmount - holdPrevPartAmount
        else null 
    end as AmountApplied
from
    (select 
        holdingsBuckets.BucketAccount, holdingsBuckets.Bucket, holdingsBuckets.PartAmount, holdingsBuckets.prevTotalPartAmount  
        , IsNull(MinHoldingCoveringBucket, minAccountHoldingId) as MinHoldingCoveringBucket
        , IsNull(MaxHoldingCoveringBucket, maxAccountHoldingId) as MaxHoldingCoveringBucket

        , hold.ID, hold.Amount
        , partHoldings.PartAmount as holdPartAmount
        , partHoldings.prevPartAmount as holdPrevPartAmount
    from
        (select     
            topLimits.*
            , min(botLimits.SubTotalId) as MinHoldingCoveringBucket
        from
            (select 
                partBuckets.*   
                , min(partHoldings.SubTotalId) as MaxHoldingCoveringBucket  
            from
                (select subBuckets.BucketAccount, subBuckets.Bucket, sum(buckets.TotalAmount) as PartAmount, sum(buckets.TotalAmount) - subBuckets.TotalAmount as prevTotalPartAmount
                from #buckets buckets
                    inner join #buckets subBuckets 
                        on buckets.BucketAccount = subBuckets.BucketAccount
                        and buckets.Bucket <= subBuckets.Bucket
                group by subBuckets.BucketAccount, subBuckets.Bucket, subBuckets.TotalAmount) partBuckets

                left join 
                    (select 
                        hold.Account,   maxIds.ID as SubTotalId, sum(hold.Amount) as PartAmount
                    from #holdings hold
                        inner join #holdings maxIds
                            on hold.Account = maxIds.Account
                            and hold.Id <= maxIds.ID            
                    group by hold.Account, maxIds.Id) partHoldings

                on partHoldings.Account = partBuckets.BucketAccount
                and partHoldings.PartAmount >= partBuckets.PartAmount


                left join 
                    (select 
                        hold.Account,   maxIds.ID as SubTotalId, sum(hold.Amount) as PartAmount
                    from #holdings hold
                        inner join #holdings maxIds
                            on hold.Account = maxIds.Account
                            and hold.Id <= maxIds.ID            
                    group by hold.Account, maxIds.Id) partHoldings2

                on partBuckets.BucketAccount = partHoldings2.Account
                and partHoldings.SubTotalId >= partHoldings2.SubTotalId
                and partHoldings2.PartAmount > partBuckets.prevTotalPartAmount  

            group by partBuckets.BucketAccount, partBuckets.Bucket, partBuckets.PartAmount, partBuckets.prevTotalPartAmount) topLimits

            left join 
                (select 
                    hold.Account,   maxIds.ID as SubTotalId, sum(hold.Amount) as PartAmount
                from #holdings hold
                    inner join #holdings maxIds
                        on hold.Account = maxIds.Account
                        and hold.Id <= maxIds.ID            
                group by hold.Account, maxIds.Id) botLimits

                on topLimits.BucketAccount = botLimits.Account
                and botLimits.PartAmount > topLimits.prevTotalPartAmount
                and botLimits.SubTotalId < topLimits.MaxHoldingCoveringBucket

        group by topLimits.BucketAccount, topLimits.Bucket, topLimits.PartAmount, topLimits.prevTotalPartAmount, topLimits.MaxHoldingCoveringBucket) holdingsBuckets

        inner join 
            (select Account, min(Id) as minAccountHoldingId, max(id) as maxAccountHoldingId
            from #holdings
            group by Account) edgeAccountHoldings

            on holdingsBuckets.BucketAccount = edgeAccountHoldings.Account

        right join #holdings hold
            on holdingsBuckets.BucketAccount = hold.Account
            and IsNull(MinHoldingCoveringBucket, minAccountHoldingId) <= hold.ID
            and hold.ID <= IsNull(MaxHoldingCoveringBucket, maxAccountHoldingId)

        left join 
            (
            select 
                hold.Account,   maxIds.ID as SubTotalId, sum(hold.Amount) as PartAmount, sum(hold.Amount) - maxIds.Amount as prevPartAmount
            from #holdings hold
                inner join #holdings maxIds
                    on hold.Account = maxIds.Account
                    and hold.Id <= maxIds.ID            
            group by hold.Account, maxIds.Id, maxIds.Amount
            ) partHoldings

            on partHoldings.Account = holdingsBuckets.BucketAccount
            and hold.ID = partHoldings.SubTotalId) selectionData

После исполнения:

HoldingId 5 должен показывать 40 в поле суммаПрименено вместо 70. Мы применили 30 от первого удержания, а затем 40 применили от второго к сумме 70.

Holdings                  
--------------------------------------------------------------------------------------------------------------------------------------------
ID       Account       Amount      Bucket      AmountApplied      Comments    
1        GB111          50           1              30          Applied 30. Bucket 1 is filled with 20 leftover, move to next bucket of same account
2        GB111          20           2              20          Insert new record. Applied 20 (from leftover in Bucket 1), and there is 30 leftover to cover in Bucket 2
3        GB111          40           2              30          Applied 30, 10 leftover in Bucket 2. We are out of holdings for this account, move on to next account
4        GB222          30           3              30          Applied 30, 70 leftover in Bucket 3
5        GB222          40           3            **70**        Applied 40, 30 leftover in bucket 3. Bucket is not filled and we are out of holdings for this account. Move on to next account 
6        GB333           5           4               5          Applied 5, 145 leftover in Bucket 4      
7        GB333         145           4             145          Applied 145, Bucket 4 is filled with 0 leftover, move on to next account
8        GB333          50          null          null          Skip as Bucket 4 is already filled

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

Ответы [ 2 ]

1 голос
/ 27 июня 2019

Вот DDL, который я использовал:

create table Buckets (Bucket int, BucketAccount varchar(5), TotalAmount int);
insert into Buckets (Bucket, BucketAccount, TotalAmount) values
    (1, 'GB111', 30), (2, 'GB111', 50), (3, 'GB222', 100), (4, 'GB333', 150),
    (5, 'GB444', 20), (6, 'GB444', 20), (7, 'GB444', 20);

create table Holdings (ID int, Account varchar(5), Amount int);
insert into Holdings (ID, Account, Amount) values
    (1, 'GB111', 50), (2, 'GB111', 40), (3, 'GB222', 30),
    (4, 'GB222', 40), (5, 'GB333', 100), (6, 'GB333', 250), (7, 'GB333', 50),
    (8, 'GB444', 15), (9, 'GB444', 30), (10, 'GB444', 10);

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

with b as (
    select *,
        sum(TotalAmount) over (partition by BucketAccount order by Bucket) -
          TotalAmount as e,
        sum(TotalAmount) over (partition by BucketAccount order by Bucket) as f,
        sum(TotalAmount) over (partition by BucketAccount) as AccountSize
    from Buckets
), h as (
    select *,
        sum(Amount) over (partition by Account order by ID) - Amount as a,
        sum(Amount) over (partition by Account order by ID) as b
    from Holdings
)
select
    h.Account, b.AccountSize,
    h.ID, h.Amount, b.Bucket, b.TotalAmount as BucketSize,
    case
        when h.a >= b.e and h.b <= b.f then h.Amount
        when h.a <  b.e and h.b <= b.f then h.b - b.e
        else b.f - case when h.a > b.e then h.a else b.e end
    end as AmountApplied,
    case
        when h.a >= b.e and h.b <= b.f then 'Type 1: ' +
               cast(b.f - h.b as char(3)) + ' unfilled'
        when h.a <  b.e and h.b <= b.f then 'Type 2: ' +
               cast(b.f - h.b as char(3)) + ' unfilled'
        else case when h.a > b.e then 'Type 3: ' else 'Type 4: ' end +
               cast(h.b - b.f as char(3)) + ' overflows'
    end as Scenario,
    case when h.a >= b.e and h.b <= b.f then 'No' else 'Yes' end as Spans,
    case when h.b >= b.f then 'Yes' else 'No' end as Depletes
from h inner join b on b.BucketAccount = h.Account and h.a < b.f and h.b > b.e
order by h.Account, h.ID, b.Bucket;

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

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

Также

Ради интереса я поиграл с возможностью немного визуализировать это.Надеюсь, это поможет.

1 голос
/ 27 июня 2019

Это было интересно, и я не уверен на 100%, что делать с отрицательным удержанием - предположим, это увеличивает доступное пространство в корзине ??

В любом случае, это будет сделано для вас, если вышеприведенное верно:

;with row1 as
(
    select ID, Account, Amount, b.Bucket, b.TotalAmount, 
        row_number() over (partition by Account order by ID, b.Bucket) rn
    from #holdings h
    join #buckets b on b.BucketAccount=h.Account
) 
, allocations as
(
    select ID, Account, Amount, Bucket, TotalAmount, 
        convert(decimal,case when Amount<=TotalAmount then Amount else TotalAmount end) as Allocated,
        convert(decimal,case when Amount>=TotalAmount then Amount-TotalAmount else 0.0 end) as HoldingRemaining,
        convert(decimal,case when Amount>=TotalAmount then 0.0 else TotalAmount-Amount end) as BucketRemaining
    from row1 where rn=1
    union all
    select ID, Account, Amount, Bucket, TotalAmount,
        convert(decimal,case when HoldingRemaining<=BucketRemaining then HoldingRemaining else BucketRemaining end) as Allocated,
        convert(decimal,case when HoldingRemaining>=BucketRemaining then HoldingRemaining-BucketRemaining else 0.0 end) as HoldingRemaining,
        convert(decimal,case when HoldingRemaining>=BucketRemaining then 0.0 else BucketRemaining-HoldingRemaining end) as BucketRemaining
    from (
        select h.ID, h.Account, h.Amount, b.Bucket, b.TotalAmount, 
            case when h.ID=a.ID then HoldingRemaining else h.Amount end as HoldingRemaining,
            case when h.Bucket=a.Bucket then BucketRemaining else b.TotalAmount end as BucketRemaining
        from allocations a
        -- Move to next holding if required
        join #holdings h on h.Account=a.Account
            and (
                (HoldingRemaining>0 and h.ID=a.ID)
                or (HoldingRemaining=0 and h.ID=a.ID+1)
            )
        -- Move to next bucket if required
        join #buckets b on b.BucketAccount=a.Account
            and (
                (BucketRemaining>0 and b.Bucket=a.Bucket)
                or (BucketRemaining=0 and b.Bucket=a.Bucket+1)
            )
     ) q
)
select * from allocations order by Account, ID, Bucket

Результат:

ID  Account Amount  Bucket  TotalAmount Allocated   HoldingRemaining    BucketRemaining
1   GB111   50      1       30          30          20                  0
1   GB111   50      2       50          20          0                   30
2   GB111   40      2       50          40          0                   10
3   GB222   30      3       100         30          0                   70
4   GB222   40      3       100         40          0                   60
5   GB333   -100    4       150         -100        0                   250
6   GB333   250     4       150         150         100                 0
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...