Aufrufen von benutzerdefinierten Aggregatfunktionen durch CLR
Gilt für:SQL Server
In Transact-SQL SELECT
Anweisungen können Sie benutzerdefinierte Common Language Runtime (CLR)-Aggregate aufrufen, die allen Regeln unterliegen, die für Systemaggregatfunktionen gelten.
Es gelten folgende zusätzliche Regeln:
Der aktuelle Benutzer muss über
EXECUTE
Berechtigung für das benutzerdefinierte Aggregat verfügen.Benutzerdefinierte Aggregate müssen mithilfe eines zweiteiligen Namens in Form von <schema_name>aufgerufen werden.<udagg_name>.
Der Argumenttyp des benutzerdefinierten Aggregats muss mit der input_type des Aggregats übereinstimmen oder implizit konvertierbar sein, wie in der
CREATE AGGREGATE
-Anweisung definiert.Der Rückgabetyp des benutzerdefinierten Aggregats muss mit dem return_type in der
CREATE AGGREGATE
-Anweisung übereinstimmen.
Beispiele
Ein. Benutzerdefiniertes Aggregat verkettet Zeichenfolgenwerte
Der folgende Code ist ein Beispiel für eine benutzerdefinierte Aggregatfunktion, die einen Satz von Zeichenfolgenwerten verkettet, die aus einer Spalte in einer Tabelle stammen:
using System;
using System.Data;
using Microsoft.SqlServer.Server;
using System.Data.SqlTypes;
using System.IO;
using System.Text;
[Serializable]
[SqlUserDefinedAggregate(
Format.UserDefined, //use clr serialization to serialize the intermediate result
IsInvariantToNulls = true, //optimizer property
IsInvariantToDuplicates = false, //optimizer property
IsInvariantToOrder = false, //optimizer property
MaxByteSize = 8000) //maximum size in bytes of persisted value
]
public class Concatenate : IBinarySerialize
{
/// <summary>
/// The variable that holds the intermediate result of the concatenation
/// </summary>
public StringBuilder intermediateResult;
/// <summary>
/// Initialize the internal data structures
/// </summary>
public void Init()
{
this.intermediateResult = new StringBuilder();
}
/// <summary>
/// Accumulate the next value, not if the value is null
/// </summary>
/// <param name="value"></param>
public void Accumulate(SqlString value)
{
if (value.IsNull)
{
return;
}
this.intermediateResult.Append(value.Value).Append(',');
}
/// <summary>
/// Merge the partially computed aggregate with this aggregate.
/// </summary>
/// <param name="other"></param>
public void Merge(Concatenate other)
{
this.intermediateResult.Append(other.intermediateResult);
}
/// <summary>
/// Called at the end of aggregation, to return the results of the aggregation.
/// </summary>
/// <returns></returns>
public SqlString Terminate()
{
string output = string.Empty;
//delete the trailing comma, if any
if (this.intermediateResult != null
&& this.intermediateResult.Length > 0)
{
output = this.intermediateResult.ToString(0, this.intermediateResult.Length - 1);
}
return new SqlString(output);
}
public void Read(BinaryReader r)
{
intermediateResult = new StringBuilder(r.ReadString());
}
public void Write(BinaryWriter w)
{
w.Write(this.intermediateResult.ToString());
}
}
Nachdem Sie den Code in MyAgg.dll
kompiliert haben, können Sie das Aggregat in SQL Server wie folgt registrieren:
CREATE ASSEMBLY MyAgg
FROM 'C:\MyAgg.dll';
GO
CREATE AGGREGATE MyAgg(@input NVARCHAR (200))
RETURNS NVARCHAR (MAX)
EXTERNAL NAME MyAgg.Concatenate;
Hinweis
Visual C++-Datenbankobjekte, z. B. skalare Wertfunktionen, die mit der Compileroption /clr:pure
kompiliert wurden, werden für die Ausführung in SQL Server nicht unterstützt.
Wie bei den meisten Aggregaten befindet sich der Großteil der Logik in der Accumulate
-Methode. Hier wird die Zeichenfolge, die als Parameter an die Accumulate
-Methode übergeben wird, an das StringBuilder
-Objekt angefügt, das in der Init
-Methode initialisiert wurde. Angenommen, die Accumulate
-Methode wurde noch nicht aufgerufen, wird auch ein Komma an die StringBuilder
angefügt, bevor die übergebene Zeichenfolge angefügt wird. Am Ende der Rechenvorgänge wird die Terminate
Methode aufgerufen, die die StringBuilder
als Zeichenfolge zurückgibt.
Angenommen, eine Tabelle mit folgendem Schema liegt vor:
CREATE TABLE BookAuthors
(
BookID INT NOT NULL,
AuthorName NVARCHAR (200) NOT NULL
);
Fügen Sie dann die folgenden Zeilen ein:
INSERT BookAuthors
VALUES
(1, 'Johnson'),
(2, 'Taylor'),
(3, 'Steven'),
(2, 'Mayler'),
(3, 'Roberts'),
(3, 'Michaels');
Die folgende Abfrage würde dann zum folgenden Ergebnis führen:
SELECT BookID, dbo.MyAgg(AuthorName)
FROM BookAuthors
GROUP BY BookID;
BookID | Author Names |
---|---|
1 |
Johnson |
2 |
Taylor, Mayler |
3 |
Roberts, Michaels, Steven |
B. Benutzerdefiniertes Aggregat mit zwei Parametern
Das folgende Beispiel zeigt ein Aggregat mit zwei Parametern für die Accumulate
-Methode.
using System;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;
[Serializable]
[SqlUserDefinedAggregate(
Format.Native,
IsInvariantToDuplicates = false,
IsInvariantToNulls = true,
IsInvariantToOrder = true,
IsNullIfEmpty = true,
Name = "WeightedAvg")]
public struct WeightedAvg
{
/// <summary>
/// The variable that holds the intermediate sum of all values multiplied by their weight
/// </summary>
private long sum;
/// <summary>
/// The variable that holds the intermediate sum of all weights
/// </summary>
private int count;
/// <summary>
/// Initialize the internal data structures
/// </summary>
public void Init()
{
sum = 0;
count = 0;
}
/// <summary>
/// Accumulate the next value, not if the value is null
/// </summary>
/// <param name="Value">Next value to be aggregated</param>
/// <param name="Weight">The weight of the value passed to Value parameter</param>
public void Accumulate(SqlInt32 Value, SqlInt32 Weight)
{
if (!Value.IsNull && !Weight.IsNull)
{
sum += (long)Value * (long)Weight;
count += (int)Weight;
}
}
/// <summary>
/// Merge the partially computed aggregate with this aggregate
/// </summary>
/// <param name="Group">The other partial results to be merged</param>
public void Merge(WeightedAvg Group)
{
sum += Group.sum;
count += Group.count;
}
/// <summary>
/// Called at the end of aggregation, to return the results of the aggregation.
/// </summary>
/// <returns>The weighted average of all inputed values</returns>
public SqlInt32 Terminate()
{
if (count > 0)
{
int value = (int)(sum / count);
return new SqlInt32(value);
}
else
{
return SqlInt32.Null;
}
}
}
Führen Sie nach dem Kompilieren des C#- oder Visual Basic .NET-Quellcodes die folgenden Transact-SQL-Dateien aus. In diesem Skript wird vorausgesetzt, dass die DLL WghtAvg.dll heißt und sich im Stammverzeichnis von Laufwerk C: befindet. Außerdem muss die Datenbank test vorhanden sein.
USE test;
GO
-- EXECUTE sp_configure 'clr enabled', 1;
-- RECONFIGURE WITH OVERRIDE;
-- GO
IF EXISTS (SELECT name
FROM systypes
WHERE name = 'MyTableType')
DROP TYPE MyTableType;
GO
IF EXISTS (SELECT name
FROM sysobjects
WHERE name = 'WeightedAvg')
DROP AGGREGATE WeightedAvg;
GO
IF EXISTS (SELECT name
FROM sys.assemblies
WHERE name = 'MyClrCode')
DROP ASSEMBLY MyClrCode;
GO
CREATE ASSEMBLY MyClrCode
FROM 'C:\WghtAvg.dll';
GO
CREATE AGGREGATE WeightedAvg(@value INT, @weight INT)
RETURNS INT
EXTERNAL NAME MyClrCode.WeightedAvg;
GO
CREATE TYPE MyTableType AS TABLE (
ItemValue INT,
ItemWeight INT);
GO
DECLARE @myTable AS MyTableType;
INSERT INTO @myTable
VALUES (1, 4),
(6, 1);
SELECT dbo.WeightedAvg(ItemValue, ItemWeight)
FROM @myTable;
GO