Улучшение SQL-запроса: накопленные суммы с течением времени - PullRequest
2 голосов
/ 10 января 2010

Предположим, у меня есть таблица наград 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)

Я попытался добавить индекс в Awards.Date, но это имело минимальное значение.

Прежде чем я прибегну к другим стратегиям, таким как кэширование, существует ли более эффективный способ кодирования вычисления промежуточного итога?

Ответы [ 3 ]

3 голосов
/ 10 января 2010

Я обычно использую временную таблицу для этого:

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.1006 *

Кроме того, этот вопрос задавался несколько раз раньше.Смотрите здесь или ищите "sql промежуточный итог".Насколько я знаю, решение, которое я опубликовал, по-прежнему обладает наилучшей производительностью, а также легко написано.

0 голосов
/ 10 января 2010

Вот рабочее решение, основанное на ответе @ 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 
0 голосов
/ 10 января 2010

У меня нет настройки базы данных передо мной, поэтому я надеюсь, что нижеприведенное сработает с первого раза. Подобный шаблон должен привести к гораздо более быстрому запросу ... вы просто присоединяетесь дважды, аналогичная сумма агрегации:

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 к нему, чтобы исключить его. Дайте мне знать в комментариях, если это не работает или не подходит, и я внесу любые изменения.

...