T-SQL选择满足汇总条件的范围的所有组合

时间:2018-08-28 01:45:55

标签: sql sql-server tsql join aggregate

每个评论重述的问题

说我们有以下整数id和计数...

id   count
1    0
2    10
3    0
4    0
5    0
6    1
7    9
8    0

我们还有一个变量@id_range int

给定@id_range的值,我们如何才能选择id ranges 的所有组合,而又不使用满足条件的while循环或游标以下标准?

1)组合中不能有两个范围重叠(每个范围的最小值和最大值均包括在内)
2)sum(count)(范围组合必须等于初始数据集的sum(count)(在这种情况下为20)
3)仅包含sum(count)> 0
的范围

最简单的情况是当@id_range = max(id) - min(id)或7给出上述数据时。在这种情况下,只有一种解决方案:

minId   maxId   count
---------------------
1       8       20

但是如果以@id_range = 1为例,将有4种可能的解决方案:

解决方案1:

minId   maxId   count
---------------------
1      2        10
5      6        1
7      8        9

解决方案2:

minId   maxId   count
---------------------
1      2        10
6      7        10

解决方案3:

minId   maxId   count
---------------------
2       3       10
5       6       1
7       8       9

解决方案4:

minId   maxId   count
---------------------
2       3       10
6       7       10

最终目标是确定范围最小的解决方案(解决方案2和4,在上面的示例中,@id_range = 1)。

1 个答案:

答案 0 :(得分:0)

此解决方案未列出所有可能的组合,而只是尝试将其组合到尽可能少的行中。

希望它将涵盖所有可能的情况

-- create the sample table
declare @sample table
(
    id  int,
    [count] int
)

-- insert some sample data
insert into @sample select 1,     0
insert into @sample select 2,    10
insert into @sample select 3,     0
insert into @sample select 4,     0
insert into @sample select 5,     0
insert into @sample select 6,     1
insert into @sample select 7,     9
insert into @sample select 8,     0

-- the @id_range
declare @id_range   int = 1

-- the query
; with
cte as
(
    -- this cte identified those rows with count > 0 and group them together
    -- sign(0) gives 0, sign(+value) gives 1
    -- basically it is same as case when [count] > 0 then 1 else 0 end
    select  *, 
            grp = row_number() over (order by id) 
                - dense_rank() over(order by sign([count]), id)
    from    @sample
),
cte2 as
(
    -- for each grp in cte, assign a sub group (grp2). each sub group 
    -- contains @id_range number of rows
    select  *, 
            grp2 = (row_number() over (partition by grp order by id) - 1) 
                 / (@id_range + 1)
    from    cte
    where   count   > 0
)
select  MinId   = min(id), 
        MaxId   = min(id) + @id_range, 
        [count] = sum(count)
from    cte2
group by grp, grp2