Parallele Masseneinfügung mit SqlBulkCopy und Azure




 Ich habe eine azurblaue App in der Cloud mit einer SQL Azure-Datenbank. Ich habe eine Arbeiterrolle, die Parsing + Verarbeitung für eine Datei (bis zu ~ 30 Millionen Zeilen) durchführen muss, so dass ich BCP oder SSIS nicht direkt verwenden kann. 

 Ich benutze derzeit SqlBulkCopy, aber das scheint zu langsam, da ich Ladezeiten von bis zu 4-5 Minuten für 400k Zeilen gesehen habe. 

 Ich möchte meine Bulk-Inserts parallel ausführen; Beim Lesen der Artikel über das Importieren von Daten in parallel / steuerndes Sperrverhalten wird jedoch darauf hingewiesen, dass SqlBulkCopy erfordert, dass die Tabelle keine gruppierten Indizes enthält und ein Tablelock (BU-Sperre) angegeben werden muss. Azure Tabellen müssen jedoch einen Clustered Index haben ... 

 Ist es sogar möglich, SqlBulkCopy in derselben Tabelle in SQL Azure parallel zu verwenden? Wenn nicht, gibt es eine andere API (die ich im Code verwenden kann), um dies zu tun?
Ich sehe nicht, wie Sie schneller als mit SqlBulkCopy ausführen können. Auf unserem Projekt können wir 250K Zeilen in ungefähr 3 Minuten importieren, also scheint Ihre Rate ungefähr richtig zu sein. 

 Ich denke nicht, dass es parallel dazu helfen würde, selbst wenn es technisch möglich wäre. Wir führen nur jeweils einen Import aus, ansonsten beginnt SQL Azure damit, unsere Anforderungen zu terminieren. 

 In der Tat ist es manchmal nicht möglich, eine große Group-by-Abfrage gleichzeitig mit dem Import auszuführen. SQL Azure führt eine Menge Arbeit aus, um die Servicequalität zu gewährleisten. Dazu gehört auch das Zeitlimit für Anforderungen, die zu lange dauern, zu viele Ressourcen usw 

 Wenn Sie also mehrere große Masseneinsätze zur selben Zeit machen, werden Sie *wahrscheinlich* eine Auszeit haben.
<p>hinweis: ab azure sql database v12 gelten diese einschränkungen nicht mehr.</p><p>das ist kein "primärindex". es gibt so etwas wie einen "primärschlüssel" und es gibt auch so etwas wie einen "clustered index". unterschiedliche konzepte, oft verwirrt. lassen sie uns angesichts dieser unterscheidung die frage noch einmal betrachten:</p><p>q1) kann der clustered-index in einer sql azure-tabelle geändert werden?<br> a: ja. verwenden sie <code>with (drop\_existing=on)</code>:</p><pre><code>create table friend ( userid int not null, id int not null); go create clustered index cdxfriend on friend (userid, id); go create clustered index cdxfriend on friend (id, userid) with (drop\_existing=on); go </code></pre><p>f2) kann der clustered-index einer tabelle mit einer primärschlüsseleinschränkung geändert werden?<br> a: ja, wie oben, solange die primärschlüsseleinschränkung nicht über den clustered-index erzwungen wird:</p><pre><code>create table friend ( userid int not null, id int not null identity(1,1), constraint pk\_friend primary key nonclustered (id)); create clustered index cdxfriend on friend (userid, id); go create clustered index cdxfriend on friend (id, userid) with (drop\_existing=on); go </code></pre><p>f3) kann die primärschlüsseleinschränkung einer tabelle geändert werden?<br> a: ja, solange die primäre einschränkung nicht über den clustered-index erzwungen wird:</p><pre><code>create table friend ( userid int not null, id int not null identity(1,1), constraint pk\_friend primary key nonclustered (id)); go create clustered index cdxfriend on friend (userid, id); go alter table friend drop constraint pk\_friend; alter table friend add constraint pk\_friend primary key nonclustered (userid) go </code></pre><p>f4) kann der primärschlüssel einer tabelle geändert werden, wenn er über den clustered-index erzwungen wird?<br> a: ja, wenn die tabelle nie zeilen hatte:</p><pre><code>create table friend ( userid int not null, id int not null identity(1,1), constraint pk\_friend primary key clustered (userid, id)); go alter table friend drop constraint pk\_friend; alter table friend add constraint pk\_friend primary key clustered (id, userid) go </code></pre><p>f5) kann der primärschlüssel einer tabelle geändert werden, wenn er über den clustered-index erzwungen wird, wenn die tabelle gefüllt ist?<br> a: nein. jede operation, die einen aufgefüllten clustered-index in einen heap konvertiert, wird in sql azure blockiert <em>, auch wenn die tabelle leer ist </em>:</p><pre><code>create table friend ( userid int not null, id int not null identity(1,1), constraint pk\_friend primary key clustered (userid, id)); go insert into friend (userid) values (1); delete from friend; go alter table friend drop constraint pk\_friend; </code></pre><p>als randnotiz: die einschränkung kann geändert werden, wenn die tabelle <em> abgeschnitten ist </em>.</p><p>die problemumgehung zum ändern der pk-einschränkung einer aufgefüllten tabelle besteht darin, das gute alte <a href="http://msdn.microsoft.com/en-us/library/ms188351.aspx"><code>sp\_rename</code></a> trick:</p><pre><code>create table friend ( userid int not null, id int not null identity(1,1), constraint pk\_friend primary key clustered (userid, id)); go insert into friend (userid) values (1); go create table friendnew ( userid int not null, id int not null identity(1,1), constraint pk\_friend\_new primary key clustered (id, userid)); go set identity\_insert friendnew on; insert into friendnew (userid, id) select userid, id from friend; set identity\_insert friendnew off; go begin transaction exec sp\_rename 'friend', 'friendold'; exec sp\_rename 'friendnew', 'friend'; commit; go sp\_help 'friend'; </code></pre><p>das <code>sp\_rename</code> ansatz hat einige probleme, vor allem, dass berechtigungen für die tabelle während des umbenennens nicht übertragen werden, sowie fremdschlüsseleinschränkungen.</p> <p>es gibt keine einfache antwort auf ihre frage, aber hier sind einige dinge, über die sie nachdenken sollten.</p><p>erstens ist die skalierung nicht das einzige, worüber man sich sorgen machen muss. was sie mit ihren daten machen, ist. wenn sie 500 tabellen mit 30 tb daten) haben und einfach oltp mit sehr wenig berichterstellung) arbeiten, werden sie wahrscheinlich nicht zu viele probleme haben es gibt 32-tb-datenbanken auf postgresql. gleichzeitig wird sich die leistung jedoch etwas verschlechtern, da die festplatte auf alles übertragen werden muss. wenn sie über 50 tb daten verfügen, aber häufig einen treffer von etwa 100 gb haben, können sie dies auch erstellen sie einen server mit genügend ram, um diesen teil der datenbank im speicher zu halten, und sie sind golden.</p><p>wenn sie jedoch versuchen, den modus (häufigster wert) aus 1 tb daten zu entfernen, spielt es keine rolle, welches system sie verwenden. dies wird <em> schmerzhaft sein </em> mit oder ohne scherben. (bearbeiten: sharding kann dieses problem <em> tatsächlich verschlimmern. </em>)</p><p>die hauptprobleme, auf die sie mit riesigen datenbanken unter mysql und postgresql stoßen werden, sind die tatsache, dass keine der beiden parallelen zwischen abfragen unterstützt. mit anderen worten, eine abfrage wird von einem einzelnen thread als einzelner block ausgeführt und kann nicht in teile zerlegt und separat ausgeführt werden. dies ist meistens ein problem, wenn große analytische abfragen über große datenmengen ausgeführt werden. hier kommen postgres-xc und green plum zur rettung, da sie speicher von ausführung trennen und dies auf koordinatorebene tun können. beachten sie, dass postgres-xc und green plum im wesentlichen intern sharding verwenden, die koordinatoren jedoch die gesamte konsistenz global erzwingen.</p><p>mit intraquery-parallelität können sie die abfrage aufteilen, verschiedene prozessoren/festplatten-e/a-kanäle teile davon ausführen lassen und teile der ergebnismenge zurückmelden, die zusammengestellt und an die anwendung zurückgegeben werden sollen. auch dies ist normalerweise am hilfreichsten bei analytischen und nicht bei transaktionsverarbeitungslasten.</p><p>die zweite sache ist, dass einige systeme wie vertica oder greenplum informationsspalten zusammen speichern. dies erschwert die verwendung des systems aus der perspektive von oltp] und verringert die leistung dort, erhöht jedoch die leistung für große analytische workloads drastisch. dies ist also ein workload-spezifischer kompromiss.</p><p>die antwort ist also, dass sie <em>, sobald sie über 1-2 tb in der größe) </em> stehen, möglicherweise mit einer zahl konfrontiert werden dies ist wiederum spezifisch für datenbanken, größe der arbeitssätze usw. an diesem punkt müssen sie sich jedoch wirklich für schneeflockensysteme entscheiden, dh solche, die einzigartig und auf ihre arbeitslast zugeschnitten sind.</p><p>dies bedeutet natürlich, dass die grenzwerte im allgemeinen nicht quantifizierbar sind.</p><p><strong>bearbeiten</strong>: ich habe jetzt mit einer 9-tb-datenbank gearbeitet, die eine mischung aus entscheidungsunterstützung und transaktionsverarbeitungs-workloads in postgresql verarbeitet. die größte herausforderung besteht darin, dass sie bei fragen, die große teile des datensatzes betreffen, eine weile auf die antwort warten müssen.</p><p>bei sorgfältiger beachtung der grundlagen (einschließlich indizes, autovakuum, funktionsweise auf niedrigem niveau usw.) und ausreichender rechenressourcen sind diese jedoch vollständig verwaltbar (und ich schätze, dass sie bis weit in den 30-tb-bereich in pg verwaltbar sind).</p><p><strong>edit2</strong>: sobald sie 100 tb erreicht haben, hängt es von ihrem datensatz ab, was funktioniert. ich arbeite gerade an einem, der nicht in diesen bereich skaliert, da er zuerst das limit von 32 tb pro tabelle in postgresql erreicht.</p> <p>es gibt optionen, die ihnen dabei helfen:</p><pre><code> --insert-ignore insert rows with insert ignore. --replace use replace into instead of insert into. -t, --no-create-info don't write table creation info. </code></pre><p>denken sie an dieses paradigma</p><ul> <li>mysqldump alles von db1 in dump1</li> <li>laden sie dump1 in db3</li> <li>mysqldump alles aus db2 mit --replace (oder --insert-ignore) und --no-create-info in dump2</li> <li>laden sie dump2 in db3</li> </ul> <p>für amazon cloud drive wurde eine hervorragende lösung verfügbar, die wunderbar funktioniert! die von amazon bereitgestellte schnittstelle zu amazon cloud drive lässt zu wünschen übrig. es gibt ein webinterface und eine pc-anwendung, von denen keine sehr gut funktioniert, außer dass sie einmalig auf dem server platziert werden.</p><p>syncbackpro hat kürzlich eine amazon cloud drive-schnittstelle implementiert. dies unterscheidet sich von ihren amazon s3- und amazon glacier-schnittstellen. ich kann nicht glauben, wie gut es funktioniert. es ist etwas langsam, das hochladen von dateien nach dateien zu starten, aber sobald die verarbeitung einer datei beginnt, wird die verbindungsgrenze überschritten. syncbackpro hat eine hervorragende rechenschaftspflicht gegenüber dateien, die mit einer guten visuellen darstellung und einer guten protokollierung verarbeitet wurden. es ist eine sehr schöne software. ich mag die "spiegel" -funktion, die nach bedarf vom ziel gelöscht und zum ziel hinzugefügt wird, um der quelle zu entsprechen.</p><p>das setup war so einfach. ich habe lediglich ein "alles" -konto für amazon cloud drive erstellt. syncbackpro hat mich zu einer einmaligen amazon-authentifizierungsseite geführt, ein schlüssel wurde ausgetauscht, und das war es mit dem geringstmöglichen interaktionsaufwand.</p><p>amazon cloud drive "everything" kostet 59,99 us-dollar pro jahr und akzeptiert laut kundensupport bis zu 200 terabyte im rahmen dieses plans. ich bin sehr froh, dass ich durchgehalten habe. dies scheint das beste preis-leistungs-verhältnis zu sein. syncbackpro kostet 54,95 usd.</p> <p>im allgemeinen ist der schnellste weg, eine tabelle zu duplizieren, einfach:</p><pre><code>create table table2 as select * from table1; </code></pre><p>parallele inserts sind möglicherweise schneller, jedoch nur mit einem sehr schnellen festplattensubsystem (wenn daten auf vielen laufwerken verschachtelt sind). andernfalls ist dies langsamer.</p><p>sobald sie mit dem ändern von <code>table2</code> fertig sind, kann es den neuen namen annehmen mit:</p><pre><code>begin; drop table table1; alter table table2 rename to table1; commit; </code></pre><p>der befehl <code>drop table</code> benötigt eine exklusive sperre, die sich auf gleichzeitige leser auf eine weise auswirkt, die sie vielleicht vorhersehen möchten:</p><ul> <li><code>drop</code> wartet, bis alle ausstehenden lesevorgänge anderer transaktionen in der tabelle abgeschlossen sind.</li> <li>jede neue transaktion, die in der zwischenzeit versucht, diese tabelle zu lesen, wird gewartet und schlägt dann fehl, da das ursprüngliche <code>table1</code> nicht mehr vorhanden ist. der fehler würde so aussehen: "beziehung mit oid <em>oid</em> konnte nicht geöffnet werden")</li> </ul><p>um das zweite problem zu vermeiden, können sie <code>table1</code> in <code>old\_table1</code> umbenennen, anstatt es zu löschen, und es dann erst später außerhalb der transaktion löschen, wenn diese leser damit fertig sind. die obige sequenz würde also werden:</p><pre><code>begin; alter table table1 rename to old\_table1; alter table table2 rename to table1; commit; ... drop table old\_table1; </code></pre> <p>bitte führen sie diese abfrage aus:</p><pre><code>select data\_bb / power(1024,1) data\_kb, data\_bb / power(1024,2) data\_mb, data\_bb / power(1024,3) data\_gb from (select sum(data\_length) data\_bb from information\_schema.tables where table\_schema not in ('information\_schema','performance\_schema','mysql')) a; </code></pre><p>dies gibt ihnen eine baseball-figur. die kolumne <code>index\_length</code> wird nicht verwendet, da mysqldump keine indizes, sondern nur daten ausgibt. um sicher zu gehen, sollten sie es immer sofort gzip:</p><pre><code>mysqldump --all-databases --routines --triggers | gzip &gt; mysqldata.sql.gz </code></pre><p>versuche es !!!</p> <p>ja, mysql kann (unter bestimmten bedingungen) einen index für die spalten in order by verwenden. mysql kann jedoch keinen index für <em> gemischt</em> asc, desc-reihenfolge nach (<code>select * from foo order by bar asc, pants desc</code>). wenn sie ihre anfrage und die anweisung create table teilen, können wir ihre frage genauer beantworten.</p><p>hinweise zum optimieren der order by-klauseln: <a href="http://dev.mysql.com/doc/refman/5.1/en/order-by-optimization.html">http://dev.mysql.com/doc/refman/5.1/en/order-by-optimization.html</a> </p><p><strong> bearbeiten 21.01.2012 08:53 </strong></p><p>es gab fragen zur quelle meiner aussage zur verwendung eines index mit gemischtem asc/desc im order by. aus der dokumentation <a href="http://dev.mysql.com/doc/refman/5.5/en/order-by-optimization.html">order by optimization</a> :</p><blockquote> <p>in einigen fällen kann mysql keine indizes verwenden, um order by aufzulösen, obwohl es weiterhin indizes verwendet, um die zeilen zu finden, die mit der where-klausel übereinstimmen. diese fälle umfassen folgendes:</p> <p>...</p> <p>sie mischen asc und desc:</p> <p>select * from t1 order by key\_part1 desc, key\_part2 asc;</p> <p>...</p> </blockquote><p>außerdem lautet das desc-schlüsselwort <a href="http://dev.mysql.com/doc/refman/5.5/en/create-index.html">bedeutungslos</a> in create index</p><blockquote> <p>eine index\_col\_name-spezifikation kann mit asc oder desc enden. diese schlüsselwörter sind für zukünftige erweiterungen zur angabe des speichers für aufsteigende oder absteigende indexwerte zulässig. derzeit werden sie analysiert, aber ignoriert. indexwerte werden immer in aufsteigender reihenfolge gespeichert.</p> </blockquote> <p><strong>einfache methode</strong></p><p>sie können ein bash-skript einrichten, das diesen befehl in einer endlosschleife ausführt und dann 5 minuten ruht.</p><p>art</p><pre><code>sudo nano /usr/local/bin/amazon-sync </code></pre><p>art</p><pre><code>#!/bin/sh while true do acd\_cli sync sleep 300 done </code></pre><p>drücken sie <kbd>ctrl</kbd>+<kbd>o</kbd></p><p>drücken sie <kbd>ctrl</kbd>+<kbd>x</kbd></p><p>art</p><pre><code>sudo chmod +x /usr/local/bin/amazon-sync </code></pre><p>wenn sie ihren computer starten, drücken sie <kbd>ctrl</kbd>+<kbd>alt</kbd>+<kbd>t</kbd> geben sie <code>amazon-sync</code> ein und minimieren sie das terminalfenster. der befehl wird alle 5 minuten (300 sekunden) ausgeführt.</p> <p>sie können das meiste davon tun, indem sie eine login.sql verwenden. login.sql wird während der - überraschenden - anmeldung ausgeführt und aus ihrem sqlpath oder dem aktuellen verzeichnis geladen. für die beispiele, die sie gegeben haben, haben sie wirklich den schlimmsten fall gewählt.</p><p>problem ist der sqlterminator. was auch immer sie dort eingeben, der schrägstrich wird als kostenloser sql-minator beibehalten. daneben sucht sqlplus zuerst nach dem sqlterminator und führt dies vor dem scannen zum string-terminator durch. ein fehler, wenn sie mich fragen. der schrägstrich kann in einer zeichenfolge verwendet werden, solange er nicht allein in einer separaten zeile steht. sobald sqlplus das als sqlterminator angegebene zeichen findet, ignoriert es alles andere und hört auf zu lesen.</p><p>der schrägstrich kann behandelt werden, solange er nicht alleine in einer zeile steht.</p><p>login.sql enthält:</p><pre><code>prompt run login.sql show sqlterminator show sqlblanklines set sqlblanklines on set sqlterminator ';' show sqlterminator show sqlblanklines prompt ready login.sql set echo on </code></pre><p>leigh.sql enthält:</p><pre><code>insert into t1 values ('fail bc semicolon a;a / '); insert into t1 values ('fail bc solo / aa / '); insert into t1 values ('ok / not solo aa /a '); delete from t1 where a = 'c a/ '; </code></pre><p>führen sie das skript aus:</p><pre><code>sqlplus leigh/leigh@orcl @leigh sql*plus: release 10.2.0.4.0 - production on thu aug 9 22:36:20 2012 copyright (c) 1982, 2007, oracle. all rights reserved. connected to: oracle database 11g enterprise edition release 11.2.0.3.0 - 64bit production with the partitioning, real application clusters, automatic storage management, olap, data mining and real application testing options run login.sql sqlterminator ";" (hex 3b) sqlblanklines off sqlterminator ";" (hex 3b) sqlblanklines on ready login.sql sql&gt; insert into t1 values ('fail bc semicolon 2 a;a 3 / error: ora-01756: quoted string not properly terminated sql&gt; '); sp2-0042: unknown command "')" - rest of line ignored. sql&gt; sql&gt; insert into t1 values ('fail bc solo / 2 3 4 aa 5 / error: ora-01756: quoted string not properly terminated sql&gt; '); sp2-0042: unknown command "')" - rest of line ignored. sql&gt; sql&gt; insert into t1 values ('ok / not solo 2 3 4 aa 5 /a 6 '); 1 row created. sql&gt; sql&gt; delete from t1 where a = 'c 2 3 4 a/ 5 '; 0 rows deleted. </code></pre><p>sie müssen nicht mit start-/endblöcken herumspielen. der befehl sqlterminator innerhalb des befehls kann nicht verarbeitet werden, unabhängig davon, wo er sich in einer zeichenfolge befindet oder nicht. er kann keine zeilen mit schrägstrich allein in einer zeile in einer zeichenfolge verarbeiten.</p> <p>sie sollten versuchen, schleifen generell zu vermeiden. sie sind normalerweise weniger effizient als satzbasierte lösungen und weniger lesbar.</p><p>das folgende sollte ziemlich effizient sein.</p><p>dies gilt umso mehr, wenn die spalten name und weight im index <code>include-</code> d sein könnten, um die schlüsselsuche zu vermeiden.</p><p>es kann den eindeutigen index in der reihenfolge <code>turn</code> scannen und die laufende summe der spalte <code>weight</code> berechnen. verwenden sie dann <code>lead</code> mit denselben ordnungskriterien, um die laufende summe zu ermitteln die nächste reihe wird sein.</p><p>sobald die erste zeile gefunden wird, in der diese 1000 überschreitet oder <code>null</code> ist (was anzeigt, dass keine nächste zeile vorhanden ist), kann der scan gestoppt werden.</p><pre><code>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 &gt; 1000 or next\_cume\_weight is null order by turn </code></pre><h1>ausführungsplan</h1><p> <a href="https://i.stack.imgur.com/gb1mw.png">(<img src="https://i.stack.imgur.com/gb1mw.png" alt="enter image description here"></a> </p><p>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.</p><p>für die beispieldaten in der frage müssten im idealfall nur zwei zeilen aus dem index-scan gelesen werden, in wirklichkeit werden jedoch 6 zeilen gelesen. dies ist jedoch kein wesentliches effizienzproblem und verschlechtert sich nicht, wenn der tabelle weitere zeilen hinzugefügt werden (wie in) <a href="https://dbfiddle.uk/?rdbms=sqlserver\_2017&amp;fiddle=892e6bb7c1af912413b01d05e606a98a">diese demo</a> )</p><p>für diejenigen, die an dieser ausgabe interessiert sind, wird unten ein bild mit den von jedem operator ausgegebenen zeilen (wie durch das erweiterte ereignis <code>query\_trace\_column\_values</code> gezeigt) angezeigt. die zeilen werden in der reihenfolge <code>row\_id</code> ausgegeben (beginnend mit <code>47</code> für die erste zeile, die vom index-scan gelesen wurde und bei <code>113</code> für <code>top</code> endet)</p><p>klicken sie auf das bild unten, um es zu vergrößern, oder sehen sie alternativ <a href="https://imgur.com/a/c7npqqh">die animierte version, um den ablauf einfacher zu verfolgen</a> .</p><p>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 einem anderen <code>turn</code> gelesen hat (für thomas - turn = 3)</p><p>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.</p><p> <a href="https://i.stack.imgur.com/cwllc.jpg">(<img src="https://i.stack.imgur.com/cwllc.jpg" alt="enter image description here"></a> </p><p>es folgt eine erläuterung der oben gezeigten spalten (basierend auf info <a href="https://www.microsoft.com/en-us/p/microsoft-sql-server-2012-high-performance-t-sql-using-window-functions/fgqpf3h0qlch">hier</a> )</p><ul> <li><strong> knotenname: index-scan, knoten-id: 15, spaltenname: id </strong> basistabellenspalte, die vom index abgedeckt wird</li> <li><strong> knotenname: index-scan, knoten-id: 15, spaltenname: drehen sie die basistabellenspalte </strong>, die vom index abgedeckt wird</li> <li><strong> nodename: clustered index seek, nodeid: 17, columnname: gewicht </strong> basistabellenspalte, die aus der suche abgerufen wurde</li> <li><strong> nodename: clustered index seek, nodeid: 17, columnname: name </strong> basistabellenspalte, die aus der suche abgerufen wurde</li> <li><strong> nodename: segment, nodeid: 13, columnname: segment1010 </strong> gibt 1 am anfang einer neuen gruppe zurück oder andernfalls null. da kein <code>partition by</code> in <code>sum</code> vorhanden ist, erhält nur die erste zeile 1</li> <li><strong> nodename: sequenzprojekt, nodeid: 12, columnname: rownumber1009 </strong> <code>row\_number()</code> 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 <code>rows between 5 preceding and 2 following</code> zum filtern von zeilen mit rechten rahmen verwendet. (oder wie für <code>lead</code> später)</li> <li><strong> nodename: segment, nodeid: 11, columnname: segment1011 </strong> gibt 1 am anfang einer neuen gruppe zurück oder andernfalls null. da kein <code>partition by</code> in <code>sum</code> vorhanden ist, erhält nur die erste zeile 1 (wie segment1010).</li> <li><strong> nodename: window spool, nodeid: 10, columnname: windowcount1012 </strong> attribut, das zeilen gruppiert, die zu einem fensterrahmen gehören. diese fensterspule verwendet den fall "fast track" für <code>unbounded preceding</code>. wo es zwei zeilen pro quellzeile ausgibt. eins mit den kumulativen werten und eins mit den detailwerten. obwohl es keinen sichtbaren unterschied in den durch <code>query\_trace\_column\_values</code> offenbarten zeilen gibt, gehe ich davon aus, dass kumulative spalten in der realität vorhanden sind.</li> <li><strong> nodename: stream aggregate, nodeid: 9, columnname: expr1004 </strong> <code>count(*)</code> gruppiert nach windowcount1012 gemäß plan, aber tatsächlich eine laufende anzahl</li> <li><strong> nodename: stream aggregate, nodeid: 9, columnname: expr1005 </strong> <code>sum(weight)</code> gruppiert nach windowcount1012 gemäß plan, aber tatsächlich die laufende gewichtssumme (dh <code>cume\_weight</code>)</li> <li><strong> nodename: segment, nodeid: 7, columnname: expr1002 </strong> <code>case when [expr1004]=(0) then null else [expr1005] end</code> - sehen sie nicht, wie <code>count(*)</code> kann sei 0, also läuft immer die summe (<code>cume\_weight</code>)</li> <li><strong> knotenname: segment, knoten-id: 7, spaltenname: segment1013 </strong> nein <code>partition by</code> in <code>lead</code>, sodass die erste zeile 1 erhält. alle verbleibenden werden null</li> <li><strong> nodename: sequenzprojekt, nodeid: 6, columnname: rownumber1006 </strong> <code>row\_number()</code> innerhalb der durch das segment1013-flag angegebenen gruppe. da sich alle zeilen in derselben gruppe befinden, sind dies ganze zahlen von 1 bis 4</li> <li><strong> nodename: segment, nodeid: 4, columnname: bottomrownumber1008 </strong> rownumber1006 + 1, da <code>lead</code> die nächste zeile erfordert</li> <li><strong> nodename: segment, nodeid: 4, columnname: toprownumber1007 </strong> rownumber1006 + 1, da <code>lead</code> die nächste zeile erfordert</li> <li><strong> knotenname: segment, knoten-id: 4, spaltenname: segment1014 </strong> nein <code>partition by</code> in <code>lead</code>, sodass die erste zeile 1 erhält. alle verbleibenden werden null</li> <li><strong> nodename: window spool, nodeid: 3, columnname: windowcount1015 </strong> attribut, das zeilen, die zu einem fensterrahmen gehören, unter verwendung der vorherigen zeilennummern gruppiert. der fensterrahmen für <code>lead</code> hat maximal 2 zeilen (die aktuelle und die nächste)</li> <li><strong> nodename: stream aggregate, nodeid: 2, columnname: expr1003 </strong> <code>last\_value([expr1002])</code> für <code>lead(cume\_weight)</code></li> </ul>