PowerBI/DAX之理解VAR变量

VAR变量可以在任意计值环境进行定义,并且它仅在定义时的计值环境里计算一次,在后续的引用中都不再计算,因此VAR变量其实是个常量,但为了与别的资料兼容,因此仍然称呼其为变量。

通过VAR变量,可以在计值环境即将发生变化之前,及时地使用当前的计值环境来计算所需要的值或提取所需要的表,并存储进变量里面。因此,使用VAR变量可以把复杂逻辑进行拆解,从而有效地简化DAX表达式的书写过程,甚至提高DAX表达式的性能。所以,善用VAR变量是成为高手的一个标志!


VAR变量的语法

语法主要为:

VAR <name1> = <expression1>
VAR <name2> = <expression2>
VAR <name3> = <expression3>
....
RETURN
<result expression>

其中,一个VAR关键字对应一个变量,变量可以定义多个,变量的表达式结果可以是标量值或表,最终通过RETURN语句返回一个结果,这个结果可以是标量值,也可以是表。在result expression里可以引用之前定义的各个变量,当然不引用也没问题,但不引用的话何必定义变量呢。

此外,VAR变量是可以嵌套的,即在某个变量的表达式中继续使用VAR变量,此时的语法为:

VAR <name1> = <expression1>

VAR <name2> = 
	VAR <name2_1> = <expression2_1>
	VAR <name2_2> = <expression2_2>
	....
	RETURN
	<result expression>
	
VAR <name3> = <expression3>
VAR <name4> = <expression4>
....
RETURN
<result expression>

上面这个语法是嵌套了两层VAR变量时的大概结构,当然也可以继续嵌套下去,只要语法正确,那么嵌套多少层都行。而且,不仅只是变量的表达式中可以嵌套VAR变量,在RETURN语句后的结果表达式中也是可以嵌套VAR变量的,而且也可以嵌套多层。比如:

VAR <name1> = <expression1>

VAR <name2> = 
	VAR <name2_1> = <expression2_1>
	VAR <name2_2> = <expression2_2>
	....
	RETURN
	<result expression>
	
VAR <name3> = <expression3>
VAR <name4> = <expression4>
....
RETURN
    VAR <name5> = 
        VAR <name5_1> = <expression5_1>
        VAR <name5_2> = <expression5_2>
        ....
        RETURN
        <result expression>
    RETURN
    <result expression>

由此可见,VAR变量的语法是很灵活的,但只要遵循一个最基本的原则就可以确保不出错,即:在每一个层级内,有VAR必有RETURN。


使用VAR变量时的注意事项

虽然VAR变量的语法很简单,但也有许多注意事项需要注意,否则很容易就会出错,具体如下。

1、变量的命名

变量的名称只支持英文字母、数字、以及下划线,并且不能以数字开头。而且,变量的名称也不能与DAX引擎的保留关键字相同,比如各种函数名称等。此外,后定义的变量名称也不能与之前已定义的变量名称相同,但内层嵌套的VAR变量名称可以使用外层的变量名称,但这会使得内层嵌套的VAR变量覆盖外层的同名变量。

如果变量命名不正确,那么DAX引擎将报错,如下图所示:

2、变量的引用顺序

在各个变量的表达式中,后定义的变量表达式中可以引用之前定义的变量,而RETURN语句后的结果表达式则可以引用同一层级内的所有变量。需要注意引用顺序是按变量定义的先后来决定的,否则DAX引擎将报错。

3、变量的作用域

我们已经知道,后定义的变量表达式中是可以引用之前定义的变量的,并且后定义的变量名称也不能与之前定义的变量名称相同,但这只是针对单层VAR变量的语法而言的。当存在VAR变量的嵌套时,内层的VAR变量的名称是可以使用外层的变量名称的,因此想要知道某个变量引用所指代的值具体是哪个,那么就要把变量的作用域搞清楚。如果你有编程语言的基础的话,那么这里的变量作用域,可以类比成编程语言中的全局变量与局部变量。

变量的作用域规则其实很好理解,即内层的变量可以引用外层的先定义变量,并且内层的变量可以覆盖或屏蔽外层的同名变量。因此我就不再赘述,下面直接看示例:

4、表变量中的列引用

从上面的讲解中我们可以知道,变量的表达式返回的结果可以是标量值或表,如果返回结果是表,那么一般称这个变量为表变量。那么此时,如果我们想要引用这个表变量中的某个列,对其进行聚合,又该如何操作呢?

很多人可能会想到聚合基础表中某个列的方式:SUM(TableName[ColumnName]) ,从而将其也应用到表变量中。但这是错误的语法,表变量中的列不能通过TableName[ColumnName]的方式进行引用。因此在这种情况下我们不能使用语法糖进行偷懒,需要使用聚合函数的对应迭代函数的等价写法,如:SUMX(TableVariableName,[ColumnName])

5、变量的计算次数

在本文的开头就已经提到过:“VAR变量可以在任意计值环境进行定义,并且它仅在定义时的计值环境里计算一次,在后续的引用中都不再计算,因此VAR变量其实是个常量。”

需要重点记住,VAR变量只计算一次,在后续的引用中都不会再次计算,哪怕后面计值环境发生变化了,它也是不会再次计算的。即VAR变量在首次计算完后,它就变成了一个常量,这一点需要重点记忆,能否真正理解VAR变量就看能否理解这一点了。下面来看一个帮助理解的例子:


VAR变量的应用场景

1、提高代码可读性

当所写的DAX表达式过长或各种函数的嵌套过多时,此时不妨定义一些VAR变量来存储部分结果,分步骤完成计算,从而提高DAX表达式的可读性,也更优雅。

下面来看一个未使用VAR变量的DAX表达式:

=
SUMX (
    SUMMARIZE ( 'T3销售', '日期表'[Date], 'T0大类'[T0大类K] ),
    IF (
        [Sales] <> BLANK (),
        CALCULATE (
            [Sales],
            LASTNONBLANK (
                FILTER ( ALL ( '日期表'[Date] ), '日期表'[Date] < EARLIER ( '日期表'[Date] ) ),
                [Sales]
            )
        )
    )
)

然后将其用VAR变量改写,结果如下:

=
SUMX (
    SUMMARIZE ( 'T3销售', '日期表'[Date], 'T0大类'[T0大类K] ),
    IF (
        [Sales] <> BLANK (),
        VAR CurrentDay = '日期表'[Date]
        VAR PastDate = FILTER ( ALL ( '日期表'[Date] ), '日期表'[Date] < CurrentDay )
        VAR LastDay = LASTNONBLANK ( PastDate, [Sales] )
        RETURN
        CALCULATE ( [Sales], LastDay )
    )
)

这里先不管这个DAX表达式的作用,只对比使用VAR变量前后的可读性。可以发现使用VAR变量后,代码被分步骤完成,从而使一个具有多重嵌套的复杂表达式变得容易阅读起来,所以这就是VAR变量的一个应用场景,即提高代码可读性。

2、降低DAX表达式的复杂性

VAR变量的一个特性就是仅在定义时的计值环境里计算一次,在后续的引用中都不再计算。因此,我们可以利用VAR变量的这个特性来降低DAX表达式的复杂性。

那么该如何降低复杂性呢?来考虑这样一个场景,我们已经通过DAX表达式更改初始计值环境,但现在想在更改后的计值环境里使用在初始计值环境下才能计算到的某个值,那么此时是不是要把更改后的计值环境还原回初始计值环境才能计算到那个值?是不是想一想都觉得麻烦?

因此,我们可以利用VAR变量仅计算一次的这个特性,在计值环境即将发生变化之前,及时地使用当前的计值环境来计算所需要的值或提取所需要的表并存储进变量里面,这样哪怕后面计值环境发生变化了,但变量的值是不会变化的,从而方便了后续的计算,进而降低了DAX表达式的复杂性。

3、提高计算效率

由于VAR变量仅在定义时的计值环境里计算一次,在后续的引用中都不再计算。因此当我们需要多次重复计算同一个值时,此时可以将该值定义成VAR变量,从而减少计算次数,进而提高计算效率。

比如下面这个计算销售金额年同比增长率的DAX表达式就多次计算了去年的销售金额,增加了不必要的计算开支:

Sales YoY Growth % =
DIVIDE (
    [Sales] - CALCULATE ( [Sales], SAMEPERIODLASTYEAR ( 'Date'[Date] ) ),
    CALCULATE ( [Sales], SAMEPERIODLASTYEAR ( 'Date'[Date] ) )
)

下面使用VAR变量对其进行改进:

Sales YoY Growth % =
VAR SalesPriorYear =
    CALCULATE([Sales], SAMEPERIODLASTYEAR('Date'[Date]))
RETURN
    DIVIDE([Sales] - SalesPriorYear, SalesPriorYear)

改进后的DAX表达式中,去年的销售金额仅计算一次,在保证结果正确的同时,也大大提高了计算效率。

<think>我们面对的是一个DAX问题:在Power BI中,如何对两个筛选后的数值进行除法运算。 根据用户的问题,我们需要计算两个筛选后的值的商。在DAX中,这通常涉及到先分别计算两个筛选条件下的值,然后进行除法运算。 步骤: 1. 理解需求:用户想要对两个不同的筛选条件下的数值进行除法运算。例如,计算某个产品在特定条件下的销售额与另一个条件下的销售额的比值。 2. 方法:我们可以使用DAX中的CALCULATE函数来改变筛选上下文,分别计算两个值,然后用除法运算符将它们相除。 示例: 假设我们有一个销售表,包含销售额(SalesAmount)和产品类别(ProductCategory)等字段。 需求:计算产品类别为“电子产品”的销售额与产品类别为“家具”的销售额的比值。 我们可以这样写: ``` Ratio = DIVIDE( CALCULATE(SUM(Sales[SalesAmount]), Sales[ProductCategory] = "电子产品"), CALCULATE(SUM(Sales[SalesAmount]), Sales[ProductCategory] = "家具") ) ``` 但是,这里需要注意的是,CALCULATE函数中的条件会覆盖外部上下文中的筛选。如果我们需要在同一个报表中同时保留两个不同的筛选条件,那么上述写法可能会因为上下文的不同而产生错误。 另一种更安全的方法是将两个计算分别定义为度量值,然后相除: ``` 电子产品销售额 = CALCULATE(SUM(Sales[SalesAmount]), Sales[ProductCategory] = "电子产品") 家具销售额 = CALCULATE(SUM(Sales[SalesAmount]), Sales[ProductCategory] = "家具") 比例 = DIVIDE([电子产品销售额], [家具销售额]) ``` 使用DIVIDE函数的好处是它可以自动处理分母为零的情况(返回空值或指定的替代值)。 如果用户希望在一个动态的筛选上下文中进行这种计算(比如根据切片器选择的不同条件),那么可能需要使用更高级的技术,比如使用ALLSELECTED函数来保留部分筛选,或者使用变量来分别计算两个值。 例如,在同一个报表中,我们可能希望根据年份来动态计算比例,那么可以这样写: ``` 动态比例 = VAR Electronics = CALCULATE(SUM(Sales[SalesAmount]), Sales[ProductCategory] = "电子产品") VAR Furniture = CALCULATE(SUM(Sales[SalesAmount]), Sales[ProductCategory] = "家具") RETURN DIVIDE(Electronics, Furniture) ``` 但是,这样写的话,两个CALCULATE都会受到外部上下文(如年份切片器)的影响。也就是说,它会计算当前所选年份的电子产品销售额和家具销售额,然后相除。 如果用户的需求是固定的两个条件,那么上述方法即可。如果条件不固定,可能需要通过参数表或者更复杂的DAX来动态改变条件。 根据引用[4]中提到的FILTER函数,我们也可以使用FILTER来创建复杂的筛选条件,但通常CALCULATE加上简单的布尔表达式更高效。 总结:在Power BI中使用DAX对两个筛选后的数值进行除法运算,核心是使用CALCULATE函数来应用筛选条件,然后使用除法运算符或DIVIDE函数进行除法运算。 注意:在编写DAX时,要注意上下文的影响。建议将两个计算分别定义为度量值,然后再相除,这样在报表中使用时更加灵活,也便于调试。 下面我们给出一个更通用的示例,假设用户需要根据不同的筛选条件(可能是动态的)来计算两个值的商。 例如,用户可能有两个不同的筛选条件,分别来自两个不同的切片器(比如一个选择地区,一个选择产品类型),然后计算这两个条件下的销售额的比值。 这时,我们可以这样: ``` 条件1销售额 = CALCULATE(SUM(Sales[SalesAmount]), Condition1_Fields = SelectedValue1, ...) 条件2销售额 = CALCULATE(SUM(Sales[SalesAmount]), Condition2_Fields = SelectedValue2, ...) 比例 = DIVIDE([条件1销售额], [条件2销售额]) ``` 但是,如果两个条件在同一张表的同一列上,那么同时应用两个条件可能会冲突。因此,我们需要确保两个条件不会冲突,或者分别在不同的列上。 如果用户需要在一个度量值中动态地指定两个条件,那么可能需要使用更高级的方法,比如使用表函数和变量。 由于用户的问题比较宽泛,我们给出一个基础而通用的解决方案。 因此,我们建议用户按照以下步骤操作: 1. 分别创建两个度量值,用于计算在各自筛选条件下的结果。 2. 再创建一个度量值,将前两个度量值相除。 这样,在报表中,用户就可以自由地使用其他切片器,而这两个度量值会响应外部上下文,然后相除得到比例。 示例代码: ``` // 度量值1: 条件A的销售额 Sales_A = CALCULATE(SUM('Sales'[Amount]), 'Sales'[Category] = "A") // 度量值2: 条件B的销售额 Sales_B = CALCULATE(SUM('Sales'[Amount]), 'Sales'[Category] = "B") // 度量值3: 比例 Ratio_A_B = DIVIDE([Sales_A], [Sales_B]) ``` 在报表中,如果用户添加了年份切片器,那么Ratio_A_B将自动计算当前所选年份下,类别A与类别B的销售额比例。 如果用户希望条件A和条件B不受外部上下文的影响(例如,总是计算整个数据集的类别A和类别B),那么可以在CALCULATE中使用ALL函数来移除筛选。 例如: ``` Sales_A_Total = CALCULATE(SUM('Sales'[Amount]), 'Sales'[Category] = "A", ALL('Sales')) ``` 但通常,我们会希望响应外部上下文(如年份、地区等),所以一般不使用ALL。 最后,如果用户的需求是更复杂的动态条件,可能需要使用参数表或者SWITCH函数来动态切换条件。 由于用户问题中没有详细说明筛选条件是否是动态变化的,我们给出最常用的方法。 因此,回答如下:</think>在 Power BI 中使用 DAX 对两个筛选后的数值进行除法运算,核心是通过 `CALCULATE` 函数结合筛选器动态计算分子和分母,再用 `DIVIDE` 函数处理除法。以下是具体实现方法: --- ### 解决方案(分步说明) #### 1. **基础语法结构** ```dax 结果 = DIVIDE( CALCULATE( [分子表达式], 分子筛选条件 ), CALCULATE( [分母表达式], 分母筛选条件 ), [可选:分母为零时的返回值] // 默认为空值 ) ``` #### 2. **实际应用示例** 假设需要计算 **华东地区销售额** 与 **所有地区销售额** 的比值: ```dax 销售占比 = DIVIDE( CALCULATE( SUM('销售表'[销售额]), '销售表'[地区] = "华东" ), // 分子:华东地区销售额 CALCULATE( SUM('销售表'[销售额]), ALL('销售表'[地区]) ), // 分母:所有地区销售额(用ALL移除地区筛选) 0 // 分母为零时返回0 ) ``` #### 3. **动态筛选场景** 若需基于切片器动态计算(如:**A产品销量** / **B产品销量**): ```dax 动态比值 = VAR 分子值 = CALCULATE( SUM('订单表'[销量]), TREATAS( {"A产品"}, '产品表'[品名] ) ) // 筛选A产品 VAR 分母值 = CALCULATE( SUM('订单表'[销量]), TREATAS( {"B产品"}, '产品表'[品名] ) ) // 筛选B产品 RETURN DIVIDE( 分子值, 分母值 ) ``` #### 4. **处理复杂筛选** 当筛选条件涉及多列时,用 `FILTER` 函数: ```dax 特殊场景比值 = DIVIDE( CALCULATE( [销售额], FILTER( '表', '表'[类别] = "X" && YEAR('表'[日期]) = 2023 ) ), CALCULATE( [销售额], FILTER( '表', '表'[类别] = "Y" ) ), BLANK() ) ``` --- ### 关键点说明 1. **`CALCULATE` 的核心作用** - 修改筛选上下文,实现动态计算[^2][^4]。 - 可嵌套 `FILTER`、`ALL`、`TREATAS` 等函数精确控制筛选范围。 2. **`DIVIDE` 的优势** - 自动处理分母为零的错误(优于直接使用 `/` 运算符)[^4]。 - 语法:`DIVIDE(分子, 分母, [错误返回值])`。 3. **筛选器类型选择** - 静态筛选:直接写条件(如 `'表'[列] = "值"`) - 动态筛选:用 `TREATAS` 或 `VALUES` 响应切片器[^4] - 去筛选:用 `ALL` 忽略特定列筛选[^4] --- ### 验证示例 | 场景 | DAX 公式片段 | |-----------------------|--------------------------------------------------| | 计算华东区销售占比 | `CALCULATE(SUM(Sales), Region="East") / CALCULATE(SUM(Sales), ALL(Region))` | | 响应切片器的产品比值 | `DIVIDE( CALCULATE(SUM(Qty), Product="A"), CALCULATE(SUM(Qty), Product="B") )` | > 提示:在报表中直接拖入该度量值,结果将随用户交互(如切片器、图表点击)动态更新[^2][^3]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

PowerBI | 夕枫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值