【问题标题】:Select sum of a column without cursor选择没有光标的列的总和
【发布时间】:2012-07-06 12:45:34
【问题描述】:

我的数据如下:

ID   LENGTH_IN_CM
1        1.0
2        1.0
3        9.0
4        5.0
5        15.0
6        3.0
7        5.0

我知道一个页面有 20 厘米长,我想计算每个项目将在哪些页面上。

例如,id 为 1、2、3 和 4 的项目将位于第一页 (1.0 + 1.0 + 9.0 + 5.0

是否可以不使用光标计算页码?

【问题讨论】:

  • 您是否想要不使用光标来执行此操作,因为您现在正在使用光标并且速度很慢,或者您是否想要不使用光标执行此操作,因为您听说过或读过光标不好?在某些情况下(比如运行总计,可能还有这个),游标是最有效的支持选项。
  • 这不是我唯一需要的,无论如何我都会使用光标,但为此,我应该在没有光标的情况下这样做,所以最后我不会结束最多有3个游标..会有7k行并且有很多规则(当3列(x,y,z)更改时分页重置。所以如果我为此使用游标,我很确定我会需要使用 3 或 4 来做我需要的。
  • 或者也许可以使用同一个游标在所有行中仅循环一次,并在每一步做出多个决策。
  • Mikael,这不太行。将 ID 7 的值从 6 更改为 5,它将 15、3 和 5 放在不应该发生的第 2 页上。
  • 德里克,你的答案是正确的。 40/20 = 2 这应该是第二页的最后一行,而不是第三页。你为什么删除它?

标签: sql-server stored-procedures sql-server-2008-r2


【解决方案1】:

好吧,我这样做更多是为了挑战,而不是因为我认为这是个好主意。我倾向于相信 Aaron 光标可能更合适。总之:

declare @Items table (ID int not null,LENGTH_IN_CM decimal(5,1) not null)
insert into @Items(ID,LENGTH_IN_CM) values
(1,1.0),
(2,1.0),
(3,9.0),
(4,5.0),
(5,15.0),
(6,3.0),
(7,6.0)

;With PossiblePages as (
    select ID as MinID,ID as MaxID,LENGTH_IN_CM as TotalLength from @Items
    union all
    select MinID,MaxID + 1,CONVERT(decimal(5,1),TotalLength + LENGTH_IN_CM)
    from
        PossiblePages pp
            inner join
        @Items it
            on
                pp.MaxID + 1 = it.ID
    where
        TotalLength + LENGTH_IN_CM <= 20.0
), LongPages as (
    select MinID,MAX(MaxID) as MaxID,MAX(TotalLength) as TotalLength from PossiblePages group by MinID
), FinalPages as (
    select MinID,MaxID,TotalLength from LongPages where MinID = 1
    union all
    select lp.MinID,lp.MaxID,lp.TotalLength
    from
        LongPages lp
            inner join
        FinalPages fp
            on
                lp.MinID = fp.MaxID + 1
), PageNumbers as (
    select MinID,MaxID,ROW_NUMBER() OVER (ORDER BY MinID) as PageNo
    from FinalPages
)
select * from PageNumbers

结果:

MinID       MaxID       PageNo
----------- ----------- --------------------
1           4           1
5           6           2
7           7           3

如果您想为每一行分配页码,这应该很容易加入到原始表中。

PossiblePages 计算每个可能的页面 - 对于每一行,它就像该行是该页面上的第一行,然后计算出可以附加多少行,以及该范围的总长度of rows表示(可能有更简洁的方法来计算这个表达式,目前不确定)。

LongPages 然后找到PossiblePages 为每个起始行号计算的最长值。

最后,FinalPages 从第一页开始(从逻辑上讲,它必须是以ID 1 开头的那个 - 如果您不能保证从 1 开始,并且需要找到最早的)。然后,下一页是从比上一行高一个的行 ID 开始的页面。

您不需要PageNumbers,但正如我所说,我正在考虑重新加入原始表格。


正如评论者所预测的那样,我认为这不会很好 - 仅在样本上,我看到至少 4 个表扫描来计算它。


奖励精神错乱。这个只扫描表3次:

;With PageRows as (
    select ID as MinID,ID as MaxID,LENGTH_IN_CM as TotalLength from @Items where ID=1
    union all
    select MinID,MaxID + 1,CONVERT(decimal(5,1),TotalLength + LENGTH_IN_CM)
    from
        PageRows pr
            inner join
        @Items ir
            on
                pr.MaxID = ir.ID-1
    where
        TotalLength + LENGTH_IN_CM <= 20.0
    union all
    select ir.ID as MinID,ir.ID as MaxID,ir.LENGTH_IN_CM as TotalLength
    from
        PageRows pr
            inner join
        @Items ir
            on
                pr.MaxID = ir.ID-1
    where
        TotalLength + LENGTH_IN_CM > 20.0
), PageNumbers as (
    select MinID,MAX(MaxID) as MaxID,ROW_NUMBER() OVER (ORDER BY MinID) as PageNo
    from PageRows
    group by MinID
)
select * from PageNumbers

【讨论】:

  • 勇敢+1。虽然您可以使用
【解决方案2】:

这也有效。告诉我为什么这些方法不能满足您的需求

declare @mytable as table(id int, LENGTH_IN_CM int)

insert into @mytable values
(1,1),
(2,1),
(3,9),
(4,5),
(5,15),
(6,3),
(7,6);


Select t.id ,
(select floor(SUM(LENGTH_IN_CM)/20.0)+1 page from @myTable  where id<=t.id) 
from @mytable t 


id          Page
----------- ---------------------------------------
1           1
2           1
3           1
4           1
5           2
6           2
7           3

(7 row(s) affected)

【讨论】:

  • 让我们以 Aaron 经常添加的评论为例,即将 ID 7 的长度从 6 切换到 5 会报告它在第 2 页上,但它不应该是。这种方法的问题在于它将任何特定页面的大小视为 20+ 以前页面中的任何未使用空间,而不仅仅是 20
猜你喜欢
  • 2018-01-06
  • 1970-01-01
  • 1970-01-01
  • 2013-05-26
  • 2014-12-10
  • 1970-01-01
  • 1970-01-01
  • 2020-06-27
  • 2013-04-03
相关资源
最近更新 更多