Представьте себе следующую таблицу (называется TestTable
):
id somedate somevalue
-- -------- ---------
45 01/Jan/09 3
23 08/Jan/09 5
12 02/Feb/09 0
77 14/Feb/09 7
39 20/Feb/09 34
33 02/Mar/09 6
Я хотел бы запрос, который возвращает промежуточную сумму в порядке дат, например:
id somedate somevalue runningtotal
-- -------- --------- ------------
45 01/Jan/09 3 3
23 08/Jan/09 5 8
12 02/Feb/09 0 8
77 14/Feb/09 7 15
39 20/Feb/09 34 49
33 02/Mar/09 6 55
Я знаю, что есть разные способы сделать это в SQL Server 2000/2005/2008.
Я особенно заинтересован в таком методе, который использует трюк с агрегирующим набором операторов:
INSERT INTO @AnotherTbl(id, somedate, somevalue, runningtotal)
SELECT id, somedate, somevalue, null
FROM TestTable
ORDER BY somedate
DECLARE @RunningTotal int
SET @RunningTotal = 0
UPDATE @AnotherTbl
SET @RunningTotal = runningtotal = @RunningTotal + somevalue
FROM @AnotherTbl
... это очень эффективно, но я слышал, что есть проблемы, связанные с этим, потому что вы не можете обязательно гарантировать, что UPDATE
оператор будет обрабатывать строки в правильном порядке. Может быть, мы можем получить некоторые окончательные ответы по этому вопросу.
Но, может быть, есть другие способы, которые люди могут предложить?
редактировать: теперь с SqlFiddle с настройкой и примером обновления трюк выше
sql
sql-server
tsql
running-total
codeulike
источник
источник
Ответы:
Обновите , если вы используете SQL Server 2012, см .: https://stackoverflow.com/a/10309947
Проблема в том, что реализация SQL Server предложения Over несколько ограничена .
Oracle (и ANSI-SQL) позволяют вам делать такие вещи, как:
SQL Server не дает вам чистого решения этой проблемы. Моя интуиция говорит мне, что это один из тех редких случаев, когда курсор является самым быстрым, хотя мне придется сделать несколько сравнительных тестов для больших результатов.
Уловка обновления удобна, но я чувствую ее довольно хрупкую. Кажется, что если вы обновляете полную таблицу, то она будет действовать в порядке первичного ключа. Поэтому, если вы установите дату в качестве первичного ключа по возрастанию, вы будете в
probably
безопасности. Но вы полагаетесь на недокументированные детали реализации SQL Server (также, если запрос завершается выполнением двумя процессами, мне интересно, что произойдет, см .: MAXDOP):Полный рабочий образец:
Вы просили эталон, это низкий уровень.
Самым быстрым БЕЗОПАСНЫМ способом сделать это был бы Курсор, он на порядок быстрее, чем коррелированный подзапрос перекрестного соединения.
Абсолютно быстрый способ - трюк ОБНОВЛЕНИЯ. Единственное, что меня беспокоит, это то, что я не уверен, что при любых обстоятельствах обновление будет происходить линейно. В запросе нет ничего, что прямо говорит об этом.
Итог, для производственного кода я бы пошел с курсором.
Тестовые данные:
Тест 1:
Тест 2:
Тест 3:
Тест 4:
источник
В SQL Server 2012 вы можете использовать SUM () с предложением OVER () .
SQL Fiddle
источник
Хотя Сэм Саффрон проделал большую работу над этим, он все еще не предоставил рекурсивный общий код табличного выражения для этой проблемы. И для нас, которые работают с SQL Server 2008 R2, а не с Denali, это все еще самый быстрый способ подвести итоги, он примерно в 10 раз быстрее, чем курсор на моем рабочем компьютере для 100000 строк, и это также встроенный запрос.
Итак, вот оно (я предполагаю, что
ord
в таблице есть столбец и его порядковый номер без пробелов, для быстрой обработки также должно быть уникальное ограничение на это число):sql fiddle demo
Обновление Мне также было интересно узнать об этом обновлении с переменным или необычным обновлением . Обычно это работает нормально, но как мы можем быть уверены, что это работает каждый раз? хорошо, вот небольшой трюк (нашел его здесь - http://www.sqlservercentral.com/Forums/Topic802558-203-21.aspx#bm981258 ) - вы просто проверяете текущее и предыдущее
ord
и используете1/0
назначение в случае, если они отличаются от того, что вы ожидаете:Из того, что я видел, если у вас есть правильный кластеризованный индекс / первичный ключ в вашей таблице (в нашем случае это будет индекс по
ord_id
), обновление будет происходить линейным образом все время (никогда не встречалось деление на ноль). Тем не менее, вам решать, хотите ли вы использовать его в рабочем коде :)обновление 2 Я связываю этот ответ, потому что он содержит некоторую полезную информацию о ненадежности причудливого обновления - конкатенация nvarchar / index / nvarchar (max) необъяснимое поведение .
источник
Оператор APPLY в SQL 2005 и выше работает для этого:
источник
Вы также можете использовать функцию ROW_NUMBER () и временную таблицу для создания произвольного столбца, который будет использоваться при сравнении внутреннего оператора SELECT.
источник
Используйте коррелированный подзапрос. Очень просто, здесь вы идете:
Код может быть не совсем правильным, но я уверен, что идея такова.
GROUP BY - в случае, если дата появляется более одного раза, вы бы хотели видеть ее только один раз в наборе результатов.
Если вы не против увидеть повторяющиеся даты или хотите увидеть исходное значение и идентификатор, то вам нужно следующее:
источник
Вы также можете денормализовать - сохранить промежуточные итоги в той же таблице:
http://sqlblog.com/blogs/alexander_kuznetsov/archive/2009/01/23/denormalizing-to-enforce-business-rules-running-totals.aspx
Выбирает работу намного быстрее, чем любые другие решения, но модификации могут быть медленнее
источник
Предполагая, что управление окнами работает на SQL Server 2008 так же, как и в других местах (которые я пробовал), попробуйте:
MSDN говорит, что он доступен в SQL Server 2008 (а может, и в 2005 году?), Но у меня нет экземпляра, чтобы попробовать его.
РЕДАКТИРОВАТЬ: ну, очевидно, SQL Server не разрешает спецификацию окна («OVER (...)») без указания «PARTITION BY» (деление результата на группы, но без агрегирования, как это делает GROUP BY). Раздражает - ссылка на синтаксис MSDN предполагает, что это необязательно, но в данный момент у меня есть только экземпляры SqlServer 2000.
Заданный мною запрос работает как в Oracle 10.2.0.3.0, так и в PostgreSQL 8.4-beta. Так что скажи MS наверстать;)
источник
1 partitionme
и разделите этим. Кроме того, разделение по, вероятно, необходимо в реальных ситуациях при выполнении отчетов.Если вы используете Sql server 2008 R2 выше. Тогда это был бы самый короткий способ сделать;
LAG используется для получения значения предыдущей строки. Вы можете сделать Google для получения дополнительной информации.
[1]:
источник
SUM(somevalue) OVER(...)
что кажется мне чищеЯ считаю, что промежуточный итог может быть достигнут с помощью простой операции INNER JOIN ниже.
источник
Следующее даст необходимые результаты.
Наличие кластеризованного индекса на SomeDate значительно улучшит производительность.
источник
Использование соединения Еще одним вариантом является использование соединения. Теперь запрос может выглядеть так:
Для получения дополнительной информации вы можете посетить эту ссылку http://askme.indianyouth.info/details/calculating-simple-running-totals-in-sql-server-12
источник
Хотя лучший способ сделать это - использовать оконную функцию, это также можно сделать с помощью простого коррелированного подзапроса .
источник
источник
Вот 2 простых способа вычисления промежуточного итога:
Подход 1. Это можно записать так, если ваша СУБД поддерживает аналитические функции
Подход 2 : Вы можете использовать OUTER APPLY, если ваша версия базы данных / сама СУБД не поддерживает аналитические функции
Примечание: - Если вам необходимо рассчитать промежуточный итог для разных разделов по отдельности, это можно сделать, как показано здесь: Расчет промежуточных итогов по строкам и группировка по ID
источник