Wenn Sie eine benutzerdefinierte Aggregatfunktion erstellen, müssen Sie Folgendes angeben: Aufzählungsformat :
Format Enumeration wird von SqlUserDefinedTypeAttribute und verwendet SqlUserDefinedAggregateAttribute, um das Serialisierungsformat anzugeben eines benutzerdefinierten Typs (UDT) oder Aggregats.
und wenn UserDefined
format verwendet wird, muss Ihre Klasse IBinarySerialize Interface und überschreiben die Methoden read
und write
.
Meine Frage ist, was genau diese Methoden tun müssen?
Wenn Sie sich die Beispiele ansehen, sollten sie die Aggregation lesen / schreiben können Ergebnis?
Ich versuche beispielsweise, eine SQL CLR-Funktion zu erstellen, die verschiedene Zahlen verkettet. Im T-SQL kann ich 1 bis 255 eindeutige Zahlen (TINYINT-Wert) haben. Ich muss eine Zeichenfolge von ihnen erstellen (mit Begrenzer), aber auch die Zahlen sortieren. Die Funktion scheint zu funktionieren, aber ich bin mir nicht sicher, ob ich die Methoden wie erwartet überschrieben habe:
%Vor% Eine bestimmte Instanz des benutzerdefinierten Aggregats (User-Defined Aggregate, UDA) ist nicht garantiert während der gesamten Lebensdauer der Abfrage vorhanden. Es muss eine speicherbare Darstellung haben. Wenn Sie nur Werttypen verwenden (wie im Link "Aufzählungsformat" in der Frage angegeben), verstehen die bereitgestellten Methoden Read
und Write
, wie Sie die UDA serialisieren und deserialisieren. In diesem Fall würden Sie Format.Native
verwenden. Wenn Sie jedoch Referenztypen (String, Sammlungen, benutzerdefinierte Typen usw.) verwenden, müssen Sie festlegen, wie diese Werte serialisiert und deserialisiert werden. In diesem Fall müssen Sie Format.UserDefined
verwenden und die Read
und Write
Methoden, damit Sie diese Operationen steuern können.
Die Werte, die serialisiert werden müssen, sind alles, was benötigt wird, um die neue Instanz der UDA wieder in den Zustand exact zu bringen, in dem sie sich vor der Serialisierung befand. Das bedeutet: Verlassen Sie sich nicht auf die Init () -Methode, die ausgeführt wird (es wird einmal pro Gruppe ausgeführt!), Oder auf Variableninitialisierungen (sie werden einmal pro Instanz ausgeführt, und die UDA kann für mehrere Gruppen wiederverwendet werden, ohne sie neu zu erstellen!). Sie müssen also alle Basiswerte serialisieren, auch wenn sie nicht direkt mit der endgültigen Ausgabe zusammenhängen.
Das heißt, Sie sollten zumindest die in @ Damien_The_Unbelievers Antwort notierten Optimierungen tun:
Führen Sie die Sortierung nicht in der Write
-Methode durch. Sie tun es bereits in der Terminate
-Methode (dem entsprechenden Ort), also ist es sinnlos, zweimal zu machen, ganz zu schweigen von sehr ineffizient.
Speichern Sie die Anzahl der Sammlung und dann die einzelnen Elemente
Darüber hinaus:
Wenn Sie sagen, dass Ihr UDA "eindeutige Zahlen verkettet", wenn Sie wirklich "verschieden" gemeint haben, dann müssen Sie jede Zahl überprüfen, wenn sie eingeht, um zu sehen, ob sie bereits in der Liste ist. Ich vermute, das ist dein Wunsch, da du IsInvariantToDuplicates
auf wahr gesetzt hast. Sie würden dies in der Accumulate
-Methode tun:
und in der Merge
-Methode (aufgerufen, wenn Parallelismus beteiligt ist):
Bitte beachten Sie, dass ich Ihre Besetzung - (byte)value
- in der Methode Accumulate
in die Eigenschaft Value
geändert habe. Alle SqlTypes
(z. B. SqlByte
, SqlString
, SqlInt32
usw.) haben eine Eigenschaft Value
, die den zu erwartenden .NET-Typ zurückgibt. Dies bedeutet, dass es nicht notwendig ist, ToString()
auf einem SqlString
aufzurufen, wie es viele Leute tun.
Ich wäre vorsichtig mit einem MaxByteSize
von 1024. Diese Bedenken würden durch die Implementierung von @ Damiens Vorschlägen teilweise gelindert werden, da das Speichern von "165; 207" in einer Zeichenkette (aktuelle Methode) technisch 14 Bytes (7 Zeichen * 2 Bytes pro Zeichen), während das Speichern der Zählung und der einzelnen Bytes nur 6 Bytes lang ist (4 Bytes für das Int32, um count
+ 2 einzelne Bytes zu speichern). Und diese Disparität dient nur zum Speichern von 2 Werten. Speichern von 200? Yeesh!
Sie haben nicht die IsNullIfEmpty Eigenschaft angegeben. Sie müssen dies angeben, insbesondere in Anbetracht der Tatsache, dass Ihre Methode Terminate
eine leere Zeichenfolge zurückgibt, wenn die interne Auflistung null
ist. Sie sollten IsNullIfEmpty = false
hinzufügen, da Sie NULL
nicht zurückgeben möchten, wenn dies nie aufgerufen wird.
Die zusätzliche Logik in der Terminate
-Methode zur Behandlung einer null
-Auflistung ist wahrscheinlich nicht notwendig. Die Sammlung wird in den Methoden Init
und Read
initialisiert, so dass ich nicht sicher bin, wie es null
jemals sein könnte, wenn Terminate
aufgerufen wird.
Wenn Sie beispielsweise ein benutzerdefiniertes Aggregat mit Format.UserDefined
erstellen möchten, werfen Sie einen Blick auf Optimale Nutzung von SQL Server 2005-UDTs und -UDAs (kostenlose Registrierung erforderlich). Ich schrieb, dass vor dem Herauskommen von SQL Server 2008, das mehr als 8000 Byte serialisieren ließ, Sie (im Moment) die Aspekte bezüglich der Komprimierung der zu serialisierenden Daten ignorieren können.
Wenn Sie mehr über SQLCLR im Allgemeinen erfahren möchten, schreibe ich darüber eine Serie für SQL Server Central: Stairway an SQLCLR (gleiche Seite wie der erste verlinkte Artikel).
Ich würde sagen, dass du mehr Arbeit in deinen Methoden machst, als du brauchst. Alles, was Sie tun müssen, ist genug in die Methode Write
zu schreiben, damit Ihre Read
-Methode Ihren internen Zustand rekonstruieren kann. Da Ihr interner Zustand nur ein List<byte>
ist, müssen Sie nicht alles als Strings behandeln:
Und wie ich in den Kommentaren vorgeschlagen habe, habe ich auch Sort
aus der Methode Write
entfernt, da es immer einen letzten Sort
-Aufruf in Terminate
gibt, bevor die Daten erstellt wurden an die Verbraucher Ihres Aggregats weitergegeben.
Wir speichern die Count
zuerst in den Daten, so dass wir wissen, wie oft ReadByte
in der Methode Read
aufgerufen wird. Dies ermöglicht auch eine (wahrscheinlich sinnlose) Optimierung, die wir dem List<byte>
-Konstruktor genau sagen können, für wie viele Elemente er Platz benötigt.
Ihr Aggregat kann serialisiert und gelöscht werden, während es die Zeilen, auf denen es ausgeführt wird, zur Hälfte durchläuft. Das Datenbankmodul kann dann eine neue Instanz erstellen und deserialisieren, um dorthin zurückzukehren, wo es aufgehört hat.
Daher muss die Methode Write
in der Lage sein, den Status des Aggregats zu speichern, wenn nur einige der Datensätze an Accumulate
übergeben wurden. Die Methode Read
muss in der Lage sein, das Aggregat für weitere Aufrufe in Accumulate
oder Merge
wieder bereit zu machen.
Als solches würde ich sagen, dass Sie diese korrekt implementiert haben.
Die Methoden IBianarySerialize
dienen zum Speichern Ihres Objekts und Wiederherstellen für den Fall, dass es auf die Festplatte geschrieben werden muss.
Daher sollte die Methode Write
funktionieren Speichern Sie alles, was benötigt wird, um das Objekt in seinem aktuellen Zustand (den Daten) und dem Read sollte die Ausgabe von Write übernehmen und den Status des Objekts festlegen (damit es mit dem Original übereinstimmt).
Die anderen Antworten scheinen ziemlich gut darin zu sein, die Probleme mit diesem Prozess anzugehen, obwohl ich empfehle, die Daten, die Sie lesen / schreiben, mit diesen Methoden so klein und schnell wie möglich zu halten.
Tags und Links sql .net sql-server c# sqlclr