用户定义的函数

用户定义的函数是可重复使用的子查询,可将其定义为查询本身(查询定义的函数)的一部分,或作为数据库元数据(存储函数)的一部分长期存储。 通过“名称”调用用户定义的函数,为其提供零个或多个“输入参数”(可以是标量或表格),这些函数将根据函数体生成单个值(可以是标量或表格) 。

用户定义的函数属于以下两个类别之一:

  • 标量函数
  • 表格函数

函数的输入参数和输出决定它是标量还是表格,进而确定其使用方式。

若要在单个查询中优化用户定义函数的多次使用,请参阅优化使用命名表达式的查询

标量函数

  • 具有零个输入参数,或者其所有输入参数均为标量值
  • 生成单个标量值
  • 可在允许使用标量表达式的任何地方使用
  • 只能使用定义它的行上下文
  • 只能引用可访问架构中的表(和视图)

表格函数

  • 接受一个或多个表格输入参数、零个或多个标量输入参数和/或:
  • 生成单个表格值

函数名称

有效的用户定义的函数名称必须遵循与其他实体相同的标识符命名规则

该名称在定义的范围内也必须是唯一的。

注意

如果存储函数和表的名称相同,则对该名称的任何引用都将解析为存储函数,而不是表名。 请改为使用表函数引用表。

输入参数

有效的用户定义的函数遵循以下规则:

  • 用户定义的函数具有一个包含零个或多个输入参数的强类型列表。
  • 输入参数具有名称、类型和(对于标量参数)默认值
  • 输入参数的名称是一个标识符。
  • 输入参数的类型为标量数据类型之一或表格架构。

语法上,输入参数列表是用逗号分隔的参数定义列表,并用括号括起来。 每个参数定义都指定为

ArgName:ArgType [= ArgDefaultValue]

对于表格参数,ArgType 具有与表定义相同的语法(括号和列名/类型对列表),另外还有一个单独的 (*)(指示“任何表格架构)。

例如:

语法 输入参数列表说明
() 无参数
(s:string) 名为 s 的单个标量参数,其值类型为 string
(a:long, b:bool=true) 两个标量参数,其中的第二个参数具有默认值
(T1:(*), T2(r:real), b:bool) 三个参数(两个表格参数和一个标量参数)

注意

同时使用表格输入参数和标量输入参数时,请将所有表格输入参数置于标量输入参数之前。

示例

标量函数

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

不带参数的表格函数

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

带参数的表格函数

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

输出

x
9
10

使用未指定列的表格输入的表格函数。 可将任意表传递给函数,并且不能在函数内引用任何表列。

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

输出

x
1
2
3

声明用户定义的函数

用户定义的函数的声明提供:

  • 函数名称
  • 函数架构(它接受的参数,如果有)
  • 函数体

注意

不支持重载函数。 不能创建多个具有相同名称和不同输入架构的函数。

提示

Lambda 函数没有名称,该函数使用 let 语句绑定到名称。 因此,可将其视为用户定义的存储函数。 示例:Lambda 函数的声明,该函数接受两个参数(称为 sstring 和称为 ilong)。 它将返回第一个(将其转换为数字后)和第二个的乘积。 Lambda 绑定到名称 f

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

函数体包括:

  • 恰好一个表达式,该表达式提供函数的返回值(标量或表格值)。
  • 任何数量(零个或多个)的 let 语句,这些语句的范围是函数体的范围。 如果指定,则 let 语句必须在定义函数返回值的表达式之前。
  • 任何数量(零个或多个)的查询参数语句,这些语句声明函数使用的查询参数。 如果指定,则这些语句必须在定义函数返回值的表达式之前。

注意

函数体内不支持“顶级”查询所支持的其他类型的查询语句。 必须用分号分隔任意两个语句。

用户定义的函数示例

以下部分演示了如何使用用户定义的函数的示例。

使用 let 语句的用户定义的函数

以下示例显示接受名为 ID 的参数的用户定义函数 (lambda)。 该函数绑定到名称 Test 并使用三个 let 语句,其中 Test3 定义使用 ID 参数。 运行时,查询的输出为 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

定义参数默认值的用户定义的函数

以下示例演示一个接受三个参数的函数。 后两个参数具有默认值,并且不必存在于调用站点。

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"

调用用户定义的函数

调用用户定义函数的方法取决于函数预期接收的参数。 以下部分介绍如何调用不带参数的 UDF调用带标量参数的 UDF 以及调用带表格参数的 UDF

调用不带参数的 UDF

不带参数的用户定义的函数可以通过函数名称或名称以及括号中的空参数列表进行调用。

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

调用带标量参数的 UDF

可以使用函数名称和括号中的具体参数列表来调用采用一个或多个标量参数的用户定义的函数:

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

调用带表格参数的 UDF

可以使用函数名称和括号中的具体参数列表来调用采用一个或多个表参数(有任意数量的标量参数)的用户定义的函数:

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

还可以使用运算符 invoke 来调用采用一个或多个表参数并返回一个表的用户定义的函数。 如果函数的第一个具体表参数是 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(":-)")

默认值

在以下条件下,函数可以为其某些参数提供默认值:

  • 只能为标量参数提供默认值。
  • 默认值始终为文本(常量)。 它们不能是任意计算。
  • 没有默认值的参数始终位于具有默认值的参数之前。
  • 调用方必须提供所有参数的值,并且没有以与函数声明相同的顺序进行排列的默认值。
  • 调用方不需要为具有默认值的参数提供值,但可以这样做。
  • 调用方可以按与参数顺序不匹配的顺序提供参数。 如果是这样,则他们必须为其参数命名。

以下示例返回一个包含两个相同记录的表。 在 f 的第一次调用中,参数被完全“打乱”,因此每个参数都被明确赋予了一个名称:

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"

输出

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

视图函数

如果用户定义的函数不采用任何参数并返回一个表格表达式,则可以将其标记为“视图”。 将用户定义的函数标记为视图意味着每当执行通配符表名称解析时,该函数的行为就类似于表。

以下示例演示两个用户定义的函数(T_viewT_notview),并展示 union 中的通配符引用如何仅解析第一个函数:

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

限制

存在以下限制:

  • 用户定义的函数无法传入 toscalar() 调用信息中,该信息取决于调用该函数的行上下文。
  • 不能使用随行上下文变化的参数来调用返回表格表达式的用户定义函数。
  • 无法在远程群集上调用至少具有一个表格输入的函数。
  • 无法在远程群集上调用标量函数。

只有当用户定义的函数仅由标量函数构成,且不使用 toscalar() 时,才可以使用随行上下文而变化的参数来调用用户定义的函数。

示例

支持的标量函数

支持以下查询,因为 f 是一个不引用任何表格表达式的标量函数。

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)

支持以下查询,因为 f 是引用表格表达式 Table1 但调用时不引用当前行上下文 f(10) 的标量函数:

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)

不支持的标量函数

不支持以下查询,因为 f 是引用表格表达式 Table1 的标量函数,并且是使用对当前行上下文 f(Column) 的引用来调用的:

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)

不支持的表格函数

不支持以下查询,因为 f 是在一个需要标量值的上下文中调用的表格函数。

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)

用户定义的函数当前不支持的功能

为了确保完整性,下面列出了用户定义的函数当前不支持的一些常见请求功能:

  1. 函数重载:目前没有办法重载函数(一种创建多个具有相同名称和不同输入架构的函数的方法)。

  2. 默认值:函数的标量参数默认值必须是标量文本(常量)。