Ich habe eine Tabelle, die eine Spalte mit Dezimalwerten enthält, wie diese:
id value size
-- ----- ----
1 100 .02
2 99 .38
3 98 .13
4 97 .35
5 96 .15
6 95 .57
7 94 .25
8 93 .15
Was ich erreichen muss, ist etwas schwierig zu beschreiben. Bitte nehmen Sie Kontakt mit mir auf. Ich versuche, einen Gesamtwert der size
Spalte zu erstellen, der jedes Mal um 1 erhöht wird, wenn die vorhergehenden Zeilen in absteigender Reihenfolge zu 1 summieren value
. Das Ergebnis würde ungefähr so aussehen:
id value size bucket
-- ----- ---- ------
1 100 .02 1
2 99 .38 1
3 98 .13 1
4 97 .35 1
5 96 .15 2
6 95 .57 2
7 94 .25 2
8 93 .15 3
Mein naiver erster Versuch war, einen laufenden Wert SUM
und dann CEILING
diesen Wert beizubehalten. Er behandelt jedoch nicht den Fall, in dem einige Datensätze size
zu insgesamt zwei separaten Buckets beitragen. Das folgende Beispiel könnte dies verdeutlichen:
id value size crude_sum crude_bucket distinct_sum bucket
-- ----- ---- --------- ------------ ------------ ------
1 100 .02 .02 1 .02 1
2 99 .38 .40 1 .40 1
3 98 .13 .53 1 .53 1
4 97 .35 .88 1 .88 1
5 96 .15 1.03 2 .15 2
6 95 .57 1.60 2 .72 2
7 94 .25 1.85 2 .97 2
8 93 .15 2.00 2 .15 3
Wie Sie sehen können, wenn ich einfach zu bedienen waren CEILING
auf crude_sum
Rekord # 8 würde 2. zu Eimer zugeordnet werden diese durch die verursacht wird , size
von Datensätzen # 5 und # 8 , die geteilt in zwei Eimern. Stattdessen besteht die ideale Lösung darin, die Summe jedes Mal zurückzusetzen, wenn sie 1 erreicht. Dadurch wird die bucket
Spalte erhöht und eine neue SUM
Operation ab dem size
Wert des aktuellen Datensatzes gestartet. Da die Reihenfolge der Datensätze für diesen Vorgang wichtig ist, habe ich die value
Spalte eingefügt, die in absteigender Reihenfolge sortiert werden soll.
Meine ersten Versuche umfassten das Durchführen mehrerer Durchgänge über die Daten, einmal zum Ausführen des SUM
Vorgangs, noch einmal zum Durchführen CEILING
usw. Hier ist ein Beispiel dafür, was ich zum Erstellen der crude_sum
Spalte getan habe :
SELECT
id,
value,
size,
(SELECT TOP 1 SUM(size) FROM table t2 WHERE t2.value<=t1.value) as crude_sum
FROM
table t1
Dies wurde in einer UPDATE
Operation verwendet, um den Wert in eine Tabelle einzufügen, mit der später gearbeitet werden soll.
Bearbeiten: Ich würde gerne noch einmal versuchen, dies zu erklären, also geht es weiter. Stellen Sie sich vor, jeder Datensatz ist ein physischer Gegenstand. Diesem Element ist ein Wert zugeordnet und eine physische Größe von weniger als eins. Ich habe eine Reihe von Eimern mit einer Volumenkapazität von genau 1, und ich muss bestimmen, wie viele dieser Eimer ich benötige und in welchen Eimer jeder Artikel gemäß dem Wert des Artikels geht, sortiert vom höchsten zum niedrigsten.
Ein physischer Gegenstand kann nicht an zwei Orten gleichzeitig existieren, daher muss er sich in dem einen oder anderen Eimer befinden. Aus diesem Grund kann ich keine laufende Total + CEILING
-Lösung durchführen, da Datensätze dadurch ihre Größe auf zwei Buckets übertragen können.
distinct_count
erschwert das Erfordernis des Aufbockens die Dinge. Aaron Bertrand hat eine großartige Zusammenfassung Ihrer Optionen auf SQL Server für diese Art von Fensterarbeiten. Ich habe die "skurrile Update" -Methode verwendet, um zu berechnendistinct_sum
, die Sie hier auf SQL Fiddle sehen können , aber dies ist unzuverlässig.Antworten:
Ich bin nicht sicher, welche Art von Leistung Sie suchen, aber wenn CLR oder externe App keine Option ist, bleibt nur ein Cursor übrig. Auf meinem alten Laptop komme ich mit der folgenden Lösung in etwa 100 Sekunden durch 1.000.000 Zeilen. Das Schöne daran ist, dass es linear skaliert, so dass ich mir ungefähr 20 Minuten Zeit nehmen würde, um das Ganze durchzugehen. Mit einem anständigen Server sind Sie schneller, aber nicht um eine Größenordnung. Es würde also noch einige Minuten dauern, bis dies abgeschlossen ist. Wenn dies ein einmaliger Prozess ist, können Sie sich die Langsamkeit wahrscheinlich leisten. Wenn Sie dies regelmäßig als Bericht oder ähnliches ausführen müssen, möchten Sie die Werte möglicherweise in derselben Tabelle speichern und nicht aktualisieren, wenn neue Zeilen hinzugefügt werden, z. B. in einem Trigger.
Wie auch immer, hier ist der Code:
Es löscht und erstellt die Tabelle MyTable neu, füllt sie mit 1000000 Zeilen und macht sich dann an die Arbeit.
Der Cursor kopiert jede Zeile in eine temporäre Tabelle, während die Berechnungen ausgeführt werden. Am Ende gibt die Auswahl die berechneten Ergebnisse zurück. Sie sind möglicherweise etwas schneller, wenn Sie die Daten nicht kopieren, sondern stattdessen ein direktes Update durchführen.
Wenn Sie die Möglichkeit haben, ein Upgrade auf SQL 2012 durchzuführen, können Sie sich die neuen von Window Spool unterstützten Moving Window Aggregate ansehen, die Ihnen eine bessere Leistung bieten sollen.
Nebenbei bemerkt, wenn Sie eine Assembly mit erlaubnis_set = safe installiert haben, können Sie einem Server mit Standard-T-SQL mehr schlechte Dinge antun als mit der Assembly, also würde ich weiter daran arbeiten, diese Barriere zu entfernen - Sie haben eine gute Verwendung Fall hier, wo CLR Ihnen wirklich helfen würde.
quelle
Ohne die neuen Fensterfunktionen in SQL Server 2012 kann eine komplexe Fensterung mithilfe rekursiver CTEs durchgeführt werden. Ich frage mich, wie gut dies gegen Millionen von Zeilen funktioniert.
Die folgende Lösung deckt alle von Ihnen beschriebenen Fälle ab. Sie können es hier auf SQL Fiddle in Aktion sehen .
Jetzt atme tief ein. Hier gibt es zwei wichtige CTEs, denen jeweils ein kurzer Kommentar vorangestellt ist. Der Rest sind nur "Bereinigungs" -CTEs, um beispielsweise die richtigen Zeilen zu ziehen, nachdem wir sie eingestuft haben.
Diese Lösung geht davon aus, dass
id
es sich um eine lückenlose Sequenz handelt. Wenn nicht, müssen Sie Ihre eigene lückenlose Sequenz generieren, indem Sie am Anfang einen zusätzlichen CTE hinzufügen, der die ZeilenROW_NUMBER()
in der gewünschten Reihenfolge nummeriert (zROW_NUMBER() OVER (ORDER BY value DESC)
. B. ).Fankly, das ist ziemlich ausführlich.
quelle
crude_sum
mitdistinct_sum
den zugehörigenbucket
Spalten, um zu sehen, was ich meine.Dies fühlt sich wie eine dumme Lösung an und lässt sich wahrscheinlich nicht gut skalieren. Testen Sie daher sorgfältig, ob Sie sie verwenden. Da das Hauptproblem von dem im Eimer verbleibenden "Leerzeichen" herrührt, musste ich zuerst einen Fülldatensatz erstellen, um ihn mit den Daten zu verbinden.
http://sqlfiddle.com/#!3/72ad4/14/0
quelle
Das Folgende ist eine weitere rekursive CTE-Lösung, obwohl ich sagen würde, dass sie einfacher ist als der Vorschlag von @ Nick . Es ist tatsächlich näher an @ Sebastians Cursor , nur ich habe Laufdifferenzen anstelle von Laufsummen verwendet. (Zuerst dachte ich sogar, dass @ Nicks Antwort dem entsprechen würde, was ich hier vorschlage, und nachdem ich erfahren hatte, dass es sich tatsächlich um eine ganz andere Frage handelte, entschied ich mich, meine anzubieten.)
Hinweis: Bei dieser Abfrage wird davon ausgegangen, dass die
value
Spalte aus eindeutigen Werten ohne Lücken besteht. Ist dies nicht der Fall, müssen Sie eine berechnete Rangfolge-Spalte basierend auf der absteigenden Reihenfolge von einführenvalue
und im rekursiven CTE verwenden, anstattvalue
den rekursiven Teil mit dem Anker zu verbinden.Eine SQL Fiddle-Demo für diese Abfrage finden Sie hier .
quelle
size
mitroom_left
) zu vergleichen, als einen einzelnen Wert mit einem Ausdruck (1
mitrunning_size
+size
) zu vergleichen. Ich habe zuerst keineis_new_bucket
Flagge verwendet, sondern mehrereCASE WHEN t.size > r.room_left ...
("mehrere", weil ich auch die Gesamtgröße berechnet (und zurückgegeben) habe, dann aber der Einfachheit halber dagegen gedacht habe), also dachte ich, es wäre eleganter dieser Weg.