Próbuję obliczyć sumę bieżącą. Ale powinien się zresetować, gdy suma skumulowana jest większa niż inna wartość kolumny
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
Szczegóły indeksu:
CREATE UNIQUE CLUSTERED INDEX ix_load_reset_runn_total
ON #test(rn, grp)
przykładowe dane
+----+-----+-----------+-----+
| 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 |
+----+-----+-----------+-----+
Spodziewany wynik
+----+-----+-----------------+-------------+
| 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
+----+-----+-----------------+-------------+
Pytanie:
Mam wynik przy użyciu Recursive CTE
. Oryginalne pytanie jest tutaj /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
Czy jest jakaś lepsza alternatywa T-SQL
bez używania CLR
.?
50000
grup z60
Id . więc całkowita liczba rekordów będzie w pobliżu3000000
. Na pewnoRecursive CTE
nie będzie dobrze skalować3000000
. Zaktualizuję wskaźniki, kiedy wrócę do biura. Czy możemy to osiągnąć za pomocą tego,sum()Over(Order by)
którego użyłeś w tym artykule sqlperformance.com/2012/07/t-sql-queries/running-totalsOdpowiedzi:
Patrzyłem na podobne problemy i nigdy nie byłem w stanie znaleźć rozwiązania funkcji okna, które wykonuje jedno przejście przez dane. Nie sądzę, żeby to było możliwe. Funkcje okna muszą być możliwe do zastosowania do wszystkich wartości w kolumnie. To bardzo utrudnia obliczenia resetowania, ponieważ jeden reset zmienia wartość wszystkich poniższych wartości.
Jednym ze sposobów myślenia o problemie jest to, że możesz uzyskać pożądany wynik końcowy, jeśli obliczysz podstawową sumę bieżącą, o ile możesz odjąć sumę bieżącą od poprawnego poprzedniego wiersza. Na przykład w przykładowych danych wartością
id
4 jestrunning total of row 4 - the running total of row 3
. Wartośćid
6 wynika z tego,running total of row 6 - the running total of row 3
że reset jeszcze się nie odbył. Wartośćid
7 jest taka samarunning total of row 7 - the running total of row 6
.Podchodziłbym do tego z T-SQL w pętli. Trochę mnie poniosło i myślę, że mam pełne rozwiązanie. Dla 3 milionów wierszy i 500 grup kod zakończył się w ciągu 24 sekund na moim pulpicie. Testuję z SQL Server 2016 Developer Edition z 6 vCPU. Korzystam z równoległych wstawek i równoległego wykonywania, więc być może będziesz musiał zmienić kod, jeśli używasz starszej wersji lub masz ograniczenia DOP.
Poniżej kodu, którego użyłem do wygenerowania danych. Zakresy
VAL
iRESET_VAL
powinny być podobne do danych przykładowych.Algorytm wygląda następująco:
1) Zacznij od wstawienia wszystkich wierszy ze standardową sumą bieżącą do tabeli tymczasowej.
2) W pętli:
2a) Dla każdej grupy obliczyć pierwszy wiersz z sumą bieżącą powyżej wartości_resetu pozostającą w tabeli i zapisać identyfikator, sumę roboczą, która była zbyt duża, i poprzednią sumę roboczą, która była zbyt duża w tabeli tymczasowej.
2b) Usuń wiersze z pierwszej tabeli tymczasowej do tabeli wyników tymczasowej, która ma wartość
ID
mniejszą lub równąID
drugiej tabeli tymczasowej. Użyj innych kolumn, aby dostosować bieżącą sumę według potrzeb.3) Po usunięciu nie przetwarza już wierszy uruchom dodatkową
DELETE OUTPUT
tabelę wyników. Dotyczy to wierszy na końcu grupy, które nigdy nie przekraczają wartości resetowania.Przejdę krok po kroku przez jedną implementację powyższego algorytmu w języku T-SQL.
Zacznij od utworzenia kilku tabel tymczasowych.
#initial_results
przechowuje oryginalne dane ze standardową sumą bieżącą,#group_bookkeeping
jest aktualizowany w każdej pętli, aby dowiedzieć się, które wiersze można przenieść, i#final_results
zawiera wyniki z sumą bieżącą skorygowaną dla resetowania.Po utworzeniu indeksu klastrowego w tabeli tymczasowej wstawianie i budowanie indeksu można wykonywać równolegle. Zrobiłem dużą różnicę na moim komputerze, ale może nie na twoim. Tworzenie indeksu w tabeli źródłowej nie wydawało się pomocne, ale może pomóc na twoim komputerze.
Poniższy kod działa w pętli i aktualizuje tabelę księgowości. Dla każdej grupy musimy znaleźć maksimum,
ID
które należy przenieść do tabeli wyników. Potrzebujemy sumę bieżącą z tego wiersza, abyśmy mogli odjąć ją od początkowej sumy bieżącej.grp_done
Kolumna jest ustawiona na 1, gdy nie ma nic więcej do zrobienia dlagrp
.Naprawdę nie
LOOP JOIN
jestem fanem podpowiedzi w ogóle, ale jest to proste zapytanie i był to najszybszy sposób na uzyskanie tego, czego chciałem. Aby naprawdę zoptymalizować czas odpowiedzi, chciałem złączeń równoległych zagnieżdżonych zamiast połączeń scalających DOP 1.Poniższy kod działa w pętli i przenosi dane z początkowej tabeli do końcowej tabeli wyników. Zwróć uwagę na korektę początkowej sumy bieżącej.
Dla Twojej wygody poniżej znajduje się pełny kod:
źródło
Recursive CTE
zajęło 2 minuty i 15 sekundKorzystanie z KURSORA:
Sprawdź tutaj: http://rextester.com/WSPLO95303
źródło
Wersja bez okien, ale w czystej wersji SQL:
Nie jestem specjalistą w dialekcie SQL Server. To jest początkowa wersja PostrgreSQL (jeśli dobrze rozumiem, nie mogę użyć LIMIT 1 / TOP 1 w części rekurencyjnej w SQL Server):
źródło
grp
kolumny.Wygląda na to, że masz kilka zapytań / metod, aby zaatakować problem, ale nie dostarczyłeś nam - a nawet zastanowiłeś się? - indeksy na stole.
Jakie indeksy są w tabeli? Czy to jest kupa, czy ma indeks klastrowany?
Wypróbowałbym różne rozwiązania sugerowane po dodaniu tego indeksu:
Lub po prostu zmień (lub stwórz) indeks klastrowany
(grp, id)
.Posiadanie indeksu ukierunkowanego na określone zapytanie powinno poprawić wydajność - większości, jeśli nie wszystkich metod.
źródło