Sie sollten versuchen, Schleifen generell zu vermeiden. Sie sind normalerweise weniger effizient als satzbasierte Lösungen und weniger lesbar.
Das Folgende sollte ziemlich effizient sein.
Dies gilt umso mehr, wenn die Spalten name und weight INCLUDE-
im Index enthalten sein könnten , um die Schlüsselsuche zu vermeiden.
Es kann den eindeutigen Index in der Reihenfolge scannen turn
und die laufende Summe der Weight
Spalte berechnen - und dann verwendenLEAD
dieselben Ordnungskriterien, um zu sehen, wie hoch die laufende Summe in der nächsten Zeile sein wird.
Sobald die erste Zeile gefunden wird, in der diese 1000 überschreitet oder ist NULL
(was anzeigt, dass keine nächste Zeile vorhanden ist), kann der Scan gestoppt werden.
WITH T1
AS (SELECT *,
SUM(Weight) OVER (ORDER BY turn ROWS UNBOUNDED PRECEDING) AS cume_weight
FROM [dbo].[line]),
T2
AS (SELECT LEAD(cume_weight) OVER (ORDER BY turn) AS next_cume_weight,
*
FROM T1)
SELECT TOP 1 name
FROM T2
WHERE next_cume_weight > 1000
OR next_cume_weight IS NULL
ORDER BY turn
Ausführungsplan
In der Praxis scheint es ein paar Zeilen vor dem unbedingt erforderlichen zu lesen - es sieht so aus, als würde jedes Fenster-Spool / Stream-Aggregat-Paar zwei zusätzliche Zeilen lesen.
Für die Beispieldaten in der Frage müssten im Idealfall nur zwei Zeilen aus dem Index-Scan gelesen werden, in Wirklichkeit jedoch 6, aber dies ist kein signifikantes Effizienzproblem und verschlechtert sich nicht, wenn der Tabelle weitere Zeilen hinzugefügt werden (wie in) diese Demo )
Für diejenigen, die an dieser Ausgabe interessiert sind, query_trace_column_values
wird unten ein Bild mit den von jedem Operator ausgegebenen Zeilen (wie durch das erweiterte Ereignis gezeigt) angezeigt. Die Zeilen werden in der angegebenen row_id
Reihenfolge ausgegeben (beginnend mit 47
der ersten Zeile, die vom Index-Scan gelesen wurde, und endend mit 113
für TOP
).
Klicken Sie auf das Bild unten, um es zu vergrößern, oder sehen Sie sich alternativ die animierte Version an, um den Ablauf zu vereinfachen .
Anhalten der Animation an dem Punkt, an dem das rechte Stream-Aggregat seine erste Zeile ausgegeben hat (für gary - turn = 1). Es scheint offensichtlich, dass es darauf gewartet hat, seine erste Zeile mit einem anderen WindowCount zu erhalten (für Jo - turn = 2). Und die Fensterspule gibt die erste "Jo" -Zeile erst frei, wenn sie die nächste Zeile mit einer anderen gelesen hatturn
-Zeile (für thomas - turn = 3).
Die Fensterspule und das Stream-Aggregat bewirken also, dass eine zusätzliche Zeile gelesen wird, und es gibt vier davon im Plan - daher 4 zusätzliche Zeilen.
Es folgt eine Erläuterung der oben gezeigten Spalten (basierend auf Informationen hier ).
- Knotenname: Index-Scan, Knoten-ID: 15, Spaltenname: ID-Basistabellenspalte, die vom Index abgedeckt wird
- NodeName: Index-Scan, NodeId: 15, ColumnName: Dreht die vom Index abgedeckte Basistabellenspalte
- NodeName: Clustered Index Seek, NodeId: 17, ColumnName: Spalte der Gewichtsbasis- Tabelle, die aus der Suche abgerufen wurde
- NodeName: Clustered Index Seek, NodeId: 17, ColumnName: Spalte der Namensbasis- Tabelle, die aus der Suche abgerufen wurde
- NodeName: Segment, NodeId: 13, ColumnName: Segment1010 Gibt 1 am Anfang einer neuen Gruppe zurück oder andernfalls null. Als Nein
Partition By
in der SUM
einzigen bekommt die erste Reihe 1
- NodeName: Sequence Project, NodeId: 12, ColumnName: RowNumber1009
row_number()
innerhalb der durch das Segment1010-Flag angegebenen Gruppe. Da sich alle Zeilen in derselben Gruppe befinden, werden ganze Zahlen von 1 bis 6 aufgestiegen. Wird in Fällen wie z rows between 5 preceding and 2 following
. B. zum Filtern von Zeilen mit rechten Rahmen verwendet . (oder wie für LEAD
später)
- NodeName: Segment, NodeId: 11, ColumnName: Segment1011 Gibt 1 am Anfang einer neuen Gruppe zurück oder andernfalls null. Als Nein
Partition By
in der SUM
einzigen erhält die erste Zeile 1 (wie Segment1010)
- NodeName: Window Spool, NodeId: 10, ColumnName: WindowCount1012 Attribut, das Zeilen gruppiert , die zu einem Fensterrahmen gehören. Diese Fensterspule verwendet den Fall "Fast Track" für
UNBOUNDED PRECEDING
. Wo es zwei Zeilen pro Quellzeile ausgibt. Eins mit den kumulativen Werten und eins mit den Detailwerten. Obwohl es keinen sichtbaren Unterschied in den Zeilen gibt, die von query_trace_column_values
angezeigt werden, gehe ich davon aus, dass kumulative Spalten in der Realität vorhanden sind.
- NodeName: Stream Aggregate, NodeId: 9, ColumnName: Expr1004,
Count(*)
gruppiert nach WindowCount1012 gemäß Plan, aber tatsächlich eine laufende Anzahl
- NodeName: Stream Aggregate, NodeId: 9, ColumnName: Expr1005,
SUM(weight)
gruppiert nach WindowCount1012 gemäß Plan, aber tatsächlich der laufenden Gewichtssumme (dh cume_weight
)
- NodeName: Segment, NodeId: 7, ColumnName: Expr1002
CASE WHEN [Expr1004]=(0) THEN NULL ELSE [Expr1005] END
- Sehen Sie nicht, wie COUNT(*)
0 sein kann, also wird immer sum ( cume_weight
) ausgeführt.
- Knotenname: Segment, Knoten-ID: 7, Spaltenname: Segment1013 Nein
partition by
in der LEAD
ersten Zeile erhält 1. Alle verbleibenden erhalten null
- NodeName: Sequenzprojekt, NodeId: 6, ColumnName: RowNumber1006
row_number()
innerhalb der durch das Segment1013-Flag angegebenen Gruppe. Da sich alle Zeilen in derselben Gruppe befinden, sind dies ganze Zahlen von 1 bis 4
- NodeName: Segment, NodeId: 4, ColumnName: BottomRowNumber1008 RowNumber1006 + 1, da dies die
LEAD
nächste Zeile erfordert
- NodeName: Segment, NodeId: 4, ColumnName: TopRowNumber1007 RowNumber1006 + 1, da dies die
LEAD
nächste Zeile erfordert
- NodeName: Segment, NodeId: 4, ColumnName: Segment1014 Nein
partition by
in der LEAD
ersten Zeile erhält 1. Alle verbleibenden erhalten null
- NodeName: Window Spool, NodeId: 3, ColumnName: WindowCount1015 Attribut, das Zeilen, die zu einem Fensterrahmen gehören, unter Verwendung der vorherigen Zeilennummern gruppiert. Der Fensterrahmen für
LEAD
hat maximal 2 Zeilen (die aktuelle und die nächste)
- NodeName: Stream Aggregate, NodeId: 2, ColumnName: Expr1003
LAST_VALUE([Expr1002])
für dieLEAD(cume_weight)
Client statistics --> Total Execution Time
, nicht den,Actual execution plan
der hier wahrscheinlich am interessantesten ist. AbClient Statistics
Ihre Lösung ist ein klein wenig langsamer als Martin. Danke für die zusätzlichen Infos. Mit welcher Methode können Leistungsunterschiede zwischen verschiedenen Ansätzen gemessen werden?ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
einenSequence Project (Compute Scalar)
Operator eingeführt. Unnötig zu sagen, ich habe keine Ahnung, was das bedeutet :-)Sie können einen Join gegen sich selbst durchführen:
Diese Art von Dingen ist nicht sehr effizient, da sie eine Auswahl pro Zeile verursachen. Aber zumindest wird es als eine einzige Aussage ausgedrückt.
Wenn Sie dies nicht vollständig in SQL tun müssen, können Sie einfach alle Zeilen auswählen und sie durchlaufen.
Sie können dasselbe auch in einer gespeicherten Prozedur ohne die temporäre Tabelle tun. Halten Sie einfach die Summe und den Namen der letzten Zeile in einer Variablen.
quelle
self-join
soll. Wenn Sie ein kleines reproduzierbares Beispiel machen könnten, habe ich meiner Frage die Tabellendefinition hinzugefügt. Mein SQL ist schlecht ... Ich brauche den Namen der Person, die <= 1000 lbs am nächsten ist.COALESCE()
oderISNULL()
Funktion oder einenCASE
Ausdruck verwenden, um es 0 zu machen.