3

我的表的表示:

CREATE TABLE Sales 
    (
     id int identity primary key, 
     SaleAmount numeric(10,2)
    );

DECLARE @i INT;
SELECT @i = 1;
SET NOCOUNT ON
WHILE @i <= 100
BEGIN
    INSERT INTO Sales VALUES (ABS(CHECKSUM(NEWID()))/10000000.0 );
    SELECT @i = @i + 1;
END;
SET NOCOUNT OFF

我需要对我的表进行排序SalesSaleAmount然后选择运行总数SaleAmount不大于 X的所有记录。

为此,我目前正在使用一个临时表来首先对记录进行排序,然后选择运行总计小于或等于 X 的记录(在此示例中为 10)。

CREATE TABLE #TEMP_TABLE 
    (
      ID integer IDENTITY PRIMARY KEY, 
      SaleAmount numeric(10,2)
    );

INSERT INTO #TEMP_TABLE 
(SaleAmount)
SELECT SaleAmount FROM Sales
ORDER BY SaleAmount

SELECT * FROM
  (SELECT
      Id,
      SaleAmount,
      (SaleAmount+COALESCE((SELECT SUM(SaleAmount)
          FROM #TEMP_TABLE b
          WHERE b.Id < a.Id),0))
          AS RunningTotal
    FROM #TEMP_TABLE a) InnerTable
WHERE RunningTotal <= 10

有没有一种方法可以让我在不使用临时表的情况下先订购我Sales的桌子?

4

5 回答 5

3

如果您使用的是 SQL Server 2012,那么您可以只使用窗口函数进行累积和:

select s.*,
       sum(SaleAmount) over (order by id) as RunningTotal
from Sales s

这等效于以下相关子查询:

select s.*,
       (select sum(SalesAmount) from sales s2 where s2.id <= s.id) as RunningTotal
from Sales s
于 2013-03-04T14:34:49.803 回答
1

遵循 Aaron Bertrand 关于使用游标方法的建议:

DECLARE @st TABLE
(
    Id       Int PRIMARY KEY,
    SaleAmount  Numeric(10,2),
    RunningTotal Numeric(10,2)
);

DECLARE
    @Id         INT,
    @SaleAmount  Numeric(10,2),
    @RunningTotal Numeric(10,2) = 0;

DECLARE c CURSOR
    LOCAL STATIC FORWARD_ONLY READ_ONLY
    FOR
    SELECT id, SaleAmount
      FROM Sales
      ORDER BY SaleAmount;

OPEN c;

FETCH NEXT FROM c INTO @Id, @SaleAmount;

WHILE @@FETCH_STATUS = 0
BEGIN
    SET @RunningTotal = @RunningTotal + @SaleAmount;

    INSERT @st(Id, SaleAmount,  RunningTotal)
        SELECT @Id, @SaleAmount, @RunningTotal;

    FETCH NEXT FROM c INTO @Id, @SaleAmount;
END

CLOSE c;
DEALLOCATE c;

SELECT Id, SaleAmount, RunningTotal
    FROM @st
    WHERE RunningTotal<=10
    ORDER BY SaleAmount;

这是代码的增加,仍然需要一个表变量。但是,性能的提高是显着的。

归功于 Aaron Bertrand写的关于运行总计的出色文章

于 2013-03-04T16:17:00.127 回答
1

CTE、ROW_NUMBER()排名函数和APPLY()运算符的另一种选择

  ;WITH cte AS
   (
    SELECT ROW_NUMBER() OVER(ORDER BY SaleAmount) AS rn, SaleAmount
    FROM Sales s  
    )
    SELECT *
    FROM cte c CROSS APPLY (
                            SELECT SUM(s2.SaleAmount) AS RunningTotal
                            FROM Sales s2
                            WHERE c.SaleAmount >= s2.SaleAmount
                            ) o
    WHERE o.RunningTotal <= 10     

仅供参考,为了避免排序操作,您可以使用此索引:

CREATE INDEX ix_SaleAmount_Sales ON Sales(SaleAmount)

在此处输入图像描述

于 2013-03-05T22:06:31.077 回答
0

首先,您正在执行子选择,然后从子选择中执行 select *。这是不必要的。

SELECT
  Id,
  SaleAmount,
  (SaleAmount+COALESCE((SELECT SUM(SaleAmount)
      FROM #TEMP_TABLE b
      WHERE b.Id < a.Id),0))
      AS RunningTotal
FROM #TEMP_TABLE
WHERE RunningTotal <= 10

现在,临时表只是对 Sales 表的查询。对临时表进行排序是没有意义的,因为根据 SQL 的规则,不必遵守临时表中的顺序,只需要外部查询上的 order by 子句,所以

SELECT
  Id,
  SaleAmount,
  (SaleAmount+COALESCE((SELECT SUM(SaleAmount)
      FROM Sales b
      WHERE b.Id < a.Id),0))
      AS RunningTotal
FROM Sales
WHERE RunningTotal <= 10
于 2013-03-04T14:31:18.473 回答
0

经过一番研究,我相信你的目标是不可能的,除非使用 SS2012 或 Oracle。

由于您的解决方案似乎有效,我建议使用表变量而不是模式表:

DECLARE @TEMP_TABLE TABLE (
    ID integer IDENTITY PRIMARY KEY,
    SaleAmount numeric(10,2) 
);

INSERT INTO @TEMP_TABLE 
(SaleAmount)
SELECT SaleAmount FROM Sales
ORDER BY SaleAmount

SELECT * FROM
  (SELECT
      Id,
      SaleAmount,
      (SaleAmount+COALESCE((SELECT SUM(SaleAmount)
          FROM @TEMP_TABLE b
          WHERE b.Id < a.Id),0))
          AS RunningTotal
    FROM @TEMP_TABLE a) InnerTable
WHERE RunningTotal <= 10

在并行测试时,我发现了一些性能改进。

于 2013-03-04T14:45:56.247 回答