Power BI模块化DAX时代来临!一文全面了解DAX自定义函数

DAX用户定义函数 (User-Defined Functions, 以下简称UDF) ,标志着DAX语言演进的一个重要里程碑,DAX将从一门纯粹的“公式”或“表达式”语言,演变为一门更具“工程化”和“模块化”能力的语言。

它允许将一段复杂的DAX计算逻辑封装成一个独立的、可命名的“函数”,并能在模型中的任何度量值、计算列中被反复调用 。PowerBI报告开发者可以将复杂的业务逻辑抽象成独立的、可测试的模块,然后像积木一样在整个数据模型中构建更高级的度量值。

本文将带你全面了解UDF的使用方法和核心功能。

如何启用DAX UDF?

DAX用户定义函数目前仍处于预览阶段 ,需要手动启用它。更新到9月版本后,前往 文件 > 选项和设置 > 选项 ,在预览功能 选项卡中,勾选"DAX用户定义函数":

640

点击确定并重启Power BI Desktop ,就可以使用这个功能了。

如何创建UDF?

启用后现在可以通过两个位置来定义UDF:DAX查询视图TMDL视图

在这两个视图中,直接根据语法要求编写公式即可,下面会具体介绍。

UDF语法结构

UDF的基本语法结构如下:

FUNCTION  函数名称=

   (参数:类型和模式)=>

     函数主体

定义 UDF 时,请遵循以下命名要求:

函数名称:

在模型中必须格式良好且唯一。

可以包括命名空间中的句点(例如Microsoft.PowerBI.MyFunc)。 不能以句点开头或结束,也不能有连续的句点。

除了句点以外,名称只能包含字母数字字符或下划线, 不允许使用空格或特殊字符。

不得与内置 DAX 函数或保留字(例如度量值、函数、定义)冲突。

参数名称:

参数用括号包裹,括号中可以有多个参数,用逗号分隔,参数名只能包含字母数字字符或下划线,不允许使用句点,不能是保留字。

参数类型和传递模式定义参数时,可以选择为每个参数指定类型和传递模式,采用以下形式(参数类型、子类型和传递模式按顺序书写,中间用空格隔开,可全部省略或部分省略):参数名称:[参数类型] [子类型] [传递模式]

参数类型:AnyVal(任意)、Scalar(标量值)或Table(表)

子类型:只有当参数类型为Scalar(标量值)时,才需要定义子类型;

传递模式:可以是val或expr。

  • val:Value的简称,在调用函数之前计算表达式一次, 然后将生成的值传递到函数中,函数执行期间不再变化。
  • expr: Expression的简称,表达式在函数内部计算,可能在不同的上下文中执行(例如行上下文或筛选器上下文),并且如果被多次引用或在迭代中使用,可能会被多次计算。 

关于参数类型、子类型和传递模式也不用刻意记,需要用时可以从下表查找,其中最关键的是传递模式,写错可能导致自定义函数无法正确计算。

640-1

类型检查

为了使 DAX 和 TMDL/TMSL 更加一致,还引入了一套新的函数来进行类型检查,并更新了 DATATABLE、CONVERT 和 EXTERNALMEASURE 函数,使其能够与所有同义词一起使用。例如,现在添加了 ISSTRING,它是 ISTEXT 的替代函数,以及 ISNUMERIC,它是 ISNUMBER 的补充函数。

通过上面的语法看起来好像比较复杂,其实用起来并不难,下面通过几个示例来看看它具体怎么用。

UDF应用示例

假如我们想要创建一个函数,来计算两个参数的平方之和,就可以这样写个自定义函数。

打开DAX查询视图,输入:

DEFINE  

FUNCTION SumofSquares=

    (x,y)=>

    x^2+y^2

其中SumofSquares是函数名,x、y是参数,x^2+y^2是计算逻辑。

这里参数x和y的类型都省略了,默认参数类型是AnyVal、传递模式是val,上面的写法等价于:

DEFINE

FUNCTIONSumofSquares=

    (x:Anyval Val,y:Anyval Val)=>

    x^2+y^2

其实这里要计算数据的平方,其实参数应该指定为数值型,严格的写法应该是将参数指定为数值型(可以只定义子类型为数值型,传递模型省略默认为val):

DEFINE

FUNCTIONSumofSquares=

    (x:numeric,y:numeric)=>

    x^2+y^2

上面三种写法都能正常计算两个数值的平方和,通过它们主要是认识一下参数类型的用法。

640-2

然后点击代码上方的"更新模型:添加新函数",或者“使用更改更新模型”按钮,一个UDF就创建好了。

在模型资源管理器的函数中,可以看到模型中所有已创建的UDF :

640-3

然后这个UDF就可以在度量值或者计算列等场景中使用,比如写个度量值=SumofSquares(1,3)

它就会按我们定义的逻辑来计算这两个数字的平方之和.

640-4

TMDL视图中同样可以创建UDF,语法与DAX查询视图基本一致,只需要将前面的DEFINE  改成createOrReplace就可以了,比如上面的UDF在TMDL视图中这样写:

640-5

无论是TMDL视图,还是DAX查询视图,都可以一次性创建多个UDF,比如创建两个函数,来计算两个数的平方和、立方和。

在DAX查询视图可以这样写:

640-6

TMDL视图的写法:

640-7

这样就会同时创建两个UDF,利用这种做法可以实现批量创建UDF。上面用了个简单的例子来说明UDF的基本用法,下面再来看个更符合实际数据分析需求的例子。
比如常用的上年同期,写销售额的上年同期时,你需要用时间智能函数写一遍;计算利润的上年同期时,你还需要再写一遍,现在我们就可以将上年同期封装成自定义函数。根据上面的介绍,你可能会这样写UDF:

FUNCTION sply=

    (amount)=>

    CALCULATE(

        amount,

        SAMEPERIODLASTYEAR('日期表'[日期])

    )

640-8

然后我们用这个UDF来写度量值看看是什么效果:

销售额 上年同期 =sply([销售额])

640-9

结果和本年一样,并没有上年同期的效果,这是怎么回事呢?

问题出在参数传递模式上,上面的参数省略了传递模式,默认是val,在调用函数之前计算表达式一次,然后就不再变化,所以它无法迭代计算上年同期的结果。(这个效果很像VAR变量的逻辑 )

解决的方法很简单,只需要把传递模式指定为expr,参数就会在上年同期的日历中进行迭代计算。

640-10

更改传递模式后,用这个UDF建立的度量值正常计算出了上年同期:

640-11

UDF也支持嵌套,比如我们在上面已经定义上年同期的基础上,定义个同比的UDF,就可以这样写:

640-12

然后利用这个UDF就可以轻松计算销售额同比:

销售额 同比 =yoy([销售额])

640-13

有了封装的上年同期和同比UDF,写任何指标的同类计算都会变得非常简单,比如计算利润的这两个指标,只需要将上面度量值中的销售额改成利润:

利润上年同期 =sply([利润])

利润 同比 =yoy([利润])

是不是变得非常简单呢?UDF的引入将极大地扩展DAX的应用边界,尤其是在以下高阶场景中:

标准化业务规则: 在大型组织中,许多业务规则需要在不同的报告和分析中保持一致。通过将这些规则封装成DAX UDFs,可以确保所有开发者都能调用同一套标准逻辑,从而避免因手动编写而产生的不一致性。

参数化复杂计算: DAX UDFs可以被设计为接受参数,从而实现更灵活的计算。例如,可以创建一个UDF来计算不同折扣率下的销售额,只需在调用时传入不同的折扣参数即可。

抽象底层逻辑: 当数据模型中存在复杂的表关系或需要通过复杂的筛选逻辑才能获取正确的数据时,可以将这些底层逻辑封装在DAX UDF中,从而简化上层度量值的代码,使开发者可以专注于业务分析本身。

DAX自定义函数的出现,还将会有一个全新的发展方向:创建可共享的DAX自定义函数库,DAX开发者将能够封装和分享他们编写的UDF,相信大家很快就会看到DAX自定义函数的爆炸式出现。之前用上百行SVG代码制作的可视化效果,现在也可以利用UDF封装,封装后只需要写个简单的度量值,就可以实现惊艳的可视化效果,下面这个视频展示的效果就是用UDF实现的。

DAX用户定义函数目前处于预览状态,现在使用时,请注意官方公布的以下注意事项和限制:

常规:

无法在服务中创建或定义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 参数传递或使用非限定列引用时,可能会看到红色下划线或验证错误。

以上就是目前UDF的基本用法,后面还会介绍更多进阶的玩法。