SQL对行的消耗值

时间:2019-06-26 22:43:20

标签: sql sql-server

我正在编写一个存储过程,该过程将拥有一个表并将其分配到存储桶表(如果它们是同一帐户的一部分)。如果某个库存与该存储桶具有相同的帐户,那么我将用该帐户下的每个库存的数量填充该存储桶,直到a)该存储桶被填充,在这种情况下,移至下一个存储桶b)没有更多的存储留给该帐户,在这种情况下,请转到下一个帐户

对于同一帐户的库存和存储桶,存在三种情况:

  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应该在amountApplied字段中显示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 个答案:

答案 0 :(得分:1)

这很有趣,我不确定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

答案 1 :(得分:1)

这是我使用的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;

这将以累积方式预计算保管箱和铲斗的位置,就像沿着数字线堆叠一样。然后根据询问每个保留范围是否与每个存储桶范围重叠的方式进行联接。

输出中包括其他可能有用的列,这些列描述了如何将货品的每个部分应用于存储桶。

为了娱乐,我played around通过一点可视化的方式。希望对您有所帮助。