选择运行总计,直到达到特定 SUM

Posted

技术标签:

【中文标题】选择运行总计,直到达到特定 SUM【英文标题】:Select running total until specific SUM is reached 【发布时间】:2013-01-08 15:20:16 【问题描述】:

我正在尝试从下表变量中选择前 n 个 rowid 值,这将使我接近 200,000 的 sum(itemcount) 而不会超过该阈值。如果我手动查看这个,我会选择前 3 行。除非没有基于纯集的方式,否则我不想使用游标。

什么是基于集合的好方法来获取所有 rowid 值“总和而/直到”我达到 200,000 的运行总数?

我在http://www.1keydata.com/sql/sql-running-totals.html 上查看了“运行总计”,但这似乎不会奏效,因为实际表有大约 50 万行。

这是我迄今为止尝试过的:

declare  @agestuff table ( rowid int primary key , itemcount int , itemage datetime )
insert into @agestuff values ( 1 , 175000 , '2013-01-24 17:21:40' )
insert into @agestuff values ( 2 , 300    , '2013-01-24 17:22:11' )
insert into @agestuff values ( 3 , 10000 , '2013-01-24 17:22:11' )
insert into @agestuff values ( 4 , 19000 , '2013-01-24 17:22:19' )
insert into @agestuff values ( 5 , 16000 , '2013-01-24 17:22:22' )
insert into @agestuff values ( 6 , 400   , '2013-01-24 17:23:06' )
insert into @agestuff values ( 7 , 25000 , '2013-01-24 17:23:06' )

select sum(itemcount) from @agestuff  -- 245700 which is too many

select sum(itemcount) from @agestuff  
  where rowid in (1,2,3) -- 185300 which gets me as close as possible

使用 SQL Server 2008。如果需要,我将切换到 2012。

【问题讨论】:

【参考方案1】:

窗口函数 - 仅限 SQL Server 2012

DECLARE @point INT = 200000;

;WITH x(rowid, ic, r, s) AS
(
  SELECT
    rowid, itemcount, ROW_NUMBER() OVER (ORDER BY itemage, rowid),
    SUM(itemcount) OVER (ORDER BY [itemage], rowid RANGE UNBOUNDED PRECEDING)
  FROM @agestuff
)
SELECT x.rowid, x.ic, x.s
FROM x WHERE x.s <= @point
ORDER BY x.rowid; 

结果:

rowid  ic      sum   
-----  ------  ------
1      175000  175000
2      300     175300
3      10000   185300

SQL fiddle demo

如果由于某种原因您不能使用 SQL Server 2012,那么在 SQL Server 2008 上,您可以使用几个替代方案:


古怪的更新

请注意,此行为未记录在案,也不保证以正确的顺序计算您的运行总计。因此,请自行承担使用风险。

DECLARE @st TABLE
(
    rowid INT PRIMARY KEY,
    itemcount INT,
    s INT
);
 
DECLARE @RunningTotal INT = 0;
 
INSERT @st(rowid, itemcount, s)
  SELECT rowid, itemcount, 0
    FROM @agestuff
    ORDER BY rowid;
 
UPDATE @st
  SET @RunningTotal = s = @RunningTotal + itemcount
  FROM @st;
 
SELECT rowid, itemcount, s
  FROM @st
  WHERE s < @point
  ORDER BY rowid;

光标

DECLARE @st TABLE
(
  rowid INT PRIMARY KEY, itemcount INT, s INT
);
 
DECLARE
  @rowid INT, @itemcount INT, @RunningTotal INT = 0;
 
DECLARE c CURSOR LOCAL FAST_FORWARD
  FOR SELECT rowid, itemcount
    FROM @agestuff ORDER BY rowid;
 
OPEN c;
 
FETCH c INTO @rowid, @itemcount;
 
WHILE @@FETCH_STATUS = 0
BEGIN
    SET @RunningTotal = @RunningTotal + @itemcount;

    IF @RunningTotal > @point
      BREAK;
 
    INSERT @st(rowid, itemcount, s)
      SELECT @rowid, @itemcount, @RunningTotal;
 
    FETCH c INTO @rowid, @itemcount;
END
 
CLOSE c;
DEALLOCATE c;
 
SELECT rowid, itemcount, s
  FROM @st
  ORDER BY rowid;

我只选择了两个备选方案,因为其他备选方案更不受欢迎(主要是从性能角度来看)。您可以在以下博客文章中看到它们,其中包含有关它们如何执行的一些背景信息以及有关潜在问题的更多信息。不要把自己画到一个角落,因为你坚持认为光标不好 - 有时,就像在这种情况下,它们可能是最有效的支持和可靠的选择:

http://www.sqlperformance.com/2012/07/t-sql-queries/running-totals

【讨论】:

以上是关于选择运行总计,直到达到特定 SUM的主要内容,如果未能解决你的问题,请参考以下文章

sql运行总计

Postgresql 选择直到达到一定的总量并锁定

当数字达到X.60时,将细胞向上舍入

获取每个项目的计算(美元货币转换)总计

如何在其运行总计达到零的帐户上应用状态

需要 SQL 来选择行,直到列的总和达到最后一行不会完全消耗值的值