DAX 用户定义的函数(预览版)

注释

DAX 用户定义函数目前为 预览版

DAX 用户定义函数(UDF)允许打包 DAX 逻辑,并像使用任何其他 DAX 函数一样重复使用它。 UDF 引入了新的 FUNCTION 关键字、可选 参数 (标量、表和引用),以及 类型检查 帮助程序,使创作更安全、更清晰。 定义 UDF 后,可以在度量值计算列视觉计算甚至其他用户定义的函数中使用。 用户可以集中业务规则,提高可维护性,并随着时间的推移安全地改进计算。 Functions 是可以在查询视图和 DAX创建和管理的一流模型对象,可以在 Functions 节点下的模型资源管理器中查看它们。

启用用户定义的函数

若要在桌面中试用 UDF,请执行以下作:

  1. 转到 “文件 > 选项”和“设置 > 选项”。
  2. 选择 预览功能 并检查 DAX 用户定义的函数
  3. 选择 “确定 ”并 重启 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”。

Power BI Desktop 中查询视图的 DAX 屏幕截图,其中突出显示了可以保存用户定义的函数的两个位置。第一个是视图顶部有更改按钮的更新模型。第二个是标记为“更新模型”的代码编辑器中的状态行:添加新函数

使用 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 Desktop 中 TMDL 视图的屏幕截图,其中突出显示了视图顶部的“应用”按钮。这是可以保存用户定义的函数的位置。

在 Power BI 项目中使用 TMDL 脚本

使用 Power BI 项目时,语义模型 TMDL 脚本中还包括 UDF。 可以在functions.tmdl文件夹中找到它们。

Power BI 项目的 Visual Studio Code 屏幕截图。资源管理器打开了语义模型文件夹。“functions.tmdl”在代码编辑器中打开。显示了三个函数:CustomerLifetimeValue、AverageOrderValue 和 AddTax。

使用模型资源管理器

可以从 “函数 ”节点下的模型资源管理器查看模型中的所有用户定义函数。 有关模型资源管理器的其他信息,请参阅 模型资源管理器

Power BI Desktop 中的“模型资源管理器”面板,其中显示了展开的“Functions”节点。列出了三个用户定义的函数:AddTax、AverageOrderValue 和 CustomerLifetimeValue。

DAX 查询视图中,可以在模型资源管理器中 UDF 的右键单击菜单中使用 快速查询 轻松定义和评估函数。

Power BI Desktop 中的“模型资源管理器”窗格显示展开的“Functions”节点。打开两个上下文菜单:第一个菜单提供快速查询、重命名、从模型中删除、在报表视图中隐藏、全部隐藏、全部折叠和全部展开。突出显示并选中快速查询。第二个菜单突出显示,并提供快速查询选项“评估”、“定义和评估”、“定义新函数”和“定义此模型中的所有函数”。

TMDL 视图中,可以将函数 拖放 到画布中,或使用 脚本 TMDL 在 模型资源管理器中 UDF 的右键单击菜单中生成脚本。

Power BI Desktop 中的“模型资源管理器”窗格显示展开的“函数”节点。打开两个上下文菜单:第一个菜单提供“导出脚本到 TMDL”、“重命名”、“从模型中删除”、“在报表视图中隐藏”、“取消隐藏所有”、“全部折叠”和“全部展开”。“导出脚本到 TMDL”被突出显示并选中。第二个菜单被突出显示,并提供“导出脚本到 TMDL”选项:“脚本”选项卡和“剪贴板”。

使用动态管理视图 (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 可用于计算列,以将可重用逻辑应用于表中的每一行。

注释

在计算列中使用 UDF 时,请确保函数返回一致的类型的标量。 有关详细信息,请参阅 参数 。 如果需要,请使用 CONVERT 或类似函数将结果转换为目标类型。

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 定义参数时,可以选择为每个参数指定类型提示:

  • 类型:参数接受的值类型(AnyValScalarTableAnyRef)。
  • 子类型(仅适用于标量类型):特定的标量数据类型(Variant、、、Int64DecimalDoubleString、、DateTime或)。 BooleanNumeric
  • ParameterMode:计算参数时(valexpr)。

类型提示采用以下形式: [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:接受任何数值(Int64DecimalDouble子类型)

参数模式

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 ISNUMERICISNUMBER
Double ISDOUBLE
整数 ISINT64, ISINTEGER
Decimal ISDECIMALISCURRENCY
String ISSTRINGISTEXT
布尔 ISBOOLEANISLOGICAL
日期和时间 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 参数传递或使用非限定列引用时,可能会看到红色下划线或验证错误。