注释
DAX 用户定义函数目前为 预览版。
DAX 用户定义函数(UDF)允许打包 DAX 逻辑,并像使用任何其他 DAX 函数一样重复使用它。 UDF 引入了新的 FUNCTION 关键字、可选 参数 (标量、表和引用),以及 类型检查 帮助程序,使创作更安全、更清晰。
定义 UDF 后,可以在度量值、计算列、视觉计算甚至其他用户定义的函数中使用。 用户可以集中业务规则,提高可维护性,并随着时间的推移安全地改进计算。 Functions 是可以在查询视图和 DAX创建和管理的一流模型对象,可以在 Functions 节点下的模型资源管理器中查看它们。
启用用户定义的函数
若要在桌面中试用 UDF,请执行以下作:
- 转到 “文件 > 选项”和“设置 > 选项”。
- 选择 预览功能 并检查 DAX 用户定义的函数。
- 选择 “确定 ”并 重启 Power BI Desktop。
定义和管理用户定义函数
有几个位置可以定义和管理函数:
- DAX 查询视图 (DQV)。 在 DQV 中定义和修改函数。 DQV 还包括上下文菜单 快速查询(评估、定义和评估,以及定义该模型中所有函数),帮助你更快地测试和管理 UDF。
- TMDL 视图。 也可以在 TMDL 中创作和编辑 UDF。 TMDL 视图还包括上下文菜单 脚本 TMDL 到。
- 模型资源管理器。 可以在模型资源管理器中的 Functions 节点下查看现有函数。
定义 UDF 时,请遵循以下命名要求:
函数名称:
- 在模型中必须格式良好且唯一。
- 可以包括命名空间中的句点(例如Microsoft.PowerBI.MyFunc)。 不能以句点开头或结束,也不能有连续的句点。
- 除了句点以外,名称只能包含字母数字字符或下划线。 不允许使用空格或特殊字符。
- 不得与内置 DAX 函数或保留字(例如度量值、函数、定义)冲突。
参数名称:
- 只能包含字母数字字符或下划线。 不允许使用句点。
- 不能是保留字。
使用 DAX 查询视图
可以在查询视图中定义、更新和评估用户定义的函数 DAX 。 有关查询视图的其他 DAX 信息,请参阅 DAX 查询视图。
一般形式
DEFINE
/// Optional description above the function
FUNCTION <FunctionName> = ( [ParameterName]: [ParameterType], ... ) => <FunctionBody>
小窍门
使用 /// 来描述函数。 单行 (//) 或多行 (/* */) 注释将不会显示在 IntelliSense 函数说明中。
示例:简单税务函数
DEFINE
/// AddTax takes in amount and returns amount including tax
FUNCTION AddTax =
( amount : NUMERIC ) =>
amount * 1.1
EVALUATE
{ AddTax ( 10 ) }
// Returns 11
保存至模型
若要将 UDF 从 DAX 查询视图保存到模型,请执行以下步骤:
- 单击 更新模型并保存更改 以保存查询中的所有 UDF。
- 或单击“ 更新模型:在定义的函数上方添加新函数 以保存单个 UDF”。
使用 TMDL 视图
可以在 TMDL 视图中定义和/或更新用户定义的函数。 有关 TMDL 视图的其他信息,请参阅 TMDL 视图。
通用形式
createOrReplace
/// Optional description above the function
function <FunctionName> = ( [ParameterName]: [ParameterType], ... ) => <FunctionBody>
示例:简单税务函数
createOrReplace
/// AddTax takes in amount and returns amount including tax
function AddTax =
(amount : NUMERIC) =>
amount * 1.1
保存到模型
单击视图顶部的 “应用 ”按钮,将脚本中的所有 UDF 保存到模型。
在 Power BI 项目中使用 TMDL 脚本
使用 Power BI 项目时,语义模型 TMDL 脚本中还包括 UDF。 可以在functions.tmdl文件夹中找到它们。
使用模型资源管理器
可以从 “函数 ”节点下的模型资源管理器查看模型中的所有用户定义函数。 有关模型资源管理器的其他信息,请参阅 模型资源管理器。
在 DAX 查询视图中,可以在模型资源管理器中 UDF 的右键单击菜单中使用 快速查询 轻松定义和评估函数。
在 TMDL 视图中,可以将函数 拖放 到画布中,或使用 脚本 TMDL 在 模型资源管理器中 UDF 的右键单击菜单中生成脚本。
使用动态管理视图 (DMV) 检查用户定义函数 (UDF)
可以使用 动态管理视图 (DMV)检查模型中的 UDF。 通过这些视图,可以查询有关函数的信息,包括 UDF。
可以使用 INFO.FUNCTIONS 函数来检验模型中的 UDF。 若要仅将结果限制为 UDF,请将 ORIGIN 参数指定为 2.
EVALUATE INFO.FUNCTIONS("ORIGIN", "2")
此查询返回模型中当前所有 UDF 的表,包括其名称、说明和关联的元数据。
使用用户定义的函数
定义 UDF 并将其保存到模型后,可以从度量值、计算列、视觉计算和其他 UDF 调用它。 这与调用内置 DAX 函数的工作方式相同。
调用 UDF 以生成度量值
使用度量值中的 UDF 应用具有完整筛选器上下文的可重用逻辑。
Total Sales with Tax = AddTax ( [Total Sales] )
下表显示了示例度量值:
在计算列中调用 UDF
UDF 可用于计算列,以将可重用逻辑应用于表中的每一行。
Sales Amount with Tax = CONVERT ( AddTax ( 'Sales'[Sales Amount] ), CURRENCY )
我们可以看到下表中使用的此示例度量值:
在视觉计算中调用 UDF
可以在视觉对象计算中使用 UDF 将逻辑直接应用于视觉对象。 有关视觉计算的其他信息,请参阅 视觉计算。
注释
视觉计算仅对视觉对象中存在的字段进行操作。 它们无法访问不属于视觉对象的模型对象,并且不能在此上下文中将模型对象(如不在视觉对象中的列或度量值)传递到 UDF。
Sales Amount with Tax = AddTax ( [Sales Amount] )
我们可以看到下表中的此示例度量值:
在另一个 UDF 中调用 UDF
可以通过调用另一函数来嵌套 UDF。 在此示例中,我们定义简单的 AddTax UDF,并在另一个 UDF AddTaxAndDiscount中调用它。
DEFINE
/// AddTax takes in amount and returns amount including tax
FUNCTION AddTax =
( amount : NUMERIC ) =>
amount * 1.1
FUNCTION AddTaxAndDiscount =
(
amount : NUMERIC,
discount : NUMERIC
) =>
AddTax ( amount - discount )
EVALUATE
{ AddTaxAndDiscount ( 10, 2 ) }
// Returns 8.8
参数
DAX UDF 可以接受零个或多个参数。 为 UDF 定义参数时,可以选择为每个参数指定类型提示:
-
类型:参数接受的值类型(
AnyVal、Scalar或TableAnyRef)。 -
子类型(仅适用于标量类型):特定的标量数据类型(
Variant、、、Int64DecimalDouble、String、、DateTime或)。BooleanNumeric -
ParameterMode:计算参数时(
val或expr)。
类型提示采用以下形式: [type] [subtype] [parameterMode]
对于每个参数,可以包括所有这些类型提示、部分或无类型提示,以使函数在调用站点更安全、更可预测。 如果省略所有内容,只需写出参数名称,它的行为方式如同AnyVal val,这意味着参数会在调用时立即被计算。 这对于简单函数非常有用。
类型
类型定义参数接受的参数类别,以及它是作为 值 还是 表达式传递。
UDF 参数中有DAX两个类型系列:值类型和表达式类型:
值类型 :当调用函数时,此参数会被<立即计算>(预先求值),然后将计算结果传递到函数中。-
AnyVal:接受一个标量或一个数据表。 如果省略参数的类型,则这是默认值。 -
Scalar:接受标量值(还可以添加子类型)。 -
Table:接受表。
-
-
表达式类型:此参数传递 未计算的表达式 (延迟计算)。 该函数决定在何时以及什么上下文中对其进行评估。 当需要控制筛选器上下文(例如内部 CALCULATE)时,引用参数是必需的,并且非常有用。
expr类型可以是对列、表、日历或度量值的引用。-
AnyRef:接受引用(列、表、日历或度量值)。
-
子类型
子类型允许你定义特定的 Scalar 数据类型。 如果定义子类型,则无需将参数显式定义为类型 Scalar ,则会自动假定此参数。
子类型为:
-
Variant:接受任何标量。 -
Int64:接受一个整数。 -
Decimal:支持固定精度十进制(如货币或金钱)。 -
Double:接受浮点小数。 -
String:接受文本。 -
DateTime:接受日期/时间。 -
Boolean:接受 TRUE/FALSE。 -
Numeric:接受任何数值(Int64或DecimalDouble子类型)
参数模式
ParameterMode 控制参数表达式的计算时间和位置。 其中包括:
-
val(急切求值):在调用函数之前计算表达式一次。 然后将生成的值传递到函数中。 对于简单的标量或表输入,这很常见。 如果省略参数的参数模式(parameterMode),则该参数将使用默认值。 -
expr(延迟计算):表达式在函数内部计算,可能在不同的上下文中执行(例如行上下文或筛选器上下文),并且如果被多次引用或在迭代中使用,可能会被多次计算。 这对于引用参数是必需的,在需要控制评估上下文时非常有用。
类型 Scalar 可以使用 val 或者 expr。 当希望在调用方上下文中评估标量一次时,使用val。 如果要延时计算,并可能在函数内部应用上下文时,请使用 expr。 请参阅 示例:表参数 作为示例。
类型 AnyRef 必须是 expr,因为它的引用(列、表、度量值等)需要在函数的上下文中进行评估。
示例:类型转换
DEFINE
/// returns x cast to an Int64
FUNCTION CastToInt = (
x : SCALAR INT64 VAL
) =>
x
EVALUATE
{ CastToInt ( 3.4 ), CastToInt ( 3.5 ), CastToInt ( "5" ) }
// returns 3, 4, 5
这使用 Scalar 类型、Int64 子类型和 val 参数模式,以实现可预测的舍入和文本到数字的转换,并确保任何表达式都提前计算。 还可以通过仅包括以下示例中所示的 Int64 子类型来实现此目的。 非数值字符串将导致错误。
DEFINE
/// returns x as an Int64
FUNCTION CastToInt = (
x : INT64
) =>
x
EVALUATE
{ CastToInt ( 3.4 ), CastToInt ( 3.5 ), CastToInt ( "5" ) }
// returns 3, 4, 5
示例:表参数(值与表达式)
为了说明 UDF parameterMode 如何影响筛选器上下文,请考虑两个函数对“Sales”表中的行进行计数。
CALCULATETABLE(t, ALL('Date'))两者在函数体中使用,但一个参数声明为val(急切评估),另一个参数声明为expr(懒惰评估):
DEFINE
/// Table val: receives a materialized table, context can't be changed
FUNCTION CountRowsNow = (
t : TABLE VAL
) =>
COUNTROWS ( CALCULATETABLE ( t, ALL ( 'Date' ) ) )
/// Table expr: receives an unevaluated expression, context CAN be changed
FUNCTION CountRowsLater = (
t : TABLE EXPR
) =>
COUNTROWS ( CALCULATETABLE ( t, ALL ( 'Date' ) ) )
EVALUATE
{
CALCULATE ( CountRowsNow ( 'Sales' ), 'Date'[Fiscal Year] = "FY2020" ),
CALCULATE ( CountRowsLater ( 'Sales' ), 'Date'[Fiscal Year] = "FY2020" )
}
// returns 84285, 121253
CountRowsNow 仅返回 2020 财年的销售计数。 在输入函数之前,“Sales”表已按年份进行筛选,因此 ALL('Date') 函数内部没有效果。
CountRowsLater 返回所有年份的销售计数。 该函数接收未计算的表表达式,并在ALL('Date')下进行计算,移除外部年份筛选器。
类型检查
UDF 中的类型检查可以使用新的和现有的类型检查函数来完成,可以在函数正文中调用,以确认传递的参数的运行时类型。 这允许 UDF 使用上下文控制,提前验证参数,在计算之前规范化输入。
注释
对于 expr parameterMode 参数,当参数在函数体中被引用时(而不是在函数调用时),会执行类型检查。
可用的类型检查函数
UDF 可以使用以下函数来测试标量值。 每个返回 TRUE/FALSE 取决于提供的值是否为该类型。
| 类别 | Functions |
|---|---|
| Numeric | ISNUMERIC、 ISNUMBER |
| Double | ISDOUBLE |
| 整数 | ISINT64, ISINTEGER |
| Decimal | ISDECIMAL、ISCURRENCY |
| String | ISSTRING, ISTEXT |
| 布尔 | ISBOOLEAN, ISLOGICAL |
| 日期和时间 | ISDATETIME |
示例:检查参数是否为字符串
DEFINE
/// Returns the length of a string, or BLANK if not a string
FUNCTION StringLength = (
s
) =>
IF ( ISSTRING ( s ), LEN ( s ), BLANK () )
EVALUATE
{ StringLength ( "hello" ), StringLength ( 123 ) }
// Returns: 5, BLANK
这可以防止错误,并允许你决定如何处理函数中的非字符串输入(在此示例中,返回 BLANK)。
示例:接受多个参数类型
DEFINE
/// Helper 1: get currency name by int64 key
FUNCTION GetCurrencyNameByKey = (
k : INT64
) =>
LOOKUPVALUE ( 'Currency'[Currency], 'Currency'[CurrencyKey], k )
/// Helper 2: get currency name by string code
FUNCTION GetCurrencyNameByCode = (
code : STRING
) =>
LOOKUPVALUE ( 'Currency'[Currency], 'Currency'[Code], code )
/// Accepts key (int64) or code (string) and returns the currency name
FUNCTION GetCurrencyName = (
currency
) =>
IF (
ISINT64 ( currency ),
GetCurrencyNameByKey ( currency ),
GetCurrencyNameByCode ( currency )
)
EVALUATE
{ GetCurrencyName ( 36 ), GetCurrencyName ( "USD" ) }
// returns "Euro", "US Dollar"
此示例演示如何使用 UDF 中的类型检查安全地接受多个输入类型并返回单个可预测的结果。
GetCurrencyName 采用一个参数, currency可以是整数货币键或文本货币代码。 该函数使用 ISINT64.. 检查参数类型。 如果输入为整数,则调用基于货币键查找货币名称的帮助程序 GetCurrencyNameByKey 。 如果输入不是整数,则调用基于货币代码查找货币名称的帮助程序 GetCurrencyNameByCode 。
同时定义多个函数
UDF 允许在单个查询或脚本中定义多个函数,以便轻松组织可重用逻辑。 如果要将相关计算或帮助程序例程封装在一起,这尤其有用。 函数可以一起或单独进行评估。
DEFINE
/// Multiplies two numbers
FUNCTION Multiply = (
a,
b
) =>
a * b
/// Adds two numbers and 1
FUNCTION AddOne = (
x,
y
) =>
x + y + 1
/// Returns a random integer between 10 and 100
FUNCTION RandomInt = () =>
RANDBETWEEN ( 10, 100 )
EVALUATE
{ Multiply ( 3, 5 ), AddOne ( 1, 2 ), RandomInt () }
// returns 15, 4, 98
高级示例:灵活的货币换算
为了说明 UDF 如何处理 DAX 更复杂的逻辑,我们将介绍货币换算方案。 此示例使用类型检查和嵌套函数将给定金额转换为目标货币,使用给定日期的平均或结束日汇率。
createOrReplace
function ConvertDateToDateKey =
(
pDate: scalar variant
) =>
YEAR ( pDate ) * 10000 + MONTH ( pDate ) * 100 + DAY ( pDate )
function ConvertToCurrency =
(
pCurrency:scalar variant,
pDate: scalar variant,
pUseAverageRate: scalar boolean,
pAmount: scalar decimal
) =>
var CurrencyKey =
EVALUATEANDLOG (
IF (
ISINT64 ( pCurrency ),
pCurrency,
CALCULATE (
MAX ( 'Currency'[CurrencyKey] ),
'Currency'[Code] == pCurrency
)
)
, "CurrencyKey"
)
var DateKey =
EVALUATEANDLOG (
SWITCH (
TRUE,
ISINT64 ( pDate ), pDate,
ConvertDateToDateKey ( pDate )
)
, "DateKey"
)
var ExchangeRate =
EVALUATEANDLOG (
IF (
pUseAverageRate,
CALCULATE (
MAX ( 'Currency Rate'[Average Rate] ),
'Currency Rate'[DateKey] == DateKey,
'Currency Rate'[CurrencyKey] == CurrencyKey
),
CALCULATE (
MAX ( 'Currency Rate'[End Of Day Rate] ),
'Currency Rate'[DateKey] == DateKey,
'Currency Rate'[CurrencyKey] == CurrencyKey
)
)
, "ExchangeRate"
)
var Result =
IF (
ISBLANK ( pCurrency ) || ISBLANK ( pDate ) || ISBLANK ( pAmount ),
BLANK (),
IF (
ISBLANK ( ExchangeRate ) ,
"no exchange rate available",
ExchangeRate * pAmount
)
)
RETURN Result
该 ConvertToCurrency 函数接受货币和日期的灵活输入类型。 用户可以直接提供货币密钥或日期密钥,或者提供货币代码或标准日期值。 该函数检查每个输入的类型并相应地处理它:如果是 pCurrency 整数,则将其视为货币键;否则,该函数假定货币代码并尝试解析相应的密钥。
pDate 遵循类似的模式,如果它是整数,则被视为日期键;否则,该函数假定它是标准日期值,并使用帮助程序函数转换为日期键 ConvertDateToDateKey 。 如果函数无法确定有效的汇率,则返回消息“没有可用的汇率”。
然后,可以使用此逻辑来定义度量值,例如 本地货币的总销售额。
Total Sales in Local Currency =
ConvertToCurrency (
SELECTEDVALUE ( 'Currency'[Code] ),
SELECTEDVALUE ( 'Date'[DateKey] ),
TRUE,
[Total Sales]
)
这可以选择性地与 动态格式字符串 配对,以适当的货币格式显示结果。
CALCULATE (
MAX ( 'Currency'[Format String] ),
'Currency'[Code] == SELECTEDVALUE ( 'Currency'[Code] )
)
可以在下面的屏幕截图中看到一个示例结果。
注意事项和限制
用户定义函数目前处于预览状态,在预览期间,请注意以下注意事项和限制:
常规:
- 无法在服务中创建或定义DAX UDF。
- 无法在模型中隐藏或取消隐藏用户定义函数(UDF)。
- 无法将 UDF 放入显示文件夹中。
- 功能区中没有“创建函数”按钮。
- 无法将用户自定义函数 (UDF) 与翻译合并。
- 在没有表格的模型中不支持 UDF。
定义 UDF:
- 不支持递归或相互递归。
- 不支持函数重载。
- 不支持显式返回类型。
UDF 参数:
- 不支持可选参数。
- 不支持参数说明。
- UDF 无法返回值
enum。 接受enum值作为其函数参数的内置函数将无法在该上下文中使用 UDF。 - 未绑定的类型提示
expr参数不被计算。
IntelliSense 支持:
- 尽管 UDF 可以在实时连接或复合模型中使用,但没有 IntelliSense 支持。
- 尽管 UDF 可用于视觉计算,但视觉计算公式栏没有对 UDF 的 IntelliSense 支持。
- TMDL 视图对 UDF 没有适当的 IntelliSense 支持。
已知 bug
目前已知以下问题,可能会影响功能:
- 重命名这些对象时,不会自动更新对 UDF 中的表格模型对象(例如度量值、表、列)的引用。 如果重命名 UDF 所依赖的对象,函数正文仍将包含旧名称。 必须手动编辑 UDF 表达式才能更新对重命名对象的所有引用。
- 涉及 UDF 的某些高级方案可能会导致分析程序不一致。 例如,当用户将列作为
expr参数传递或使用非限定列引用时,可能会看到红色下划线或验证错误。