Funções definidas pelo usuário

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

Uma função definida pelo usuário pertence a uma das duas categorias:

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

Os argumentos de entrada e a saída da função determinam se ela é escalar ou tabular, o que estabelece como ela poderá ser usada.

Para otimizar vários usos das funções definidas pelo usuário em uma única consulta, consulte Otimizar consultas que usam expressões nomeadas.

Função de valor escalar

  • Tem zero argumentos de entrada ou todos os argumentos de entrada são valores escalares
  • Produz apenas um valor escalar
  • Poderá ser usada sempre que uma expressão escalar for permitida
  • Pode usar apenas o contexto de linha no qual ele é definido
  • Só pode fazer referência a tabelas (e exibições) que estão no esquema acessível

Função tabular

  • Aceita um ou mais argumentos tabulares de entrada e zero ou mais argumentos escalares de entrada; e/ou:
  • Produz apenas um valor tabular

Nomes de função

Os nomes de função válidos definidos pelo usuário devem seguir as mesmas regras de nomenclatura de identificador que as demais entidades.

O nome também deve ser exclusivo no escopo de definição dele.

Observação

Se uma função armazenada e uma tabela tiverem o mesmo nome, qualquer referência a esse nome resultará na função armazenada e não no nome da tabela. Em vez disso, use a função de tabela para fazer referência à tabela.

Argumentos de entrada

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

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

Sintaticamente, a lista de argumentos de entrada é uma lista de definições de argumentos separadas por vírgula, encapsuladas entre parênteses. Cada definição de argumento é especificada como

ArgName:ArgType [= ArgDefaultValue]

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

Por exemplo:

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

Observação

Ao usar argumentos tabulares e escalares de entrada, coloque todos os argumentos tabulares antes dos escalares.

Exemplos

Função de valor 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

Função tabular que usa uma entrada tabular sem nenhuma coluna especificada. Qualquer tabela pode ser passada para 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

Declarando funções definidas pelo usuário

A declaração de uma função definida pelo usuário fornece:

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

Observação

Não há suporte para a sobrecarga de funções. Você 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 nome e são associadas a um nome usando uma instrução LET. Portanto, elas podem ser consideradas funções armazenadas definidas pelo usuário. Exemplo: Declaração de uma função lambda que aceita dois argumentos (um string chamado s e um long chamado i). Ela retorna o produto entre o primeiro (após convertê-lo em número) e o segundo. A lambda está associada 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 de retorno da função (valor escalar ou tabular).
  • Qualquer número (zero ou mais) de instruções LET, cujo escopo é o mesmo do corpo da função. Se especificadas, as instruções LET devem preceder a expressão que define o valor de retorno da função.
  • Qualquer número (zero ou mais) de instruções de parâmetros de consulta, que declaram os parâmetros de consulta usados pela função. Se especificadas, elas devem preceder a expressão que define o valor de retorno da função.

Observação

Há outros tipos de instruções de consulta que são compatíveis com o "nível superior" da consulta, mas que não têm suporte dentro do corpo de uma função. Duas instruções devem ser sempre separadas por ponto e vírgula.

Exemplos de funções definidas pelo usuário

A seção a seguir mostra exemplos de como usar funções definidas pelo usuário.

Função definida pelo usuário que usa uma instrução LET

O exemplo a seguir mostra uma função definida pelo usuário (lambda) que aceita um parâmetro chamado ID. A função está associada ao nome Teste e usa três instruções let , nas quais a definição test3 usa o parâmetro ID . Quando executada, a saída 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 usuário que estabelece um valor padrão para um parâmetro

O exemplo a seguir mostra um função que aceita três argumentos. Os dois últimos têm um valor padrão e não precisam estar presentes no site de chamada.

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"

Invocando uma função definida pelo usuário

O método para invocar uma função definida pelo usuário depende dos argumentos que a função espera receber. As seções a seguir abordam como invocar uma UDF sem argumentos, invocar uma UDF com argumentos escalares e invocar uma UDF com argumentos tabulares.

Invocar uma UDF sem argumentos

Uma função definida pelo usuário que não usa argumentos e pode ser invocada pelo nome ou pelo nome e uma lista de argumentos vazia entre 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 uma UDF com argumentos escalares

Uma função definida pelo usuário que usa um ou mais argumentos escalares pode ser invocada usando o nome da função e uma lista de argumentos concreta entre parênteses:

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

Invocar uma UDF com argumentos tabulares

Uma função definida pelo usuário que usa um ou mais argumentos de tabela (com qualquer número de argumentos escalares) e pode ser invocada usando o nome da função e uma lista de argumentos concreta entre parênteses:

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

Você também pode usar o operador invoke para invocar uma função definida pelo usuário que usa um ou mais argumentos tabulares e retorna uma tabela. Essa função será útil quando o primeiro argumento tabular concreto da função for a origem do operador de 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(":-)")

Valores padrão

As funções podem fornecer valores padrão a alguns dos parâmetros nas seguintes condições:

  • Valores padrão só podem ser fornecidos para parâmetros escalares.
  • Os valores padrão são sempre literais (constantes). Eles não podem ser cálculos arbitrários.
  • Os parâmetros sem valor padrão sempre precedem aqueles que têm valor padrão.
  • Os chamadores devem fornecer o valor de todos os parâmetros sem valores padrão organizados na mesma ordem que a declaração de função.
  • Os chamadores não precisam fornecer os valores dos parâmetros que têm valores padrão, mas poderão fazer isso se desejarem.
  • Os chamadores podem fornecer argumentos em uma ordem que não corresponde à ordem dos parâmetros. Nesse caso, eles devem dar nome aos argumentos.

O exemplo a seguir retorna uma tabela com dois registros idênticos. Na primeira invocação de f, os argumentos estão completamente "embaralhados", de modo 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

Exibir funções

Uma função definida pelo usuário que não usa argumentos e retorna uma expressão tabular pode ser marcada como uma exibição. Marcar uma função definida pelo usuário como uma exibição significa que a função se comporta como uma tabela sempre que uma resolução de nome de tabela curinga é executada.

O seguinte exemplo mostra duas funções definidas pelo usuário – T_view e T_notview – e mostra como apenas a primeira é resolvida pela referência de curinga em union:

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

Restrições

As restrições a seguir se aplicam:

  • As funções definidas pelo usuário não podem passar para toscalar() informações de invocação que dependem do contexto de linha no qual a função é chamada.
  • Funções definidas pelo usuário que retornam uma expressão tabular não podem ser invocadas com um argumento que varia de acordo com o contexto da linha.
  • Uma função que ocupa pelo menos uma entrada tabular não pode ser invocada em um cluster remoto.
  • Uma função escalar não pode ser invocada em um cluster remoto.

O único lugar em que uma função definida pelo usuário pode ser invocada com um argumento que varia de acordo com o contexto da linha é quando a função definida pelo usuário é composta apenas por funções escalares e não usa toscalar().

Exemplos

Função escalar com suporte

Há suporte para a consulta a seguir porque f é uma função escalar que não faz referência a 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 a seguir tem suporte porque f é uma função escalar que faz referência à expressão Table1 tabular, mas é invocada sem 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 sem suporte

A consulta a seguir não tem suporte porque f é uma função escalar que faz referência à 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 sem suporte

A consulta a seguir não tem suporte porque f é uma função tabular que é invocada em um 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)

Recursos que atualmente não são compatíveis com as funções definidas pelo usuário

Para fins de integridade, aqui estão alguns recursos comumente solicitados para funções definidas pelo usuário que atualmente não têm suporte:

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

  2. Valores padrão: O valor padrão do parâmetro escalar de uma função deve ser um literal escalar (constante).