Пытаюсь подсчитать промежуточную сумму. Но он должен сбрасываться, когда накопленная сумма больше, чем значение другого столбца
create table #reset_runn_total
(
id int identity(1,1),
val int,
reset_val int,
grp int
)
insert into #reset_runn_total
values
(1,10,1),
(8,12,1),(6,14,1),(5,10,1),(6,13,1),(3,11,1),(9,8,1),(10,12,1)
SELECT Row_number()OVER(partition BY grp ORDER BY id)AS rn,*
INTO #test
FROM #reset_runn_total
Детали индекса:
CREATE UNIQUE CLUSTERED INDEX ix_load_reset_runn_total
ON #test(rn, grp)
образец данных
+----+-----+-----------+-----+
| id | val | reset_val | Grp |
+----+-----+-----------+-----+
| 1 | 1 | 10 | 1 |
| 2 | 8 | 12 | 1 |
| 3 | 6 | 14 | 1 |
| 4 | 5 | 10 | 1 |
| 5 | 6 | 13 | 1 |
| 6 | 3 | 11 | 1 |
| 7 | 9 | 8 | 1 |
| 8 | 10 | 12 | 1 |
+----+-----+-----------+-----+
Ожидаемый результат
+----+-----+-----------------+-------------+
| id | val | reset_val | Running_tot |
+----+-----+-----------------+-------------+
| 1 | 1 | 10 | 1 |
| 2 | 8 | 12 | 9 | --1+8
| 3 | 6 | 14 | 15 | --1+8+6 -- greater than reset val
| 4 | 5 | 10 | 5 | --reset
| 5 | 6 | 13 | 11 | --5+6
| 6 | 3 | 11 | 14 | --5+6+3 -- greater than reset val
| 7 | 9 | 8 | 9 | --reset -- greater than reset val
| 8 | 10 | 12 | 10 | --reset
+----+-----+-----------------+-------------+
Запрос:
Я получил результат, используя Recursive CTE
. Оригинальный вопрос здесь /programming/42085404/reset-running-total-based-on-another-column
;WITH cte
AS (SELECT rn,id,
val,
reset_val,
grp,
val AS running_total,
Iif (val > reset_val, 1, 0) AS flag
FROM #test
WHERE rn = 1
UNION ALL
SELECT r.*,
Iif(c.flag = 1, r.val, c.running_total + r.val),
Iif(Iif(c.flag = 1, r.val, c.running_total + r.val) > r.reset_val, 1, 0)
FROM cte c
JOIN #test r
ON r.grp = c.grp
AND r.rn = c.rn + 1)
SELECT *
FROM cte
Есть ли лучшая альтернатива T-SQL
без использования CLR
.?
50000
групп с60
идентификаторами . так что общее количество записей будет около3000000
. УверенаRecursive CTE
не будет хорошо масштабироваться3000000
. Обновлю метрики, когда вернусь в офис. Можем ли мы добиться этого, используя,sum()Over(Order by)
как вы использовали в этой статье sqlperformance.com/2012/07/t-sql-queries/running-totalsОтветы:
Я смотрел на подобные проблемы и никогда не мог найти решение оконной функции, которое делает один проход по данным. Я не думаю, что это возможно. Оконные функции должны быть в состоянии применяться ко всем значениям в столбце. Это делает такие вычисления сброса очень сложными, потому что один сброс изменяет значение для всех следующих значений.
Один из способов решения проблемы заключается в том, что вы можете получить желаемый конечный результат, если вычисляете базовую промежуточную сумму, если вы можете вычесть промежуточную сумму из правильной предыдущей строки. Например, в ваших данных образца значение для
id
4 являетсяrunning total of row 4 - the running total of row 3
. Значениеid
6 - этоrunning total of row 6 - the running total of row 3
потому что сброс еще не произошел. Значение дляid
7 - этоrunning total of row 7 - the running total of row 6
и так далее.Я хотел бы подойти к этому с T-SQL в цикле. Я немного увлекся и думаю, что у меня есть полное решение. Для 3 миллионов строк и 500 групп код завершился за 24 секунды на моем рабочем столе. Я тестирую с SQL Server 2016 для разработчиков с 6 vCPU. Я использую преимущества параллельных вставок и параллельного выполнения в целом, поэтому вам может потребоваться изменить код, если вы используете более старую версию или имеете ограничения DOP.
Ниже код, который я использовал для генерации данных. Диапазоны на
VAL
иRESET_VAL
должны быть аналогичны вашим образцам данных.Алгоритм выглядит следующим образом:
1) Начните с вставки всех строк со стандартным промежуточным итогом во временную таблицу.
2) В цикле:
2a) Для каждой группы вычислите первую строку с промежуточной суммой выше оставшегося в таблице значения reset_value и сохраните идентификатор, промежуточную сумму, которая была слишком большой, и предыдущую промежуточную сумму, которая была слишком большой, во временной таблице.
2b) Удалить строки из первой временной таблицы во временную таблицу результатов, которые имеют значение,
ID
меньшее или равноеID
значению второй временной таблицы. Используйте другие столбцы для корректировки промежуточного итога по мере необходимости.3) После удаления больше не обрабатываемых строк запускаются дополнительные
DELETE OUTPUT
в таблицу результатов. Это для строк в конце группы, которые никогда не превышают значение сброса.Я покажу пошаговую реализацию одного из описанных выше алгоритмов в T-SQL.
Начните с создания нескольких временных таблиц.
#initial_results
содержит исходные данные со стандартным промежуточным итогом,#group_bookkeeping
обновляет каждый цикл, чтобы выяснить, какие строки могут быть перемещены, и#final_results
содержит результаты с промежуточным итогом, откорректированным для сброса.Я создаю кластеризованный индекс для временной таблицы после этого, чтобы вставка и построение индекса могли выполняться параллельно. Сделал большую разницу на моей машине, но, возможно, не на вашей. Создание индекса для исходной таблицы, похоже, не помогло, но это могло бы помочь на вашем компьютере.
Приведенный ниже код запускается в цикле и обновляет таблицу учета. Для каждой группы нам нужно найти максимальное значение,
ID
которое следует переместить в таблицу результатов. Нам нужна промежуточная сумма из этой строки, чтобы мы могли вычесть ее из начальной промежуточной суммы.grp_done
Столбец устанавливается в 1 , если не больше работы , чтобы сделать дляgrp
.На самом деле не фанат
LOOP JOIN
подсказки в целом, но это простой запрос, и это был самый быстрый способ получить то, что я хотел. Чтобы действительно оптимизировать время отклика, я хотел соединений с параллельными вложенными циклами вместо объединений DOP 1.Приведенный ниже код выполняется в цикле и перемещает данные из исходной таблицы в таблицу окончательных результатов. Обратите внимание на корректировку начального промежуточного итога.
Для вашего удобства ниже приведен полный код:
источник
Recursive CTE
заняло 2 минуты 15 секундИспользуя КУРСОР:
Проверьте здесь: http://rextester.com/WSPLO95303
источник
Не оконная, а чистая версия SQL:
Я не специалист по диалекту SQL Server. Это начальная версия для PostrgreSQL (если я правильно понимаю, я не могу использовать LIMIT 1 / TOP 1 в рекурсивной части в SQL Server):
источник
grp
столбец.Кажется, у вас есть несколько запросов / методов для решения проблемы, но вы не предоставили нам - или даже не рассмотрели? - индексы на столе.
Какие показатели есть в таблице? Это куча или кластерный индекс?
Я бы попробовал различные решения, предложенные после добавления этого индекса:
Или просто измените (или сделайте) кластерный индекс на
(grp, id)
.Наличие индекса, предназначенного для конкретного запроса, должно повысить эффективность - большинства, если не всех методов.
источник