Funzioni definite dall'utente

Le funzioni definite dall'utente sono sottoquery riutilizzabili che possono essere definite come parte della query stessa (funzioni definite da query) o archiviate come parte dei metadati del database (funzioni archiviate). Le funzioni definite dall'utente vengono richiamate tramite un nome, hanno zero o più argomenti di input (che possono essere scalari o tabulari) e producono un singolo valore (che può essere scalare o tabulare) basato sul corpo della funzione.

Le funzioni definite dall'utente appartengono a una delle due categorie seguenti:

  • Funzioni scalari
  • Funzioni tabulari

Gli argomenti di input della funzione e l'output determinano se la funzione è scalare o tabulare, stabilendo quindi come può essere usata.

Per ottimizzare più usi delle funzioni definite dall'utente all'interno di una singola query, vedere Ottimizzare le query che usano espressioni denominate.

Funzione scalare

  • Ha zero argomenti di input oppure tutti i suoi argomenti di input sono valori scalari
  • Produce un solo valore scalare
  • Può essere usata ovunque sia consentita un'espressione scalare
  • Può usare solo il contesto di riga in cui è definito
  • Può fare riferimento solo a tabelle (e viste) presenti nello schema accessibile

Funzione tabulare

  • Accetta uno o più argomenti di input tabulari e zero o più argomenti di input scalari e/o:
  • Produce un solo valore tabulare

Nomi di funzione

I nomi di funzione definiti dall'utente validi devono seguire le stesse regole di denominazione degli identificatori delle altre entità.

Il nome deve inoltre essere univoco nel proprio ambito di definizione.

Nota

Se una funzione archiviata e una tabella hanno entrambi lo stesso nome, qualsiasi riferimento a tale nome viene risolto nella funzione archiviata, non nel nome della tabella. Usare invece la funzione table per fare riferimento alla tabella.

Argomenti di input

Le funzioni definite dall'utente valide seguono queste regole:

  • Una funzione definita dall'utente ha un elenco fortemente tipizzato di zero o più argomenti di input.
  • Un argomento di input ha un nome, un tipo e, per gli argomenti scalari, un valore predefinito.
  • Il nome di un argomento di input è un identificatore.
  • Il tipo di un argomento di input è uno dei tipi di dati scalari o uno schema tabulare.

A livello di sintassi, l'elenco di argomenti di input è un elenco delimitato da virgole di definizioni di argomenti, racchiuse tra parentesi. Ogni definizione di argomento è specificata come

ArgName:ArgType [= ArgDefaultValue]

Per gli argomenti tabulari, ArgType ha la stessa sintassi della definizione della tabella (parentesi e un elenco di coppie nome/tipo di colonna), con l'aggiunta di un solitario (*) che indica "qualsiasi schema tabulare".

Ad esempio:

Sintassi Descrizione dell'elenco di argomenti di input
() Nessun argomento
(s:string) Singolo argomento scalare chiamato s che accetta un valore di tipo string
(a:long, b:bool=true) Due argomenti scalari, il secondo dei quali ha un valore predefinito
(T1:(*), T2(r:real), b:bool) Tre argomenti (due argomenti tabulari e un argomento scalare)

Nota

Quando si usano argomenti di input sia tabulari che scalari, inserire tutti gli argomenti di input tabulari prima degli argomenti di input scalari.

Esempi

Funzione scalare

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

Funzione tabulare senza argomenti

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

Funzione tabulare con argomenti

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

Output

x
9
10

Funzione tabulare che usa un input tabulare senza alcuna colonna specificata. A una funzione può essere passata qualsiasi tabella e non è possibile fare riferimento ad alcuna colonna della tabella all'interno della funzione.

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

Output

x
1
2
3

Dichiarazione di funzioni definite dall'utente

La dichiarazione di una funzione definita dall'utente fornisce:

  • Il nome della funzione
  • Lo schema della funzione (gli eventuali parametri che accetta)
  • Il corpo della funzione

Nota

L'overload delle funzioni non è supportato. Non è possibile creare più funzioni con lo stesso nome e schemi di input diversi.

Suggerimento

Le funzioni lambda non hanno un nome e sono associate a un nome tramite un'istruzione let. Possono quindi essere considerate come funzioni archiviate definite dall'utente. Esempio: dichiarazione di una funzione lambda che accetta due argomenti (un argomento string denominato s e un argomento long denominato i). Restituisce il prodotto del primo (dopo averlo convertito in un numero) e del secondo. La funzione lambda è associata al nome f:

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

Il corpo della funzione include:

  • Esattamente una espressione, che fornisce il valore restituito della funzione (scalare o tabulare).
  • Un numero qualsiasi (zero o più) di istruzioni let, il cui ambito è quello del corpo della funzione. Se specificate, le istruzioni let devono precedere l'espressione che definisce il valore restituito della funzione.
  • Un numero qualsiasi (zero o più) di istruzioni con parametri di query, che dichiarano i parametri di query usati dalla funzione. Se specificate, devono precedere l'espressione che definisce il valore restituito della funzione.

Nota

Altri tipi di istruzioni di query che sono supportati al "primo livello" della query non sono invece supportati all'interno del corpo di una funzione. Le due istruzioni devono essere separate da un punto e virgola.

Esempi di funzioni definite dall'utente

La sezione seguente illustra esempi di come usare le funzioni definite dall'utente.

Funzione definita dall'utente che usa un'istruzione let

Nell'esempio seguente viene illustrata una funzione definita dall'utente (lambda) che accetta un parametro denominato ID. La funzione è associata al nome Test e usa tre istruzioni let , in cui la definizione Test3 usa il parametro ID . Quando viene eseguito, l'output della query è 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

Funzione definita dall'utente che definisce un valore predefinito per un parametro

L'esempio seguente mostra una funzione che accetta tre argomenti. Gli ultimi due hanno un valore predefinito e non devono essere presenti nel sito di chiamata.

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"

Richiamo di una funzione definita dall'utente

Il metodo per richiamare una funzione definita dall'utente dipende dagli argomenti che la funzione prevede di ricevere. Le sezioni seguenti illustrano come richiamare una funzione definita dall'utente senza argomenti, richiamare una funzione definita dall'utente con argomenti scalari e richiamare una funzione definita dall'utente con argomenti tabulari.

Richiamare una funzione definita dall'utente senza argomenti

Funzione definita dall'utente che non accetta argomenti e può essere richiamata dal nome o dal nome e da un elenco di argomenti vuoto tra parentesi.

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

Richiamare una funzione UDF con argomenti scalari

Una funzione definita dall'utente che accetta uno o più argomenti scalari può essere richiamata usando il nome della funzione e un elenco di argomenti concreti tra parentesi:

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

Richiamare una funzione UDF con argomenti tabulari

Una funzione definita dall'utente che accetta uno o più argomenti di tabella (con qualsiasi numero di argomenti scalari) e può essere richiamata usando il nome della funzione e un elenco di argomenti concreti tra parentesi:

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

È anche possibile usare l'operatore invoke per richiamare una funzione definita dall'utente che accetta uno o più argomenti tabulari e restituisce una tabella. Questa funzione è utile quando il primo argomento concreto della tabella della funzione è l'origine dell'operatore 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(":-)")

Valori predefiniti

Le funzioni possono fornire i valori predefiniti per alcuni parametri nelle condizioni seguenti:

  • I valori predefiniti possono essere specificati solo per i parametri scalari.
  • I valori predefiniti sono sempre valori letterali (costanti). Non possono essere calcoli arbitrari.
  • I parametri che non hanno un valore predefinito precedono sempre i parametri che ce l'hanno.
  • I chiamanti devono fornire il valore di tutti i parametri senza valori predefiniti disposti nello stesso ordine della dichiarazione di funzione.
  • I chiamanti non devono necessariamente specificare il valore per i parametri con valori predefiniti, ma possono farlo.
  • I chiamanti possono specificare gli argomenti in un ordine che non corrisponde all'ordine dei parametri. In tal caso, devono assegnare un nome agli argomenti.

L'esempio seguente restituisce una tabella con due record identici. Nella prima chiamata di f gli argomenti sono completamente "mescolati", quindi a ognuno viene assegnato un nome in modo esplicito:

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"

Output

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

Visualizzare le funzioni

Una funzione definita dall'utente che non accetta argomenti e restituisce un'espressione tabulare può essere contrassegnata come vista. Contrassegnare una funzione definita dall'utente come visualizzazione significa che la funzione si comporta come una tabella ogni volta che viene eseguita una risoluzione dei nomi di tabella con caratteri jolly.

L'esempio seguente mostra due funzioni definite dall'utente, T_view e T_notview, di cui solo la prima viene risolta dal riferimento con caratteri jolly in union:

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

Restrizioni

Si applicano le restrizioni seguenti:

  • Le funzioni definite dall'utente non possono passare a toscalar() informazioni sulla chiamata che dipendono dal contesto di riga in cui viene chiamata la funzione.
  • Le funzioni definite dall'utente che restituiscono un'espressione tabulare non possono essere richiamate con un argomento che varia in base al contesto di riga.
  • Una funzione che accetta almeno un input tabulare non può essere richiamata su un cluster remoto.
  • Una funzione scalare non può essere richiamata su un cluster remoto.

Una funzione definita dall'utente può essere richiamata con un argomento che varia con il contesto di riga solo quando è composta solo da funzioni scalari e non usa toscalar().

Esempio

Funzione scalare supportata

La query seguente è supportata perché f è una funzione scalare che non fa riferimento a alcuna espressione tabulare.

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 query seguente è supportata perché f è una funzione scalare che fa riferimento all'espressione Table1 tabulare, ma viene richiamata senza riferimento al contesto f(10)di riga corrente:

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)

Funzione scalare non supportata

La query seguente non è supportata perché f è una funzione scalare che fa riferimento all'espressione Table1tabulare e viene richiamata con un riferimento al contesto f(Column)di riga corrente:

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)

Funzione tabulare non supportata

La query seguente non è supportata perché f è una funzione tabulare richiamata in un contesto che prevede un valore scalare.

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)

Funzionalità attualmente non supportate dalle funzioni definite dall'utente

Per la completezza, ecco alcune funzionalità comunemente richieste per le funzioni definite dall'utente che attualmente non sono supportate:

  1. Overload delle funzioni: attualmente non è possibile eseguire l'overload di una funzione (un modo per creare più funzioni con lo stesso nome e uno schema di input diverso).

  2. Valori predefiniti: il valore predefinito di un parametro scalare di una funzione deve essere un valore letterale scalare (costante).