Определяемые пользователем функции

Определяемые пользователем функции — это многократно используемые вложенные запросы, которые можно определить как часть самого запроса (определяемые запросом функции) или сохранить как часть метаданных базы данных (хранимые функции). Определяемые пользователем функции вызываются с указанием имени, предоставляются с входными аргументами (которые могут быть скалярными или табличными) или без них. Кроме того, они выдают одно значение (которое может быть скалярным или табличным) на основе тела функции.

Определяемая пользователем функция может принадлежать к одной из двух категорий:

  • Скалярные функции
  • табличные функции.

Входные аргументы и выходные данные функции определяют, является ли она скалярной или табличной, что обуславливает способ ее использования.

Сведения об оптимизации нескольких использования определяемых пользователем функций в одном запросе см. в статье Оптимизация запросов, использующих именованные выражения.

Скалярные функции

  • Не имеет входных аргументов, или же все ее входные аргументы являются скалярными значениями.
  • Выдает одно скалярное значение.
  • Может использоваться в тех случаях, если разрешены скалярные выражения.
  • Может использовать только контекст строки, в которой он определен
  • Может ссылаться только на таблицы (и представления), которые находятся в доступной схеме.

Табличная функция

  • Принимает один или несколько табличных входных аргументов и ноль или несколько скалярных входных аргументов.
  • Выдает одно табличное значение.

Имена функций

Допустимые имена определяемых пользователем функций должны следовать правилам именования идентификаторов, которые применяются к другим элементам.

Имя также должно быть уникальным в своей области определения.

Примечание

Если имена хранимой функции и таблицы совпадают, любая ссылка на такое имя разрешается в имя хранимой функции, а не таблицы. Используйте табличную функцию для создания ссылки на таблицу.

Входные аргументы

К допустимым определяемым пользователям функциям применяются следующие правила:

  • Определяемая пользователем функция имеет строго типизированный список с нулевым или более входными аргументами.
  • Входной аргумент имеет имя, тип и (для скалярных аргументов) значение по умолчанию.
  • Имя входного аргумента является идентификатором.
  • Входной аргумент будет иметь один из типов скалярных данных либо тип табличной схемы.

Синтаксически список входных аргументов представляет собой разделенный запятыми список определений аргументов, заключенный в скобки. Каждое определение аргумента указывается таким образом:

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

Объявление определяемых пользователем функций

При объявлении определяемой пользователем функции указывается следующее:

  • имя функции;
  • схема функции (принимаемые параметры при их наличии);
  • тело функции.

Примечание

Перегрузка функций не поддерживается. Нельзя создать несколько функций с одним именем и разными входными схемами.

Совет

Лямбда-функции не имеют имени, но привязываются к нему с помощью инструкции let. Поэтому их можно считать определяемыми пользователем хранимыми функциями. Пример Объявление лямбда-функции, которая принимает два аргумента (string с именем s и long с именем i). Она возвращает результат первого аргумента (после преобразования его в число) и второй аргумент. Лямбда-функция привязана к имени f:

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

Тело функции включает следующее:

  • Ровно одно выражение, которое предоставляет возвращаемое значение функции (скалярное или табличное).
  • Любое число (0 или более) инструкций let, областью которых является тело функции. Если инструкции let указаны, они должны предшествовать выражению, определяющему возвращаемое значение функции.
  • Любое число (0 или более) инструкций параметров запросов, которые объявляют параметры запросов, используемые функцией. Если эти инструкции указаны, они должны предшествовать выражению, определяющему возвращаемое значение функции.

Примечание

Другие виды инструкций запросов, которые поддерживаются на "верхнем уровне" запросов, не поддерживаются в теле функции. Любые два оператора должны быть разделены точкой с запятой.

Примеры определяемых пользователем функций

В следующем разделе приведены примеры использования определяемых пользователем функций.

Определяемая пользователем функция, которая использует инструкцию let

В следующем примере показана определяемая пользователем функция (лямбда-выражение), которая принимает параметр с именем ID. Функция привязана к имени 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"

Вызов определяемой пользователем функции

Метод для вызова определяемой пользователем функции зависит от аргументов, которые функция ожидает получить. В следующих разделах описывается , как вызвать определяемую пользователем функцию без аргументов, вызвать определяемую пользователем функцию с скалярными аргументами и вызвать определяемую пользователем функцию с табличными аргументами.

Вызов определяемой пользователем функции без аргументов

Определяемая пользователем функция, которая не принимает аргументов и может вызываться либо по имени, либо по имени и пустому списку аргументов в скобках.

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

Вызов определяемой пользователем функции со скалярными аргументами

Определяемую пользователем функцию, которая принимает один или несколько скалярных аргументов, можно вызвать с помощью имени функции и конкретного списка аргументов в скобках:

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

Вызов определяемой пользователем функции с табличными аргументами

Определяемая пользователем функция, которая принимает один или несколько аргументов таблицы (с любым количеством скалярных аргументов) и может вызываться с помощью имени функции и конкретного списка аргументов в круглых скобках:

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_view и T_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. Значения по умолчанию: значение по умолчанию для скалярного параметра функции должно быть скалярным литералом (константой).