Ich versuche die laufende Summe zu berechnen. Es sollte jedoch zurückgesetzt werden, wenn die kumulative Summe größer als ein anderer Spaltenwert ist
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
Indexdetails:
CREATE UNIQUE CLUSTERED INDEX ix_load_reset_runn_total
ON #test(rn, grp)
Beispieldaten
+----+-----+-----------+-----+
| 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 |
+----+-----+-----------+-----+
Erwartetes Ergebnis
+----+-----+-----------------+-------------+
| 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
+----+-----+-----------------+-------------+
Abfrage:
Ich habe das Ergebnis mit Recursive CTE
. Die ursprüngliche Frage finden Sie hier /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
Gibt es eine bessere Alternative T-SQL
ohne CLR
.?
50000
Gruppen mit60
Ausweisen tun . Die Gesamtzahl der Datensätze liegt also bei etwa3000000
. Bin mir sicherRecursive CTE
nicht gut skalierbar3000000
. Aktualisiert die Metriken, wenn ich wieder im Büro bin. Können wir dies erreichen,sum()Over(Order by)
wie Sie es in diesem Artikel verwendet haben ? Sqlperformance.com/2012/07/t-sql-queries/running-totalsAntworten:
Ich habe mir ähnliche Probleme angesehen und nie eine Fensterfunktionslösung gefunden, die einen einzigen Durchlauf über die Daten durchführt. Ich denke nicht, dass es möglich ist. Fensterfunktionen müssen auf alle Werte in einer Spalte angewendet werden können. Das macht solche Reset-Berechnungen sehr schwierig, da ein Reset den Wert für alle folgenden Werte ändert.
Eine Möglichkeit, über das Problem nachzudenken, besteht darin, dass Sie das gewünschte Endergebnis erhalten, wenn Sie eine grundlegende laufende Summe berechnen, solange Sie die laufende Summe von der richtigen vorherigen Zeile subtrahieren können. In Ihren Beispieldaten ist der Wert für
id
4 beispielsweise derrunning total of row 4 - the running total of row 3
. Der Wert fürid
6 ist der Wert,running total of row 6 - the running total of row 3
da noch kein Reset durchgeführt wurde. Der Wert fürid
7 ist derrunning total of row 7 - the running total of row 6
und so weiter.Ich würde dies mit T-SQL in einer Schleife angehen. Ich wurde ein wenig mitgerissen und denke, ich habe eine vollständige Lösung. Für 3 Millionen Zeilen und 500 Gruppen wurde der Code auf meinem Desktop in 24 Sekunden fertiggestellt. Ich teste mit SQL Server 2016 Developer Edition mit 6 vCPU. Ich nutze parallele Einfügungen und parallele Ausführung im Allgemeinen, sodass Sie möglicherweise den Code ändern müssen, wenn Sie eine ältere Version verwenden oder DOP-Einschränkungen haben.
Unter dem Code, mit dem ich die Daten generiert habe. Die Bereiche auf
VAL
undRESET_VAL
sollten Ihren Beispieldaten ähnlich sein.Der Algorithmus ist wie folgt:
1) Fügen Sie zunächst alle Zeilen mit einer Standardlaufsumme in eine temporäre Tabelle ein.
2) In einer Schleife:
2a) Berechnen Sie für jede Gruppe die erste Zeile mit einer laufenden Summe über dem in der Tabelle verbleibenden reset_value und speichern Sie die ID, die zu große laufende Summe und die zu große laufende Summe in einer temporären Tabelle.
2b) Löschen Sie Zeilen aus der ersten temporären Tabelle in eine temporäre Ergebnistabelle, die
ID
kleiner oder gleich derID
in der zweiten temporären Tabelle ist. Verwenden Sie die anderen Spalten, um die laufende Summe nach Bedarf anzupassen.3) Nachdem der Löschvorgang keine Zeilen mehr verarbeitet, führen Sie eine zusätzliche
DELETE OUTPUT
in die Ergebnistabelle ein. Dies gilt für Zeilen am Ende der Gruppe, die den Rücksetzwert niemals überschreiten.Ich werde Schritt für Schritt eine Implementierung des obigen Algorithmus in T-SQL durchgehen.
Erstellen Sie zunächst einige temporäre Tabellen.
#initial_results
enthält die Originaldaten mit der Standardlaufsumme,#group_bookkeeping
wird in jeder Schleife aktualisiert, um herauszufinden, welche Zeilen verschoben werden können, und#final_results
enthält die Ergebnisse, wobei die Laufsumme für Zurücksetzungen angepasst ist.Ich erstelle den Clustered-Index für die temporäre Tabelle, damit das Einfügen und das Erstellen des Index parallel erfolgen können. Hat einen großen Unterschied auf meiner Maschine gemacht, aber möglicherweise nicht auf Ihrer. Das Erstellen eines Index für die Quelltabelle schien nicht zu helfen, aber das könnte auf Ihrem Computer helfen.
Der folgende Code wird in der Schleife ausgeführt und aktualisiert die Buchhaltungstabelle. Für jede Gruppe müssen wir das Find-Maximum ermitteln,
ID
das in die Ergebnistabelle verschoben werden soll. Wir benötigen die laufende Summe aus dieser Zeile, damit wir sie von der anfänglichen laufenden Summe abziehen können. Diegrp_done
Spalte wird auf 1 gesetzt, wenn für a keine Arbeit mehr zu erledigen istgrp
.Wirklich kein Fan des
LOOP JOIN
Hinweises im Allgemeinen, aber dies ist eine einfache Abfrage und es war der schnellste Weg, um das zu bekommen, was ich wollte. Um die Antwortzeit wirklich zu optimieren, wollte ich parallele verschachtelte Schleifenverknüpfungen anstelle von DOP 1-Zusammenführungsverknüpfungen.Der folgende Code wird in der Schleife ausgeführt und verschiebt Daten aus der Anfangstabelle in die Endergebnis-Tabelle. Beachten Sie die Anpassung an die anfängliche laufende Summe.
Für Ihre Bequemlichkeit ist unten der vollständige Code:
quelle
Recursive CTE
dauerte 2 Minuten und 15 SekundenVerwenden eines CURSORS:
Überprüfen Sie hier: http://rextester.com/WSPLO95303
quelle
Nicht mit Fenstern versehen, aber reine SQL-Version:
Ich bin kein Spezialist für den Dialekt von SQL Server. Dies ist eine erste Version für PostrgreSQL (wenn ich das richtig verstehe, kann ich LIMIT 1 / TOP 1 im rekursiven Teil von SQL Server nicht verwenden):
quelle
grp
Spalte.Es scheint, dass Sie mehrere Fragen / Methoden haben, um das Problem anzugreifen, aber Sie haben uns nicht zur Verfügung gestellt - oder sogar darüber nachgedacht? - die Indizes auf der Tabelle.
Welche Indizes enthält die Tabelle? Ist es ein Heap oder hat es einen Clustered-Index?
Ich würde die verschiedenen vorgeschlagenen Lösungen ausprobieren, nachdem ich diesen Index hinzugefügt habe:
Oder ändern (oder erstellen) Sie einfach den Clustered-Index
(grp, id)
.Ein Index, der auf die spezifische Abfrage abzielt, sollte die Effizienz verbessern - der meisten, wenn nicht aller Methoden.
quelle