从数据到信息
从信息到洞察

理解循环依赖

在设计数据模型时,有一个涉及复杂内容的主题需要注意,即公式中的循环依赖关系。在本节中,你将学习什么是循环依赖以及如何在模型中避开循环依赖。

线性依赖

在讨论循环依赖之前,有必要先讨论简单的线性依赖关系。让我们看一个示例,其中包含以下计算列

Product[Profit] = Product[Unit Price] - Product[Unit Cost]

新的计算列依赖于同一表的另外两列。在这种情况下,我们说利润列取决于单位价格和单位成本。然后,你可以用以下公式创建一个名为 ProfitPct 的新列:

Product[ProfitPct] = Product[Profit] / Product[Unit Price]

很明显, ProfitPct 的结果取决于利润和单价。因此,当 DAX 计算这两列时,它知道只有在计算利润之后才能计算 ProfitPct。否则就无法计算出 ProfitPct 公式的有效值。

线性依赖通常不需要担心,DAX 会在数据模型刷新期间检测到计算列之间的正确计值顺序。在一个有许多计算列的普通数据模型中,列之间的依赖性形成了一个复杂的关系图,但引擎可以很好地处理这个问题。

当这个关系图中出现循环引用时就会发生循环依赖。例如,如果你试图按以下方式修改 Profit 公式,循环依赖就会发生:

Product[Profit] = Product[ProfitPct] * Product[Unit Price]

因为 ProfitPct 依赖于 Profit,,而在这个新公式中,Profit 依赖于 ProfitPct, DAX 拒绝修改公式, 并显示错误 “检测到循环依赖关系”。

到目前为止,你已经从公式的角度了解了什么是循环依赖;也就是说,不需要注意表中的数据,你在查看表达式时已经发现了依赖项的存在。不过,通过 CALCULATE 还可以产生一种更微妙、更复杂的依赖关系。让我们从产品表的子集开始,用一个示例来展示这个场景,请注意在本例中我们只加载了产品表,从模型中删除了所有其他表,以便使场景更加明显。

产品表的这个子集对于理解循环依赖关系很有用

我们的兴趣在于了解使用了 CALCULATE 函数的新计算列的依赖关系列表,如下所示:

Product[SumOfUnitPrice] = CALCULATE ( SUM ( Product[Unit Price] ) )

乍一看,这一列似乎只取决于单价,因为这是公式中使用的唯一一列。不过,请注意我们使用了 CALCULATE 将当前行上下文转换筛选上下文。因为我们没有定义与其他表的关系,也没有为它设置主键,所以当 CALCULATE 进行上下文转换时,它会筛选表的所有列。如果我们扩展 CALCULATE 调用的含义,公式实际上说的是:

检查产品表中对于 Product key、Product Name、Unit Cost 和 Unit Price 具有相同值的所有行,将它们的 Unit Price 值相加。

如果你以这种方式阅读公式,可以清楚地发现,代码依赖于产品表的所有列,因为新引入的筛选上下文将筛选表的所有列。你可以在图中看到结果。

在这里可以看到带有 SumOfUnitPrice 计算列的产品表

你可以尝试使用相同的公式在同一个表中定义一个新的计算列。比如使用以下公式定义 NewSumOfUnitPrice,这个公式与前一个公式相同。

Product[NewSumOfUnitPrice] = CALCULATE ( SUM ( Product[Unit Price] ) )

观察循环依赖现象

令人惊讶的是, 此时 DAX 提示了一个错误, 称它检测到循环依赖关系。这很奇怪, 因为它在相同的的公式中检测到了以前没有发现的循环依赖关系。这其中的原因在于表的列数发生了变化。假设我们能够将 NewSumOfUnitPrice 添加到表中, 这两个公式将分别具有以下含义:

  • SumOfListPrice 对产品表中的以下 5 列有相同值的所有行的单价求和,这 5 列分别为 ProductKey, Product Name, Unit Cost, Unit Price 和 NewSumOfListPrice。
  • NewSumOfListPrice 对产品表中的以下 5 列有相同值的所有行的单价求和,这 5 列分别为 ProductKey, Product Name, Unit Cost, Unit Price 和 SumOfListPrice。

任何添加到数据模型中的计算列都成为由 CALCULATE 引入的筛选上下文的一部分,因此所有计算列都成为依赖列表的一部分。阅读上述定义,两个公式之间存在着明确地循环依赖,这恰恰是 DAX 拒绝创建 NewSumOfListPrice 列的原因。

解决方案

理解该错误并不容易,但是找到解决方案很简单,即使这个方案看上去不是很直观:

如果表没有主键,任何包含 CALCULATE(或对任何度量值的调用,这些调用都自动添加一个 CALCULATE)的计算列都会创建对表的所有列(也包括计算列)的依赖关系。如果表中有一个行标识符(用数据库术语来说是主键),情况将会有所不同。当表中含有这个作为行标识符的列时,包含 CALCULATE 的所有列仅依赖于该行标识符,从而将依赖列表的数目降低到单列。

在产品表中,有一列可以唯一地标识出每一行,即 ProductKey。要将 ProductKey 标记为行标识符(主键),你有三种选择:

  • 使用 ProductKey 列创建事实表和产品表之间的关系。执行此操作将确保 ProductKey 列可以唯一区分产品表。
  • 在 Excel PowerPivot 中,你可以使用表行为属性设置将 ProductKey 列设为行标识符。
  • 在 Power BI Desktop 中,你可以在模型视图 – 属性 – 常规 – 键列 中为当前表设置包含唯一值的列。

以上任一操作都会使得 DAX 知道该表中存在行标识符,避免了在定义 NewSumOfListPrice 列的同时遇到循环依赖,因为使用了 CALCULATE 的两个计算列都只依赖于新设置的主键列。

虽然设置表行为属性是个不错的主意,但并不意味着你需要向所有表都添加一个行标识符,如果这么做,那些未被使用的列会占用内存。建议当该列已在数据库中且需要用于计算时,再设置行标识符属性,否则跳过此步骤。

例外情况

通过上面的介绍,我们已经知道 DAX 中的循环依赖绝大部分都是由于 CALCULATE 在执行上下文转换时产生的,但上下文转换并非发生始终发生在所有情境中,在某些特殊情况下,DAX 引擎会避免发生这种转换。观察下面这个案例,c1 列是 Table 表的原始列,cc1 和 cc2 是计算列,这里并没有提示循环依赖。

按照我们之前的理解,上下文转换会使得 cc1 和 cc2 互相依赖,而实际上, 

本文隐藏内容查看价格为2G币,请先
单独购买的内容长期有效,不受时间限制(购买前先刷新当前页面)。加入VIP会员可享受全站权益,性价比更高。

 

44
说点什么

1000
 
鼓掌微笑开心憧憬爱你色并不觉得吃瓜doge二哈喵喵思考笑哭捂脸悲伤大哭抓狂汗偷笑打脸捂眼黑线问号晕拜拜闭嘴衰咒骂ok作揖
17 评论数
31 被回复的评论
19 订阅评论的人数
 
查看最近回复
查看最热评论
  订阅本文评论  
最新 最旧 得票最多
提醒
成员
状态的状

‘商品表'[商品编码] 已经被设置为主键了,并且还是一对多中的一端。如果按CALCULATE使用主键作为筛选上下文,请问老师ALL仅忽略主键为什么做不到忽略筛选上下文呢?

1712148924428.jpg
成员
wuqixin

可以使用 ProductKey 作为目标列,来创建任意表和产品表之间的关系。执行此操作将确保 ProductKey 列可以唯一区分产品表
这句话的意思是ProductKey 作为一端或多端都可以吗?

成员
839838408

老师,还可以在Model-view-选中产品表-properties-Key column=ProductKey。并且不需要和其他表建立连线。

Capture.PNG
成员
优雅野人

这个我上个提问的图片

关于循环依赖.png
成员
优雅野人

高老师,本文最前面3个计算列,一直到第三个:Product[Profit] = Product[ProfitPct] * Product[Unit Price],我在PBI(2022年4月版)中并没有提示循环依赖。莫非只存在于excel中?如图:https://www.powerbigeek.com/understanding-circular-dependency/#comment-7666

游客
芒果

“可以使用 ProductKey 作为目标列,来创建任意表和产品表之间的关系。执行此操作将确保 ProductKey 列可以唯一区分产品表。”
这句话不理解。这句话也没有说明白怎么将productkey作为目标列的。方法是啥?怎么操作啊?啥叫目标列啊?概念不清啊。

成员
Summer1

老师,没找到SumOfListPrice和NewSumOfListPrice的公式呢?

成员
Singham

老师可否把案列文档加进来,以便阅读着边理解边练习!

游客
王先生

请教一个度量值循环引用问题:
1、本月初金额=上月末金额
2、本月平均单价=(月初金额+本月入库金额)/(月初数量+本月入库数量)
3、本月出库金额=出库数量*本月平均单价
4、本月末金额=(月初金额+本月入库金额-本月出库金额
5、下月初金额=本月末金额

按此逻辑写出的度量值存在循环引用,请指导怎么样解决?

成员
CatCatLa

你好老师,我做了如下实验,下表为table2,没有任何关联表,C1和C2是物理列,其余是计算列

col1 = CALCULATE(VALUES(table2[C1]))
col2 = CALCULATE(VALUES(table2[C1]))
col3 = table2[col1]

col_a = CALCULATE(SUM(table2[C3]))
col_b = CALCULATE(SUM(table2[C3]))
col_c = table2[col_a]

实验结果是col_b, col_c, col_a形成循环引用报错,而col1, col2, col3均正常加载,为什么同样是在计算列中使用Calculate,但结果会有不同呢?

3.png
成员
sankingg

循环依赖说白了,就是excel里面的循环引用。
1、excel中出现循环引用会报错,修改迭代次数可以让公式成功运行。
2、PBI中必须避免循环依赖,否则出错。
避免循环依赖的方法就是要注意calculate函数的使用。
calculate的作用是把行上下文转化为筛选上下文。
行上下文实际上只引用了一列,
筛选上下文表面上看,好像是只引用的一列,但实际上会牵涉到整个表的所有列(问题,会不会牵涉到整个模型的所有列,请老师解答)。因此就会出现循环依赖。
这当中还有特例,就是看你引用的列是不是主键。如果是主键就不会出现循环依赖,因为主键的筛选上下文不会牵涉表的其他所有列(这样理解对吗:主键的特性就是唯一,它天生就牵涉到所有列了,所以在这里使用calculate引用主键,不会再次去引用其他列或是依赖其他列)。

dax太难理解了,所以我把自己的理解写出来,请老师指正。同时给老师提点建议,请老师在dax圣经中多一些“费曼精神”
————————————————
费曼学习法可以简化为四个单词:Concept (概念)、Teach (教给别人)、Review (回顾)、Simplify (简化)。
第一步:把它教给一个小孩子。
拿出一张白纸,在上方写下你想要学习的主题。想一下,如果你要把它教给一个孩子,你会讲哪些,并写下来。这里你的教授对象不是你自己那些聪明的成年朋友,而是一个8岁的孩子,他的词汇量和注意力刚好能够理解基本概念和关系。
许多人会倾向于使用复杂的词汇和行话来掩盖他们不明白的东西。问题是我们只在糊弄自己,因为我们不知道自己也不明白。另外,使用行话会隐藏周围人对我们的误解。
当你自始至终都用孩子可以理解的简单的语言写出一个想法(提示:只用最常见的单词),那么你便迫使自己在更深层次上理解了该概念,并简化了观点之间的关系和联系。如果你努力,就会清楚地知道自己在哪里还有不明白的地方。这种紧张状态很好——预示着学习的机会到来了
———————————————————-
以上是百度百科,就不多说了,可自行搜索。
在这里想说的是,请老师时刻牢记,我们这些读者都只是8岁的小孩子。。。 @高飞

成员
139****9015

如果我的表里面是要A列+B列+C列才是唯一索引列,那我就需要用&新建这列出来,是吗?

成员
158****9583

循环依赖的定义和一些坑坑基本都没讲。。。

成员
linadi

高老师好,最后提示里面的“那些未被使用的列会浪费掉宝贵的内存”是什么意思吖?

游客
liu

”检查产品表中对于Product key、Product Name、Unit Cost和Unit Price具有相同值的所有行,将它们的Unit Price值相加“,这一点似乎有误,存在两行一模一样的数据时,所在行并没有汇总全部,还是等所在行的值。

DAX 圣经

导读

初识 DAX

DAX 基础知识

DAX 原理

DAX 高级原理

基础函数类型

迭代函数

CALCULATE 函数

CALCULATE 调节器

基础表函数

条件判断函数

查找匹配函数

时间智能函数

统计类函数

投影函数

分组/连接函数

集合函数

其他函数