Fonctions définies par l’utilisateur

Les fonctions définies par l’utilisateur sont des sous-requêtes réutilisables qui peuvent être définies dans le cadre de la requête elle-même (fonctions définies par la requête) ou stockées dans le cadre des métadonnées de la base de données (fonctions stockées). Les fonctions définies par l'utilisateur sont appelées via un nom, sont fournies avec zéro ou plusieurs arguments d'entrée (qui peuvent être scalaires ou tabulaires) et produisent une valeur unique (qui peut être scalaire ou tabulaire) basée sur le corps de la fonction.

Une fonction définie par l'utilisateur appartient à l'une des deux catégories suivantes :

  • Fonctions scalaires
  • Fonctions tabulaires

Les arguments d’entrée et la sortie de la fonction déterminent si elle est scalaire ou tabulaire, ce qui indique ensuite comment elle peut être utilisée.

Pour optimiser plusieurs utilisations des fonctions définies par l’utilisateur dans une requête unique, consultez Optimiser les requêtes qui utilisent des expressions nommées.

Fonction scalaire

  • Ne comporte aucun argument d'entrée, ou tous ses arguments d'entrée sont des valeurs scalaires
  • Produit une valeur scalaire unique
  • Peut être utilisée partout où une expression scalaire est autorisée
  • Peut uniquement utiliser le contexte de ligne dans lequel il est défini
  • Peut uniquement faire référence à des tables (et vues) situées dans le schéma accessible

Fonction tabulaire

  • Accepte un ou plusieurs arguments d'entrée tabulaires, et zéro ou plusieurs arguments d'entrée scalaires, et/ou :
  • Produit une valeur tabulaire unique

Noms des fonctions

Les noms des fonctions définies par l'utilisateur valides doivent suivre les règles de nommage des identificateurs des autres entités.

Le nom doit également être unique dans son étendue de définition.

Notes

Si une fonction stockée et une table portent toutes les deux le même nom, toute référence à ce nom aboutira à la fonction stockée, et non à la table. Pour référencer la table, utilisez la fonction table.

Arguments d’entrée

Les fonctions définies par l'utilisateur valides suivent les règles suivantes :

  • Une fonction définie par l’utilisateur a une liste fortement typée de zéro ou plusieurs arguments d’entrée.
  • Un argument d'entrée comporte un nom, un type et (pour les arguments scalaires) une valeur par défaut.
  • Le nom d'un argument d'entrée est un identificateur.
  • Le type d'un argument d'entrée correspond à l'un des types de données scalaires ou à un schéma tabulaire.

Du point de vue syntaxique, la liste des arguments d'entrée est une liste de définitions d'arguments séparées par des virgules et entre parenthèses. Chaque définition d'argument est spécifiée en tant que

ArgName:ArgType [= ArgDefaultValue]

Pour les arguments tabulaires, ArgType a la même syntaxe que la définition de table (parenthèses et une liste de paires nom/type de colonne), avec l’ajout d’un solitaire (*) indiquant « n’importe quel schéma tabulaire ».

Par exemple :

Syntaxe Description de la liste des arguments d'entrée
() Aucun argument
(s:string) Argument scalaire unique appelé s prenant une valeur de type string
(a:long, b:bool=true) Deux arguments scalaires, le second ayant une valeur par défaut
(T1:(*), T2(r:real), b:bool) Trois arguments (deux arguments tabulaires et un argument scalaire)

Notes

Lorsque vous utilisez à la fois des arguments d'entrée tabulaires et des arguments d'entrée scalaires, placez tous les arguments d'entrée tabulaires avant les arguments d'entrée scalaires.

Exemples

Fonction scalaire

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()

Fonction tabulaire sans arguments

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

Fonction tabulaire avec arguments

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

Sortie

x
9
10

Fonction tabulaire qui utilise une entrée tabulaire sans qu'aucune colonne ne soit spécifiée. Une table peut être transmise à une fonction, et aucune colonne de table ne peut être référencée à l'intérieur de la fonction.

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

Sortie

x
1
2
3

Déclaration de fonctions définies par l'utilisateur

La déclaration d'une fonction définie par l'utilisateur fournit ce qui suit :

  • Nom de la fonction
  • Schéma de la fonction (paramètres qu'elle accepte, le cas échéant)
  • Corps de la fonction

Notes

La surcharge des fonctions n'est pas prise en charge. Vous ne pouvez pas créer plusieurs fonctions avec le même nom et des schémas d'entrée différents.

Conseil

Les fonctions lambda n'ont pas de nom, et sont liées à un nom au moyen d'une instruction let. Elles peuvent donc être considérées comme des fonctions stockées définies par l'utilisateur. Exemple : déclaration d'une fonction lambda qui accepte deux arguments (un argument string appelé s et un argument long appelé i). Elle renvoie le produit du premier (après l'avoir converti en nombre) et du second. La fonction lambda est liée au nom f :

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

Le corps de la fonction comprend ce qui suit :

  • Une seule expression, qui fournit la valeur renvoyée de la fonction (valeur scalaire ou tabulaire).
  • Un certain nombre (zéro ou plus) d'instructions let, dont l'étendue est celle du corps de la fonction. Le cas échéant, les instructions let doivent précéder l'expression définissant la valeur renvoyée de la fonction.
  • Un certain nombre (zéro ou plus) d'instructions de déclaration des paramètres de requête, qui déclarent les paramètres de requête utilisés par la fonction. Le cas échéant, elles doivent précéder l'expression définissant la valeur renvoyée de la fonction.

Notes

D'autres types d'instructions de requête pris en charge au « niveau supérieur » de la requête ne sont pas pris en charge dans le corps d'une fonction. Deux instructions doivent être séparées par un point-virgule.

Exemples de fonctions définies par l'utilisateur

La section suivante montre des exemples d’utilisation de fonctions définies par l’utilisateur.

Fonction définie par l'utilisateur qui utilise une instruction let

L’exemple suivant montre une fonction définie par l’utilisateur (lambda) qui accepte un paramètre nommé ID. La fonction est liée au nom Test et utilise trois instructions let , dans lesquelles la définition Test3 utilise le paramètre ID . Lors de l’exécution, la sortie de la requête est de 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

Fonction définie par l'utilisateur qui définit une valeur par défaut pour un paramètre

L'exemple suivant illustre une fonction qui accepte trois arguments. Les deux derniers ont une valeur par défaut et n’ont pas besoin d’être présents sur le site d’appel.

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"

Appel d'une fonction définie par l'utilisateur

La méthode permettant d’appeler une fonction définie par l’utilisateur dépend des arguments que la fonction s’attend à recevoir. Les sections suivantes expliquent comment appeler une fonction UDF sans arguments, appeler une fonction UDF avec des arguments scalaires et appeler une fonction UDF avec des arguments tabulaires.

Appeler une fonction UDF sans arguments

Fonction définie par l’utilisateur qui ne prend aucun argument et peut être appelée soit par son nom, soit par son nom et une liste d’arguments vide entre parenthèses.

// 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())

Appeler une fonction UDF avec des arguments scalaires

Une fonction définie par l’utilisateur qui prend un ou plusieurs arguments scalaires peut être appelée à l’aide du nom de la fonction et d’une liste d’arguments concrets entre parenthèses :

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

Appeler une fonction UDF avec des arguments tabulaires

Fonction définie par l’utilisateur qui prend un ou plusieurs arguments de table (avec un nombre quelconque d’arguments scalaires) et peut être appelée à l’aide du nom de la fonction et d’une liste d’arguments concrets entre parenthèses :

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

Vous pouvez également utiliser l'opérateur invoke pour appeler une fonction définie par l'utilisateur qui prend un ou plusieurs arguments de table et renvoie une table. Cette fonction est utile lorsque le premier argument de table concret de la fonction est la source de l'opérateur invoke :

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(":-)")

Valeurs par défaut

Les fonctions peuvent fournir des valeurs par défaut à certains de leurs paramètres dans les conditions suivantes :

  • Des valeurs par défaut ne peuvent être fournies que pour les paramètres scalaires.
  • Les valeurs par défaut sont toujours des littéraux (constantes). Il ne peut pas s'agir de calculs arbitraires.
  • Les paramètres sans valeur par défaut précèdent toujours les paramètres qui en ont une.
  • Les appelants doivent fournir la valeur de tous les paramètres sans aucune valeur par défaut organisée dans le même ordre que la déclaration de fonction.
  • Les appelants ne sont pas tenus de fournir la valeur des paramètres dotés d'une valeur par défaut, mais ils peuvent le faire.
  • Les appelants peuvent fournir des arguments dans un ordre qui ne correspond pas à celui des paramètres. Dans ce cas, ils doivent nommer leurs arguments.

L'exemple suivant renvoie une table comportant deux enregistrements identiques. Lors du premier appel de f, les arguments sont complètement « brouillés », de sorte que chacun reçoit explicitement un nom :

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"

Sortie

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

Fonctions View

Une fonction définie par l'utilisateur qui ne prend aucun argument et renvoie une expression tabulaire peut être marquée comme view. Le marquage d’une fonction définie par l’utilisateur en tant que vue signifie que la fonction se comporte comme une table chaque fois qu’une résolution de noms de table générique est effectuée.

L'exemple suivant illustre deux fonctions définies par l'utilisateur, T_view et T_notview, et montre que seule la première est résolue par le caractère générique dans union :

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

Restrictions

Les restrictions suivantes s’appliquent :

  • Les fonctions définies par l'utilisateur ne peuvent pas être transmises dans les informations d'appel toscalar () qui dépendent du contexte de ligne dans lequel la fonction est appelée.
  • Les fonctions définies par l'utilisateur qui renvoient une expression tabulaire ne peuvent pas être appelées avec un argument qui varie en fonction du contexte de ligne.
  • Une fonction prenant au moins une entrée tabulaire ne peut pas être appelée sur un cluster distant.
  • Une fonction scalaire ne peut pas être appelée sur un cluster distant.

Une fonction définie par l'utilisateur ne peut être appelée avec un argument qui varie selon le contexte de ligne que lorsqu'elle est uniquement composée de fonctions scalaires et n'utilise pas toscalar().

Exemples

Fonction scalaire prise en charge

La requête suivante est prise en charge, car f est une fonction scalaire qui ne référence aucune expression tabulaire.

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)

La requête suivante est prise en charge, car f est une fonction scalaire qui fait référence à l’expression Table1 tabulaire, mais qui est appelée sans référence au contexte f(10)de ligne actuel :

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)

Fonction scalaire non prise en charge

La requête suivante n’est pas prise en charge, car f est une fonction scalaire qui fait référence à l’expression Table1tabulaire et est appelée avec une référence au contexte f(Column)de ligne actuel :

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)

Fonction tabulaire non prise en charge

La requête suivante n’est pas prise en charge, car f est une fonction tabulaire appelée dans un contexte qui attend une valeur scalaire.

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)

Fonctionnalités actuellement non prises en charge par les fonctions définies par l'utilisateur

Pour plus d’exhaustivité, voici quelques fonctionnalités fréquemment demandées pour les fonctions définies par l’utilisateur qui ne sont actuellement pas prises en charge :

  1. Surcharge de fonction : il n’existe actuellement aucun moyen de surcharger une fonction (un moyen de créer plusieurs fonctions portant le même nom et un schéma d’entrée différent).

  2. Valeurs par défaut : la valeur par défaut d'un paramètre scalaire relatif à une fonction doit être un littéral scalaire (constante).