03.09 理解循环依赖
在设计数据模型时,有一个涉及复杂内容的主题需要注意,即公式中的循环依赖关系。在本节中,你将学习什么是循环依赖以及如何在模型中避开循环依赖。
线性依赖
在讨论循环依赖之前,有必要先讨论简单的线性依赖关系。让我们看一个示例,其中包含以下计算列: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列可以唯一区分产品表。
- 你可以使用表行为属性设置将ProductKey列设为行标识符。 以上任一操作都会使得DAX知道该表中存在行标识符,避免了在定义NewSumOfListPrice列的同时遇到循环依赖,因为使用了CALCULATE的两个计算列都只依赖于新设置的主键列。除设置日期表外,目前Power BI Desktop暂时不支持表行为属性设置。另外,虽然设置表行为属性是个不错的主意,但并不意味着你需要向所有表都添加一个行标识符,事实上如果这么做,那些未被使用的列会浪费掉宝贵的内存。如果该列已在数据库中且需要用于计算时,再设置行标识符属性,否则跳过此步骤。游客liuFollow this user#652”检查产品表中对于Product key、Product Name、Unit Cost和Unit Price具有相同值的所有行,将它们的Unit Price值相加“,这一点似乎有误,存在两行一模一样的数据时,所在行并没有汇总全部,还是等所在行的值。赞0踩 回复8 天 之前作者高飞Follow this user#654截图中不存在一摸一样的数据,你是指用自己的数据模拟的情况吗赞0踩 回复8 天 之前游客liuFollow this user#657是的,使用的测试数据一列是类别(a.b.c.a)一列是数值1234,a列的汇总不会等于5的赞0踩 回复8 天 之前作者高飞Follow this user#660你模拟的数据里没有完全相同的两行,如果定义计算列公式=calculate(sum(数值列)) ,不等于5是正常的赞0踩 回复8 天 之前游客liuFollow this user#665把第二个a得值4改成1这样两行应该就一样了,但结果还是等于1,不会等于2 赞0踩 回复7 天 之前作者高飞Follow this user#666供你参考赞0踩 回复7 天 之前