代码之家  ›  专栏  ›  技术社区  ›  Herb Caudill

改进SQL查询:随时间累积的金额

  •  2
  • Herb Caudill  · 技术社区  · 16 年前

    假设我有一个SQL奖励表,其中包含日期和金额字段。我需要生成一个带有连续日期序列、每天奖励的金额和运行(累计)总数的表。

    Date         Amount_Total   Amount_RunningTotal
    ----------   ------------   -------------------
    1/1/2010              100                   100
    1/2/2010              300                   400
    1/3/2010                0                   400
    1/4/2010                0                   400
    1/5/2010              400                   800
    1/6/2010              100                   900
    1/7/2010              500                  1400
    1/8/2010              300                  1700
    

    此SQL有效,但速度不如我希望的快:

    Declare @StartDate datetime, @EndDate datetime 
    Select @StartDate=Min(Date), @EndDate=Max(Date) from Awards 
    
    ; With 
    
    /* Returns consecutive from numbers 1 through the 
    number of days for which we have data */
    Nbrs(n) as (
       Select 1 Union All 
       Select 1+n 
       From Nbrs 
       Where n<=DateDiff(d,@StartDate,@EndDate)),
    
    /* Returns all dates @StartDate to @EndDate */
    AllDays as (
       Select Date=DateAdd(d, n, @StartDate) 
       From Nbrs ) 
    
    /* Returns totals for each day */
    Select 
     d.Date,
     Amount_Total = (
            Select Sum(a.Amount) 
            From Awards a 
            Where a.Date=d.Date),
     Amount_RunningTotal = (
            Select Sum(a.Amount) 
            From Awards a 
            Where a.Date<=d.Date)
    From AllDays d
    Order by d.Date 
    Option(MAXRECURSION 1000)
    

    我试着给颁奖典礼添加一个索引。日期,但差别很小。

    在我使用其他策略(如缓存)之前,是否有更有效的方法对正在运行的总计算进行编码?

    3 回复  |  直到 16 年前
        1
  •  3
  •   Community Mohan Dere    9 年前

    我通常使用临时表:

    DECLARE @Temp TABLE
    (
        [Date] date PRIMARY KEY,
        Amount int NOT NULL,
        RunningTotal int NULL
    )
    
    INSERT @Temp ([Date], Amount)
        SELECT [Date], Amount
        FROM ...
    
    DECLARE @RunningTotal int
    
    UPDATE @Temp
    SET @RunningTotal = RunningTotal = @RunningTotal + Amount
    
    SELECT * FROM @Temp
    

    如果不能使日期列成为主键,则需要包括 ORDER BY [Date] INSERT 语句。

    另外,这个问题以前也被问过几次。见 here 或搜索“SQL运行总数”。据我所知,我发布的解决方案仍然是性能最好、而且易于编写的解决方案。

        2
  •  0
  •   Nick Craver    16 年前

    我前面没有数据库设置,所以我希望下面的第一步可以工作。这样的模式将导致更快的查询…您只需加入两次,聚合量相同:

    Declare @StartDate datetime, @EndDate datetime 
    Select @StartDate=Min(Date), @EndDate=Max(Date) from Awards 
    ; 
    WITH AllDays(Date) AS (SELECT @StartDate UNION ALL SELECT DATEADD(d, 1, Date) 
                           FROM AllDays 
                           WHERE Date < @EndDate)
    
    SELECT d.Date, sum(day.Amount) Amount_Total, sum(running.Amount) Amount_RunningTotal
    FROM AllDays d  
         LEFT JOIN (SELECT date, SUM(Amount) As Amount
                    FROM Awards 
                    GROUP BY Date) day
              ON d.Date = day.Date
         LEFT JOIN (SELECT date, SUM(Amount) As Amount
                    FROM Awards 
                    GROUP BY Date) running 
                    ON (d.Date >= running.Date)
    Group by d.Date
    Order by d.Date 
    

    注意:我在顶部更改了您的表表达式,它将在前一天删除……如果这是有意的,只需在上面加上一个WHERE子句来排除它。如果这不起作用或不合适,请在评论中告诉我,我会做任何调整。

        3
  •  0
  •   Herb Caudill    16 年前

    这是一个基于@aaronaught答案的有效解决方案。在T-SQL中,我唯一需要克服的就是 @RunningTotal 等不能为空(需要转换为零)。

    Declare @StartDate datetime, @EndDate datetime 
    Select @StartDate=Min(StartDate),@EndDate=Max(StartDate) from Awards
    
    /* @AllDays: Contains one row per date from @StartDate to @EndDate */
    Declare @AllDays Table (
        Date datetime Primary Key)
    ; With 
    Nbrs(n) as (
        Select 0 Union All 
        Select 1+n from Nbrs 
        Where n<=DateDiff(d,@StartDate,@EndDate) 
        )
    Insert into @AllDays
    Select Date=DateAdd(d, n, @StartDate) 
    From Nbrs
    Option(MAXRECURSION 10000) /* Will explode if working with more than 10000 days (~27 years) */
    
    /* @AmountsByDate: Contains one row per date for which we have an Award, along with the totals for that date */ 
    Declare @AmountsByDate Table (
        Date datetime Primary Key,
        Amount money)
    Insert into @AmountsByDate
    Select 
        StartDate, 
        Amount=Sum(Amount) 
    from Awards a
    Group by StartDate
    
    /* @Result: Joins @AllDays and @AmountsByDate etc. to provide totals and running totals for every day of the award */
    Declare @Result Table (
        Date datetime Primary Key,
        Amount money,
        RunningTotal money)
    Insert into @Result 
    Select 
        d.Date,
        IsNull(bt.Amount,0),
        RunningTotal=0
    from @AllDays d
    Left Join @AmountsByDate bt on d.Date=bt.Date
    Order by d.Date
    
    Declare @RunningTotal money Set @RunningTotal=0
    Update @Result Set @RunningTotal = RunningTotal = @RunningTotal + Amount
    
    Select * from @Result 
    
    推荐文章