Funções definidas pelo utilizador

As funções definidas pelo utilizador são subconsultas reutilizáveis que podem ser definidas como parte da própria consulta (funções definidas por consultas) ou armazenadas como parte dos metadados da base de dados (funções armazenadas). As funções definidas pelo utilizador são invocadas através de um nome, são fornecidas com zero ou mais argumentos de entrada (que podem ser escalares ou tabulares) e produzem um único valor (que pode ser escalar ou tabular) com base no corpo da função.

Uma função definida pelo utilizador pertence a uma de duas categorias:

  • Funções escalares
  • Funções tabulares

Os argumentos de entrada e saída da função determinam se é escalar ou tabular, o que, estabelece, então, a forma como pode ser utilizada.

Para otimizar múltiplas utilizações das funções definidas pelo utilizador numa única consulta, veja Otimizar consultas que utilizam expressões nomeadas.

Função escalar

  • Tem zero argumentos de entrada ou todos os argumentos de entrada são valores escalares
  • Produz um único valor escalar
  • Pode ser utilizado onde quer que seja permitida uma expressão escalar
  • Só pode utilizar o contexto de linha no qual está definido
  • Só pode referir-se a tabelas (e vistas) que estão no esquema acessível

Função tabular

  • Aceita um ou mais argumentos de entrada tabulares e zero ou mais argumentos de entrada escalar e/ou:
  • Produz um único valor tabular

Nomes das funções

Os nomes de funções definidos pelo utilizador válidos têm de seguir as mesmas regras de nomenclatura do identificador que outras entidades.

O nome também tem de ser exclusivo no âmbito da definição.

Nota

Se uma função armazenada e uma tabela tiverem o mesmo nome, qualquer referência a esse nome é resolvida para a função armazenada e não para o nome da tabela. Utilize a função de tabela para referenciar a tabela.

Argumentos de entrada

As funções definidas pelo utilizador válidas seguem estas regras:

  • Uma função definida pelo utilizador tem uma lista fortemente digitada de zero ou mais argumentos de entrada.
  • Um argumento de entrada tem um nome, um tipo e (para argumentos escalares) um valor predefinido.
  • O nome de um argumento de entrada é um identificador.
  • O tipo de argumento de entrada é um dos tipos de dados escalares ou um esquema tabular.

Sintaticamente, a lista de argumentos de entrada é uma lista separada por vírgulas de definições de argumentos, encapsulada em parênteses. Cada definição de argumento é especificada como

ArgName:ArgType [= ArgDefaultValue]

Para argumentos tabulares, o ArgType tem a mesma sintaxe que a definição da tabela (parêntese e uma lista de pares de nome/tipo de coluna), com a adição de um (*) solitário a indicar "qualquer esquema tabular".

Por exemplo:

Syntax Descrição da lista de argumentos de entrada
() Sem argumentos
(s:string) Argumento escalar único chamado s tomar um valor do tipo string
(a:long, b:bool=true) Dois argumentos escalares, o segundo dos quais tem um valor predefinido
(T1:(*), T2(r:real), b:bool) Três argumentos (dois argumentos tabulares e um argumento escalar)

Nota

Ao utilizar argumentos de entrada tabulares e argumentos de entrada escalar, coloque todos os argumentos de entrada tabulares antes dos argumentos de entrada escalar.

Exemplos

Função escalar

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

Função tabular sem argumentos

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

Função tabular com argumentos

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

Saída

x
9
10

Uma função tabular que utiliza uma entrada tabular sem nenhuma coluna especificada. Qualquer tabela pode ser transmitida a uma função e nenhuma coluna de tabela pode ser referenciada dentro da função.

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

Saída

x
1
2
3

Declarar funções definidas pelo utilizador

A declaração de uma função definida pelo utilizador fornece:

  • Nome da função
  • Esquema de função (parâmetros que aceita, se existirem)
  • Corpo da função

Nota

As funções de sobrecarga não são suportadas. Não pode criar várias funções com o mesmo nome e esquemas de entrada diferentes.

Dica

As funções Lambda não têm um nome e estão vinculadas a um nome através de uma instrução let. Por conseguinte, podem ser consideradas funções armazenadas definidas pelo utilizador. Exemplo: Declaração para uma função lambda que aceita dois argumentos (um string chamado s e um long chamado i). Devolve o produto do primeiro (depois de o converter num número) e o segundo. O lambda está vinculado ao nome f:

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

O corpo da função inclui:

  • Exatamente uma expressão, que fornece o valor devolvido da função (valor escalar ou tabular).
  • Qualquer número (zero ou mais) de instruções let, cujo âmbito é o do corpo da função. Se especificado, as instruções let têm de preceder a expressão que define o valor devolvido da função.
  • Qualquer número (zero ou mais) de instruções de parâmetros de consulta, que declaram os parâmetros de consulta utilizados pela função. Se for especificado, têm de preceder a expressão que define o valor devolvido da função.

Nota

Outros tipos de instruções de consulta que são suportadas no "nível superior" da consulta não são suportados dentro de um corpo de função. As duas instruções têm de ser separadas por ponto e vírgula.

Exemplos de funções definidas pelo utilizador

A secção seguinte mostra exemplos de como utilizar funções definidas pelo utilizador.

Função definida pelo utilizador que utiliza uma instrução let

O exemplo seguinte mostra uma função definida pelo utilizador (lambda) que aceita um parâmetro com o nome ID. A função está vinculada ao nome Teste e utiliza três instruções let , nas quais a definição Test3 utiliza o parâmetro ID . Quando executado, o resultado da consulta é 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

Função definida pelo utilizador que define um valor predefinido para um parâmetro

O exemplo seguinte mostra uma função que aceita três argumentos. Os dois últimos têm um valor predefinido e não têm de estar presentes no site de chamadas.

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"

Invocar uma função definida pelo utilizador

O método para invocar uma função definida pelo utilizador depende dos argumentos que a função espera receber. As secções seguintes abrangem como invocar um UDF sem argumentos, invocar um UDF com argumentos escalares e invocar um UDF com argumentos tabulares.

Invocar um UDF sem argumentos

Uma função definida pelo utilizador que não utiliza argumentos e pode ser invocada pelo respetivo nome ou pelo respetivo nome e uma lista de argumentos vazia em parênteses.

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

Invocar um UDF com argumentos escalares

Uma função definida pelo utilizador que utiliza um ou mais argumentos escalares pode ser invocada com o nome da função e uma lista de argumentos concretos em parênteses:

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

Invocar um UDF com argumentos tabulares

Uma função definida pelo utilizador que utiliza um ou mais argumentos de tabela (com qualquer número de argumentos escalares) e pode ser invocada com o nome da função e uma lista de argumentos concretos em parênteses:

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

Também pode utilizar o operador invoke para invocar uma função definida pelo utilizador que utiliza um ou mais argumentos de tabela e devolve uma tabela. Esta função é útil quando o primeiro argumento de tabela concreta para a função é a origem do invoke operador:

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

Valores predefinidos

As funções podem fornecer valores predefinidos a alguns dos respetivos parâmetros nas seguintes condições:

  • Os valores predefinidos só podem ser fornecidos para parâmetros escalares.
  • Os valores predefinidos são sempre literais (constantes). Não podem ser cálculos arbitrários.
  • Os parâmetros sem valor predefinido precedem sempre os parâmetros que têm um valor predefinido.
  • Os autores de chamadas têm de fornecer o valor de todos os parâmetros sem valores predefinidos dispostos pela mesma ordem que a declaração de função.
  • Os autores de chamadas não precisam de fornecer o valor para parâmetros com valores predefinidos, mas podem fazê-lo.
  • Os autores de chamadas podem fornecer argumentos por uma ordem que não corresponde à ordem dos parâmetros. Em caso afirmativo, têm de atribuir um nome aos seus argumentos.

O exemplo seguinte devolve uma tabela com dois registos idênticos. Na primeira invocação de f, os argumentos são completamente "mexidos", pelo que cada um recebe explicitamente um nome:

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"

Saída

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

Ver funções

Uma função definida pelo utilizador que não utiliza argumentos e devolve uma expressão tabular pode ser marcada como uma vista. Marcar uma função definida pelo utilizador como uma vista significa que a função se comporta como uma tabela sempre que é executada uma resolução de nomes de tabela universal.

O exemplo seguinte mostra duas funções definidas T_view pelo utilizador e , e T_notviewmostra como apenas a primeira é resolvida pela referência de carateres universais no union:

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

Restrições

Aplicam-se as seguintes restrições:

  • As funções definidas pelo utilizador não podem passar para informações de invocação toscalar() que dependem do contexto de linha no qual a função é chamada.
  • As funções definidas pelo utilizador que devolvem uma expressão tabular não podem ser invocadas com um argumento que varia consoante o contexto da linha.
  • Uma função que tenha pelo menos uma entrada tabular não pode ser invocada num cluster remoto.
  • Não é possível invocar uma função escalar num cluster remoto.

O único local onde uma função definida pelo utilizador pode ser invocada com um argumento que varia consoante o contexto de linha é quando a função definida pelo utilizador é composta apenas por funções escalares e não utiliza toscalar().

Exemplos

Função escalar suportada

A consulta seguinte é suportada porque f é uma função escalar que não referencia nenhuma expressão tabular.

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)

A consulta seguinte é suportada porque f é uma função escalar que referencia a expressão Table1 tabular, mas é invocada sem qualquer referência ao contexto f(10)de linha atual:

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)

Função escalar não suportada

A consulta seguinte não é suportada porque f é uma função escalar que referencia a expressão Table1tabular e é invocada com uma referência ao contexto f(Column)de linha atual:

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)

Função tabular não suportada

A consulta seguinte não é suportada porque f é uma função tabular que é invocada num contexto que espera um valor escalar.

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)

Funcionalidades que não são atualmente suportadas por funções definidas pelo utilizador

Para concluir, seguem-se algumas funcionalidades frequentemente pedidas para funções definidas pelo utilizador que atualmente não são suportadas:

  1. Sobrecarga da função: atualmente, não há forma de sobrecarregar uma função (uma forma de criar várias funções com o mesmo nome e esquema de entrada diferente).

  2. Valores predefinidos: o valor predefinido de um parâmetro escalar para uma função tem de ser um literal escalar (constante).