Как рассчитать промежуточную сумму в SQL без использования курсора? - PullRequest
24 голосов
/ 20 июля 2009

Я опускаю все настройки курсора и SELECT из временной таблицы для краткости.По сути, этот код вычисляет текущий баланс для всех транзакций на транзакцию.

WHILE @@fetch_status = 0
BEGIN

    set @balance = @balance+@amount

    insert into @tblArTran values ( --from artran table
                @artranid, @trandate, @type, 
                @checkNumber, @refNumber,@custid,
                @amount, @taxAmount, @balance, @postedflag, @modifieddate )


    FETCH NEXT FROM artranCursor into 
            @artranid, @trandate, @type, @checkNumber, @refNumber,
            @amount, @taxAmount,@postedFlag,@custid, @modifieddate

END

Вдохновленный этим кодом из ответа на другой вопрос,

SELECT @nvcConcatenated = @nvcConcatenated + C.CompanyName + ', '
FROM tblCompany C
WHERE C.CompanyID IN (1,2,3)

Мне было интересно, имел ли SQLспособность суммировать числа таким же образом, как это объединяет строки, если вы понимаете мое значение.То есть создать «текущий баланс» для каждой строки без использования курсора.

Возможно ли это?

Ответы [ 11 ]

20 голосов
/ 20 июля 2009

Возможно, вы захотите взглянуть на обновление решения локальной переменной здесь: http://geekswithblogs.net/Rhames/archive/2008/10/28/calculating-running-totals-in-sql-server-2005---the-optimal.aspx

DECLARE @SalesTbl TABLE (DayCount smallint, Sales money, RunningTotal money)

DECLARE @RunningTotal money

SET @RunningTotal = 0

INSERT INTO @SalesTbl 
SELECT DayCount, Sales, null
FROM Sales
ORDER BY DayCount

UPDATE @SalesTbl
SET @RunningTotal = RunningTotal = @RunningTotal + Sales
FROM @SalesTbl

SELECT * FROM @SalesTbl

Превосходит все другие методы, но имеет некоторые сомнения относительно гарантированного порядка строк. Кажется, работает нормально, когда временная таблица проиндексирована, хотя ..

  • Вложенный подзапрос 9300 мс
  • Самостоятельное соединение 6100 мс
  • Курсор 400 мс
  • Обновление локальной переменной 140 мс
10 голосов
/ 20 июля 2009

SQL может создавать промежуточные итоги без использования курсоров, но это один из немногих случаев, когда курсор на самом деле более производительный, чем решение на основе множеств (учитывая операторы, доступные в настоящее время в SQL Server). Кроме того, функция CLR может иногда хорошо светить. Ицик Бен-Ган провел отличную серию в журнале SQL Server по работе с агрегатами. Серия завершилась в прошлом месяце, но вы можете получить доступ ко всем статьям, если у вас есть онлайн-подписка.

Изменить: вот его последняя статья в серии (SQL CLR). Учитывая, что вы можете получить доступ ко всей серии, купив онлайновый месячный абонемент на один месяц - менее 6 долларов - это того стоит, если вы заинтересованы в рассмотрении проблемы со всех сторон. Ицик является Microsoft MVP и очень ярким TSQL-кодером.

8 голосов
/ 20 июля 2009

В Oracle и PostgreSQL 8.4 вы можете использовать оконные функции:

SELECT  SUM(value) OVER (ORDER BY id)
FROM    mytable

В MySQL вы можете использовать переменную сеанса для той же цели:

SELECT  @sum := @sum + value
FROM    (
        SELECT  @sum := 0
        ) vars, mytable
ORDER BY
        id

В SQL Server это редкий пример задачи, для которой курсор является предпочтительным решением.

4 голосов
/ 17 апреля 2014

В SQL Server 2012 и выше вы можете просто использовать оконную функцию Sum непосредственно для исходной таблицы:

SELECT
   artranid,
   trandate,
   type,
   checkNumber,
   refNumber,
   custid,
   amount,
   taxAmount,
   Balance = Sum(amount) OVER (ORDER BY trandate ROWS UNBOUNDED PRECEDING),
   postedflag,
   modifieddate
FROM
   dbo.Sales
;

Это будет очень хорошо работать по сравнению со всеми решениями и не будет иметь ошибок, как указано в "причудливом обновлении".

Обратите внимание, что вы должны использовать версию ROWS, когда это возможно; версия RANGE может работать хуже.

4 голосов
/ 20 июля 2009

Пример вычисления промежуточного итога для каждой записи, но только если OrderDate для записей на одну и ту же дату. Если для OrderDate задан другой день, то будет запущен новый промежуточный итог, который будет накапливаться для нового дня: (предположим, структура таблицы и данные)

select O.OrderId,
convert(char(10),O.OrderDate,101) as 'Order Date',
O.OrderAmt, 
(select sum(OrderAmt) from Orders 
                      where OrderID <= O.OrderID and 
                           convert(char(10),OrderDate,101)
                         = convert(char(10),O.OrderDate,101))
                               'Running Total' 
from Orders O
order by OrderID

Вот результаты, полученные в результате запроса с использованием примера таблицы заказов:

OrderId     Order Date OrderAmt   Running Total                            
----------- ---------- ---------- ---------------
1           10/11/2003 10.50      10.50
2           10/11/2003 11.50      22.00
3           10/11/2003 1.25       23.25
4           10/12/2003 100.57     100.57
5           10/12/2003 19.99      120.56
6           10/13/2003 47.14      47.14
7           10/13/2003 10.08      57.22
8           10/13/2003 7.50       64.72
9           10/13/2003 9.50       74.22

Обратите внимание, что "Итоговое значение" начинается со значения 10.50, а затем становится 22.00 и, наконец, становится 23.25 для OrderID 3, так как все эти записи имеют одинаковую OrderDate (10/11/2003). Но когда отображается OrderID 4, промежуточный итог сбрасывается, и текущий итог начинается заново. Это связано с тем, что OrderID 4 имеет другую дату для OrderDate, чем OrderID 1, 2 и 3. Вычисление этой промежуточной суммы для каждой уникальной даты снова выполняется с использованием коррелированного подзапроса, хотя требуется дополнительное условие WHERE, которое определили, что данные OrderDate в разных записях должны быть в один и тот же день. Это условие WHERE выполняется с помощью функции CONVERT для усечения OrderDate в формате MM / DD / YYYY.

2 голосов
/ 20 июля 2009

Вы можете просто включить коррелированный подзапрос в предложении select. (Это будет плохо работать для очень больших наборов результатов), но

   Select <other stuff>,
       (Select Sum(ColumnVal) From Table
        Where OrderColumn <= T.OrderColumn) As RunningTotal
   From Table T
   Order By OrderColumn
1 голос
/ 20 июля 2009

В SQLTeam есть также статья о подсчете промежуточных итогов. Существует сравнение трех способов сделать это, наряду с некоторым измерением производительности:

  • с помощью курсоров
  • с использованием подвыбора (согласно посту SQLMenace)
  • с использованием CROSS JOIN

Курсоры намного превосходят другие решения, но если вы не должны использовать курсоры, есть хотя бы альтернатива.

1 голос
/ 20 июля 2009

Вы можете сделать счетчик операций , вот пример, имейте в виду, что это на самом деле не так быстро, так как он должен сканировать таблицу для каждой строки, если ваша таблица велика, это может быть довольно трудоемкий и дорогой

create table #Test  (id int, Value decimal(16,4))
insert #Test values(1,100)
insert #Test values(2,100)
insert #Test values(3,100)
insert #Test values(4,200)
insert #Test values(5,200)
insert #Test values(6,200)
insert #Test values(7,200)

select *,(select sum(Value) from  #Test t2 where t2.id <=t1.id) as SumValues
 from #test t1

id  Value       SumValues
1   100.0000    100.0000
2   100.0000    200.0000
3   100.0000    300.0000
4   200.0000    500.0000
5   200.0000    700.0000
6   200.0000    900.0000
7   200.0000    1100.0000
0 голосов
/ 12 июня 2014

выберите TransactionDate, сумму, сумму + (сумма x.amount из транзакций x, где x.TransactionDate <транзакции) Runningtotal из транзакций </p>

, где x.TransactionDate <Транзакции может быть любое условие, которое будет представлять все предыдущие записи, кроме текущей </p>

0 голосов
/ 20 марта 2014

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

ОБНОВЛЕНИЕ @SalesTbl SET @RunningTotal = RunningTotal = @RunningTotal + Sales ОТ @ SalesTbl

Использование одной из других опций (курсор, оконная функция или вложенные запросы), как правило, будет самым безопасным вариантом для получения надежных результатов.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...