Benutzerdefinierte Funktionen

Benutzerdefinierte Funktionen sind wiederverwendbare Unterabfragen, die als Teil der Abfrage selbst (abfragedefinierte Funktionen) oder als Teil der Datenbankmetadaten (gespeicherte Funktionen) gespeichert werden können. Benutzerdefinierte Funktionen werden über einen Namen aufgerufen, können mit Eingabeargumenten (die skalar oder tabellarisch sein können) bereitgestellt werden, und erstellen einen einzelnen Wert (der skalar oder tabellarisch sein kann) basierend auf dem Rumpf der Funktion.

Eine benutzerdefinierte Funktion gehört zu einer der beiden folgenden Kategorien:

  • Skalarfunktionen
  • Tabellarische Funktionen

Die Eingabeargumente und die Ausgabe der Funktion bestimmen, ob sie eine Skalar- oder tabellarische Funktion ist, wodurch dann festlegt wird, wie sie verwendet werden kann.

Informationen zum Optimieren mehrerer Verwendungen der benutzerdefinierten Funktionen innerhalb einer einzelnen Abfrage finden Sie unter Optimieren von Abfragen, die benannte Ausdrücke verwenden.

Skalarfunktion

  • Hat keine Eingabeargumente, oder alle ihrer Eingabeargumente sind skalare Werte
  • Erzeugt einen einzelnen Skalarwert
  • Kann überall dort verwendet werden, wo ein skalarer Ausdruck zulässig ist
  • Darf nur den Zeilenkontext verwenden, in dem er definiert ist.
  • Kann nur auf Tabellen (und Sichten) verweisen, die sich in dem Schema befinden, auf das zugegriffen werden kann

Tabellarische Funktion

  • Akzeptiert mindestens ein tabellarisches Eingabeargument und optional skalare Eingabeargumente, und/oder:
  • Erzeugt einen einzelnen tabellarischen Wert

Funktionsnamen

Für einen gültigen Namen einer benutzerdefinierten Funktion gelten dieselben Benennungsregeln für Bezeichner wie für andere-Entitäten.

Der jeweilige Name muss außerdem innerhalb seines Definitionsbereichs eindeutig sein.

Hinweis

Haben eine gespeicherte Funktion und eine Tabelle den gleichen Namen, werden Verweise auf den Namen nicht zum Tabellennamen, sondern zur gespeicherten Funktion aufgelöst. Verwenden Sie stattdessen die Tabellenfunktion, um auf die Tabelle zu verweisen.

Eingabeargumente

Für eine gültige benutzerdefinierte Funktion gelten die folgenden Regeln:

  • Eine benutzerdefinierte Funktion verfügt über eine stark typisierte Liste mit null oder mehr Eingabeargumenten.
  • Ein Eingabeargument hat einen Namen, einen Typ und (für skalare Argumente) einen Standardwert.
  • Der Name eines Eingabearguments ist ein Bezeichner.
  • Der Typ eines Eingabearguments ist entweder einer der skalaren Datentypen oder ein tabellarisches Schema.

Syntaktisch ist die Eingabeargumentenliste eine durch Trennzeichen getrennte Liste von Argumentdefinitionen, die von runden Klammern umschlossen ist. Jede Argumentdefinition ist angegeben als

ArgName:ArgType [= ArgDefaultValue]

Bei tabellarischen Argumenten weist ArgType dieselbe Syntax wie die Tabellendefinition (Klammern und eine Liste von Spaltennamen-Typ-Paaren) auf, wobei das Hinzufügen eines Solitärs (*) "beliebiges tabellarisches Schema" angibt.

Zum Beispiel:

Syntax Beschreibung einer Eingabeargumentenliste
() Keine Argumente
(s:string) Einzelnes skalares Argument namens s, das einen Wert vom Typ string hat
(a:long, b:bool=true) Zwei skalare Argumente, wobei das zweite Argument einen Standardwert hat
(T1:(*), T2(r:real), b:bool) Drei Argumente (zwei tabellarische Argumente und ein Skalarargument)

Hinweis

Wenn Sie sowohl tabellarische Eingabeargumente als auch skalare Eingabeargumente verwenden, platzieren Sie alle tabellarischen Eingabeargumente vor den skalaren Eingabeargumenten.

Beispiele

Skalarfunktion

let Add7 = (arg0:long = 5) { arg0 + 7 };
range x from 1 to 10 step 1
| extend x_plus_7 = Add7(x), five_plus_seven = Add7()

Tabellarische Funktion ohne Argumente

let tenNumbers = () { range x from 1 to 10 step 1};
tenNumbers
| extend x_plus_7 = x + 7

Tabellarische Funktion mit Argumenten

let MyFilter = (T:(x:long), v:long) {
  T | where x >= v
};
MyFilter((range x from 1 to 10 step 1), 9)

Ausgabe

x
9
10

Eine tabellarische Funktion, die eine tabellarische Eingabe ohne Spaltenangabe verwendet. An die Funktion kann eine beliebige Tabelle übergeben werden, und in der Funktion kann nicht auf Tabellenspalten verwiesen werden.

let MyDistinct = (T:(*)) {
  T | distinct *
};
MyDistinct((range x from 1 to 3 step 1))

Ausgabe

x
1
2
3

Deklarieren von benutzerdefinierten Funktionen

In der Deklaration einer benutzerdefinierten Funktion wird Folgendes bereitgestellt:

  • Name der Funktion
  • Schema der Funktion (von ihr akzeptierte Parameter, sofern vorhanden)
  • Rumpf der Funktion

Hinweis

Überladen von Funktionen wird nicht unterstützt. Sie können nicht mehrere Funktionen mit demselben Namen und unterschiedlichen Eingabeschemas erstellen.

Tipp

Eine Lambdafunktion hat keinen Namen und wird über eine let-Anweisung an einen Namen gebunden. Daher kann sie als benutzerdefinierte gespeicherte Funktion angesehen werden. Beispiel: Deklaration für eine Lambdafunktion, die zwei Argumente akzeptiert (ein string-Argument namens s und ein long-Argument namens i). Die Funktion gibt (nachdem sie das erste Argument in eine Zahl umgewandelt hat) das Produkt des ersten und des zweiten Arguments zurück. Die Lambdafunktion wird an den Namen f gebunden:

let f=(s:string, i:long) {
    tolong(s) * i
};

Der Rumpf der Funktion enthält Folgendes:

  • Genau einen Ausdruck, der den Rückgabewert der Funktion (Skalar- oder tabellarischer Wert) bereitstellt.
  • Eine beliebige Anzahl (null oder mehr) von let-Anweisungen, deren Gültigkeitsbereich dem des Funktionsrumpfs entspricht. Sind let-Anweisungen angegeben, müssen sie dem Ausdruck vorangestellt sein, der den Rückgabewert der Funktion definiert.
  • Eine beliebige Anzahl (null oder mehr) von Abfrageparameteranweisungen, in denen die Abfrageparameter deklariert sind, die von der Funktion verwendet werden. Sind solche Anweisungen angegeben, müssen sie dem Ausdruck vorangestellt sein, der den Rückgabewert der Funktion definiert.

Hinweis

Andere Arten von Abfrageanweisungen, die auf der „obersten Ebene“ einer Abfrage unterstützt werden, werden innerhalb eines Funktionsrumpfs nicht unterstützt. Zwei Anweisungen müssen durch ein Semikolon getrennt werden.

Beispiele zu benutzerdefinierten Funktionen

Der folgende Abschnitt zeigt Beispiele für die Verwendung benutzerdefinierter Funktionen.

Benutzerdefinierte Funktion, in denen eine let-Anweisung verwendet wird

Das folgende Beispiel zeigt eine benutzerdefinierte Funktion (Lambda), die einen Parameter mit dem Namen ID akzeptiert. Die Funktion ist an den Namen Test gebunden und verwendet drei let-Anweisungen , in denen die Test3-Definition den ID-Parameter verwendet. Bei ausführung beträgt die Ausgabe der Abfrage 70:

let Test = (id: int) {
  let Test2 = 10;
  let Test3 = 10 + Test2 + id;
  let Test4 = (arg: int) {
      let Test5 = 20;
      Test2 + Test3 + Test5 + arg
  };
  Test4(10)
};
range x from 1 to Test(10) step 1
| count

Benutzerdefinierte Funktion, in der ein Standardwert für einen Parameter definiert ist

Das folgende Beispiel zeigt eine Funktion, die drei Argumente akzeptiert. Die beiden letzteren haben einen Standardwert und müssen nicht auf der Anrufwebsite vorhanden sein.

let f = (a:long, b:string = "b.default", c:long = 0) {
  strcat(a, "-", b, "-", c)
};
print f(12, c=7) // Returns "12-b.default-7"

Aufrufen einer benutzerdefinierten Funktion

Die Methode zum Aufrufen einer benutzerdefinierten Funktion hängt von den Argumenten ab, die die Funktion empfängt. Die folgenden Abschnitte behandeln das Aufrufen einer UDF ohne Argumente, das Aufrufen einer UDF mit skalaren Argumenten und das Aufrufen einer UDF mit tabellarischen Argumenten.

Aufrufen einer UDF ohne Argumente

Eine benutzerdefinierte Funktion, die keine Argumente akzeptiert und entweder durch ihren Namen oder durch ihren Namen und eine leere Argumentliste in Klammern aufgerufen werden kann.

// Bind the identifier a to a user-defined function (lambda) that takes
// no arguments and returns a constant of type long:
let a=(){123};
// Invoke the function in two equivalent ways:
range x from 1 to 10 step 1
| extend y = x * a, z = x * a()
// Bind the identifier T to a user-defined function (lambda) that takes
// no arguments and returns a random two-by-two table:
let T=(){
  range x from 1 to 2 step 1
  | project x1 = rand(), x2 = rand()
};
// Invoke the function in two equivalent ways:
// (Note that the second invocation must be itself wrapped in
// an additional set of parentheses, as the union operator
// differentiates between "plain" names and expressions)
union T, (T())

Aufrufen einer UDF mit skalaren Argumenten

Eine benutzerdefinierte Funktion, die ein oder mehrere Skalarargumente akzeptiert, kann mithilfe des Funktionsnamens und einer konkreten Argumentliste in Klammern aufgerufen werden:

let f=(a:string, b:string) {
  strcat(a, " (la la la)", b)
};
print f("hello", "world")

Aufrufen einer UDF mit tabellarischen Argumenten

Eine benutzerdefinierte Funktion, die ein oder mehrere Tabellenargumente (mit einer beliebigen Anzahl von Skalarargumenten) akzeptiert und mit dem Funktionsnamen und einer konkreten Argumentliste in Klammern aufgerufen werden kann:

let MyFilter = (T:(x:long), v:long) {
  T | where x >= v
};
MyFilter((range x from 1 to 10 step 1), 9)

Sie können auch den Operator invoke verwenden, um eine benutzerdefinierte Funktion aufzurufen, die mindestens ein Tabellenargument hat und eine Tabelle zurückgibt. Diese Funktion ist nützlich, wenn das erste Tabellenargument der Funktion die Quelle des invoke-Operators ist:

let append_to_column_a=(T:(a:string), what:string) {
    T | extend a=strcat(a, " ", what)
};
datatable (a:string) ["sad", "really", "sad"]
| invoke append_to_column_a(":-)")

Standardwerte

In Funktionen können unter den folgenden Bedingungen Standardwerte für einige ihrer Parameter bereitgestellt werden:

  • Standardwerte können nur für Skalarparameter bereitgestellt werden.
  • Standardwerte sind immer Literale (Konstanten). Sie können keine beliebigen Berechnungen sein.
  • Ein Parameter ohne Standardwert muss immer vor Parametern stehen, die keinen Standardwert haben.
  • Aufrufer müssen den Wert aller Parameter ohne Standardwerte angeben, die in derselben Reihenfolge wie die Funktionsdeklaration angeordnet sind.
  • In einem Aufruf müssen die Werte für Parameter mit Standardwerten nicht bereitgestellt werden, dies kann jedoch erfolgen.
  • In einem Aufruf können Argumente in einer Reihenfolge bereitgestellt werden, die nicht der Reihenfolge der Parameter entspricht. Ist dies der Fall, müssen die Namen der Argumente angegeben werden.

Im folgenden Beispiel wird eine Tabelle mit zwei identischen Datensätzen zurückgegeben. Im ersten Aufruf von f sind die Argumente vollständig „zusammengewürfelt“, sodass jedem explizit ein Name zugewiesen ist:

let f = (a:long, b:string = "b.default", c:long = 0) {
  strcat(a, "-", b, "-", c)
};
union
  (print x=f(c=7, a=12)), // "12-b.default-7"
  (print x=f(12, c=7))    // "12-b.default-7"

Ausgabe

x
12-b.default-7
12-b.default-7

view-Funktionen

Eine benutzerdefinierte Funktion, die keine Argumente hat und einen tabellarischen Ausdruck zurückgibt, kann als view gekennzeichnet werden. Das Markieren einer benutzerdefinierten Funktion als Sicht bedeutet, dass sich die Funktion immer dann wie eine Tabelle verhält, wenn eine Auflösung des Tabellennamens mit Einem Wildcard ausgeführt wird.

Das folgende Beispiel zeigt zwei benutzerdefinierte Funktionen, T_view und T_notview, und es zeigt, wie nur der erste durch den Platzhalterverweis in der union aufgelöst wird:

let T_view = view () { print x=1 };
let T_notview = () { print x=2 };
union T*

Beschränkungen

Es gelten folgende Einschränkungen:

  • Benutzerdefinierte Funktionen können nicht als Aufrufinformationen für toscalar() übergeben werden, die von dem Zeilenkontext abhängen, in dem die Funktion aufgerufen wird.
  • Benutzerdefinierte Funktionen, die einen tabellarischen Ausdruck zurückgeben, können nicht mit einem Argument aufgerufen werden, das sich mit dem Zeilenkontext ändert.
  • Eine Funktion, die mindestens eine tabellarische Eingabe hat, kann in einem Remotecluster nicht aufgerufen werden.
  • Eine skalare Funktion kann nicht in einem Remotecluster aufgerufen werden.

Der einzige Ausnahme, in der eine benutzerdefinierte Funktion mit einem Argument aufgerufen werden kann, das sich mit dem Zeilenkontext ändert, liegt vor, wenn die benutzerdefinierte Funktion nur aus skalaren Funktionen besteht und toscalar() nicht in ihr verwendet wird.

Beispiele

Unterstützte Skalarfunktion

Die folgende Abfrage wird unterstützt, da f eine Skalarfunktion ist, die auf keinen tabellarischen Ausdruck verweist.

let Table1 = datatable(xdate:datetime)[datetime(1970-01-01)];
let Table2 = datatable(Column:long)[1235];
let f = (hours:long) { now() + hours*1h };
Table2 | where Column != 123 | project d = f(10)

Die folgende Abfrage wird unterstützt, da f eine Skalarfunktion ist, die auf den tabellarischen Ausdruck Table1 verweist, aber ohne Verweis auf den aktuellen Zeilenkontext f(10)aufgerufen wird:

let Table1 = datatable(xdate:datetime)[datetime(1970-01-01)];
let Table2 = datatable(Column:long)[1235];
let f = (hours:long) { toscalar(Table1 | summarize min(xdate) - hours*1h) };
Table2 | where Column != 123 | project d = f(10)

Nicht unterstützte Skalarfunktion

Die folgende Abfrage wird nicht unterstützt, da f es sich um eine Skalarfunktion handelt, die auf den tabellarischen Ausdruck Table1verweist und mit einem Verweis auf den aktuellen Zeilenkontext f(Column)aufgerufen wird:

let Table1 = datatable(xdate:datetime)[datetime(1970-01-01)];
let Table2 = datatable(Column:long)[1235];
let f = (hours:long) { toscalar(Table1 | summarize min(xdate) - hours*1h) };
Table2 | where Column != 123 | project d = f(Column)

Nicht unterstützte tabellarische Funktion

Die folgende Abfrage wird nicht unterstützt, da es sich um f eine tabellarische Funktion handelt, die in einem Kontext aufgerufen wird, der einen Skalarwert erwartet.

let Table1 = datatable(xdate:datetime)[datetime(1970-01-01)];
let Table2 = datatable(Column:long)[1235];
let f = (hours:long) { range x from 1 to hours step 1 | summarize make_list(x) };
Table2 | where Column != 123 | project d = f(Column)

Features, die derzeit für benutzerdefinierte Funktionen nicht unterstützt werden

Aus Gründen der Vollständigkeit finden Sie hier einige häufig angeforderte Features für benutzerdefinierte Funktionen, die derzeit nicht unterstützt werden:

  1. Funktionsüberladung: Es gibt derzeit keine Möglichkeit, eine Funktion zu überladen (eine Möglichkeit, mehrere Funktionen mit demselben Namen und unterschiedlichem Eingabeschema zu erstellen).

  2. Standardwerte: Der Standardwert für einen Skalarparameter einer Funktion muss ein skalares Literal (Konstante) sein.