本文翻译自Marco Russo & Alberto Ferrari的文章—《Choosing between DISTINCT and VALUES in DAX》 来源:SQLBI 何时应在 DAX 中选择 DISTINCT 而非 VALUES?本文将介绍如何编写健壮的度量值,使其在数据质量不佳和模型变更时仍能保持正确性。
初学 DAX 建模时,DISTINCT 和 VALUES 函数常看似可互换:两者都会返回当前筛选上下文中列的唯一值列表。在整洁的开发模型中,它们的行为完全一致,因此很容易随意选择其一——更糟糕的是,不经思考地混用两者。
然而这两个函数并非完全相同。细微的差异在可能包含无效关系或脏数据的生产模型中至关重要。本文将详解:
每个函数的技术行为
为何一个额外的"空白行"会造成结果差异
明确的使用场景选择原则
特殊情况:表迭代、多列操作与统计度量
通过本文您将理解:为何 VALUES 应作为默认选择,以及何时需改用 DISTINCT。文中将包含非优化度量值示例,以展示使用 VALUES 或 DISTINCT 导致结果差异的简单场景。需要注意的是,这些差异同样可能出现在优化代码中,但实际用例往往过于复杂难以在简单文章中演示。因此,请将这些示例仅视为教学案例,而非编写特定计算的最佳实践。
DISTINCT(<ColumnNameOrTableExpr>)VALUES(<TableNameOrColumnName>)
从函数签名可见,VALUES 还可接受表引用,而 DISTINCT 能接收更通用的表表达式。在本文起始部分,我们重点讨论以列引用作为参数时这两个函数的行为。
二者的行为看似相同,因为它们都返回当前筛选上下文中可见的列唯一值列表。唯一区别在于:当同时满足以下所有条件时,VALUES 可能会多返回一个空白行:
该列属于常规关系中的"一"端表
该常规关系处于无效状态
筛选上下文未过滤掉无效关系产生的附加空白行
列中的空白值未被筛选上下文过滤排除
需要特别说明的是:如果从一对多关系中"一"端的列获取值,则该列不允许出现空白值——这是用于标识无效关系中特殊空白行的保留值。
常规关系指一对多(或一对一)关联,其中"一"端列是主键。当"多"端值在"一"端不存在时,关系将失效,DAX 引擎会自动向"一"端表添加空白行。所有"多"端未匹配行都会关联到该空白行。而在受限关系(多对多、双向关系等)中永远不会添加该行,因此 DISTINCT 和 VALUES 的行为完全一致。
Sales Adjusted (incorrect)=SUMX(DISTINCT(Customer[Continent]),[]*IF(Customer[Continent]=="Europe",.99,1))
错误金额在按客户[国家]字段切片显示的矩阵中很容易解释,因为两个总计之间的差异可直接归因于初始的空白国家字段。然而,当使用客户表之外的其他表列(如按产品[品牌]字段切片矩阵)进行度量值切片时,理解这种差异会变得异常复杂。在后一种情况下,由于每行都包含未在报告中直接"显现"的未知客户,两套度量值的所有行都会呈现不同金额。
以下是正确计算销售额调整值的定义:
Sales Adjusted =SUMX(VALUES(Customer[Continent]),[]*IF(Customer[Continent]=="Europe",.99,1))
在某些计算中,空白行会导致误导性结果。典型案例如 MINX、MAXX、AVERAGEX 等统计迭代器及百分位数计算——若将所有未知客户归入同一个庞大的"未知"客户类别,会造成统计偏差。此时完全跳过这些值可能是更佳选择。这类情况通常属于例外而非通则,因此我们建议默认使用 VALUES 函数,除非能明确解释公式中使用 DISTINCT 的合理性。
例如,以下是对客户[城市]平均销售额的错误计算:
City Average (incorrect)=AVERAGEX(VALUES(Customer[City]),[])
同样地,当通过客户属性(如客户[国家])对数据进行切片时,矩阵会显示出一个对应所有"未知"客户聚合值的空白行。这会导致矩阵总计值出现偏大的错误金额,而其他报表(如按产品[品牌]切片度量值的矩阵)则会在每个单元格中都出现错误金额。
City Average =AVERAGEX(DISTINCT(Customer[City]),[])
END
长按下方二维码关注“Power Pivot工坊”获取更多微软Power BI、PowerPivot相关文章、资讯,欢迎小伙伴儿们转发分享~


