Bucket Filling SQL query CTE

帅比萌擦擦* 提交于 2019-12-25 08:09:45

问题


I want to achieve the below output from the given input tables.

Input Table (Bucket to be filled)

ID | FullCapacity | CurrentAmount    
---+--------------+--------------
B1 |     100      |     0    
B2 |      50      |     0
B3 |      70      |     0

Input Table (Filler Table)

        ID | Filler            
        ---+-------
        F1 | 90              
        F2 | 70          
        F3 | 40    
        F4 | 20 

Output table should have below showing filling process.

ID | FullCapacity | CurrentAmount       
---+--------------+--------------
B1 |    100       |    90        
B2 |     50       |     0    
B3 |     70       |     0
---+--------------+--------------
B1 |    100       |   100        
B2 |     50       |    50
B3 |     70       |    10
---+--------------+--------------
B1 |    100       |   100      
B2 |     50       |    50    
B3 |     70       |    50
---+--------------+--------------
B1 |    100       |   100        
B2 |     50       |    50    
B3 |     70       |    70

I am trying to fill this one by one from filler to bucket. Can we do this without using cursor?

Please see that we can have multiple types of buckets for example red bucket, blue bucket and red filler, blue filler. Red filler to go to red bucket, blue filler to blue and so on.

Thank you


回答1:


You can do this in SQL Server 2008 like this:

declare @Buckets table (ID char(2), FullCapacity int)
declare @Filler table (ID char(2), Filler int)

insert into @Buckets 
select 'B1', 100 union all
select 'B2', 50 union all
select 'B3', 70 

insert into @Filler 
select 'F1', 90 union all
select 'F2', 70 union all
select 'F3', 40 union all
select 'F4', 20


select 
    b.ID, 
    b.FullCapacity,
    case 
        when f.TotalFill < b.RunningTotalCapacity then 0
        when f.TotalFill > b.RunningTotalCapacity + b.FullCapacity then b.FullCapacity
        else f.TotalFill - b.RunningTotalCapacity
    end as CurrentAmount
from
(
    select      
    ID,
    Filler,
    (
        select sum(f2.Filler)
        from @Filler as f2
        where f2.ID <= f.ID
    ) as TotalFill
    from @Filler as f
) as f
cross join 
(
    select 
        ID,
        FullCapacity, 
        (
            select isnull(sum(b2.FullCapacity), 0)
            from @Buckets as b2
            where b2.ID < b.ID
        ) as RunningTotalCapacity
    from @Buckets as b
) as b
order by f.ID, b.ID

You can do this using windowing functions like this:

SQL Server 2012+

    declare @Buckets table (ID char(2), FullCapacity int)
    declare @Filler table (ID char(2), Filler int)

    insert into @Buckets values
    ('B1', 100),
    ('B2', 50),
    ('B3', 70)

    insert into @Filler values
    ('F1', 90),
    ('F2', 70),
    ('F3', 40),
    ('F4', 20)

    ;with fillerCte as
    (
        select      
            ID,
            Filler,
            sum(Filler) over (order by ID) as TotalFill
        from @Filler
    ), 
    BucketCte as
    (
        select 
            ID,
            FullCapacity,
            sum(FullCapacity) over (order by ID) - FullCapacity as RunningTotalCapacity
        from @Buckets
    )
    select 
        b.ID, 
        b.FullCapacity,
        case 
            when f.TotalFill < b.RunningTotalCapacity then 0
            when f.TotalFill > b.RunningTotalCapacity + b.FullCapacity then b.FullCapacity
            else f.TotalFill - b.RunningTotalCapacity
        end as CurrentAmount
    from fillerCte as f
    cross join BucketCte as b
    order by f.ID, b.ID



回答2:


All you need to do this are cumulative sums and some joins. Hence you can do this without a cursor. The idea is to use a cumulative join and then assign each filler record to one or more buckets, based on the ranges.

Using the ANSI standard syntax for cumulative sums:

select b.*, f.id,
       (greatest(b.cumecap - b.fullcapacity, f.cumefiller - f.filler) - 
        least(b.cumecap, f.cumefiller)
       ) as amount_in_bucket
from (select b.*,
             sum(b.fullcapacity) over (order by id) as cumecap
      from buckets
     ) b join
     (select f.*,
             sum(f.filler) over (order by id) as cumefiller
      from filler f
     ) f
     on f.cumefiller - f.filler <= b.cumecap and
        f.cumefiller >= b.cumecap - b.fullcapacity;

This produces a mapping of each bucket and the amount of each filler that is in the bucket.

Note: it makes use of the functions greatest() and least(). These are easily replaced by case expressions if the functions are not available.



来源:https://stackoverflow.com/questions/39375726/bucket-filling-sql-query-cte

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!