你有没有遇到过这种情况:Power BI 报表里一个简单的去重计数,数据量一大就卡成 PPT?
明明只是数一下有多少笔交易、多少个客户,结果刷新要等半天,点个筛选还要转圈圈。
如果你正在为 DISTINCTCOUNT 的性能头疼,这篇文章能帮你省下不少时间和算力。
为什么 DISTINCTCOUNT 这么慢?
在零售模型里,数交易笔数是最常见的需求:三月份卖了多少单?新西兰有多少笔?其中多少单买了 T 恤?
大多数人第一反应就是写:
Transactions = DISTINCTCOUNT ( FactTransaction[TransactionID] )
结果正确,但在大事实表上,这是你能让存储引擎做的最昂贵操作之一。
DISTINCTCOUNT 需要扫描所有行,然后去重,这个过程极其消耗资源。
一个被低估的替代方案
今天要分享的建模技巧,能让你用 COUNTROWS 回答大部分这类问题,并且显著减少真正需要去重时的计算量。
它不会完全替代 DISTINCTCOUNT,但能让你在不需要的时候不为其买单,在需要的时候给它更少的数据去处理。
这个思路不仅适用于零售,保险理赔、制造工单、医疗就诊、物流托运、客服通话……只要你想数某个属于"自然父级"的实例数量,都适用。
动手前先做 30 秒检查
在开始建模之前,先拿到两个数字:事实表的行数,以及你要去重的那一列的基数。
用 SQL 查:
SELECT
COUNT(*) AS FactRows,
COUNT(DISTINCT TransactionID) AS DistinctTransactions
FROM dbo.FactTransaction;
或者在 DAX 里直接算:
EVALUATE
ROW (
"FactRows", COUNTROWS ( FactTransaction ),
"DistinctTransactions", DISTINCTCOUNT ( FactTransaction[TransactionID] )
)
这两个数字的比值,决定了这个技巧能帮你多少。
核心思路:行数与去重值的差值
设行数为 R,去重值为 D,那么多余的行数 E = R - D。
每笔交易如果有超过一行(比如明细行),就说明存在重复。而这个 E 值,就是你可以用 COUNTROWS 直接"免费"拿到的部分。
具体怎么做?
假设你的事实表 FactTransaction 中,每个 TransactionID 对应多行(比如一行一个商品明细)。
- 先建一个交易维度表,只包含唯一的 TransactionID
- 在事实表与维度表之间建立一对多关系(维度表一端是"一")
- 计数度量直接用 COUNTROWS(TransactionDimension)
这样,当你按时间、地区、产品筛选时,只要筛选条件能传递到维度表,COUNTROWS 就能正确返回交易笔数,而无需去重。
只有当筛选条件直接作用在事实表明细行上(比如某个商品的子类),才需要回到 DISTINCTCOUNT。
效果立竿见影
- 大部分常规交易计数:COUNTROWS 直接搞定,性能提升数倍
- 少数需要真实去重的场景:由于维度表已经大大减少了行数,DISTINCTCOUNT 也快得多
- 存储引擎的压力明显下降,报表响应更流畅
国内用户怎么看
这个技巧在电商、新零售场景特别适用——订单表动辄几千万行,每个订单多行明细,用 COUNTROWS 替代 DISTINCTCOUNT 后,报表刷新速度和交互体验都能明显改善。
而且这个思路对 Vertipaq 引擎友好,不需要额外配置,纯建模技巧就能见效。
什么时候不该用?
- 当你的事实表本身每行就是唯一实例(比如流水号),没有自然父级时
- 当筛选条件总是直接作用于明细行,无法通过维度表传递时
- 当数据模型已经很复杂,增加维度表可能让关系链变长时
总结
DISTINCTCOUNT 不是敌人,但你需要学会在合适的场景用 COUNTROWS 来绕开它。
先算算你的 R 和 D,看看差值有多大,然后设计一个交易维度表,你会发现 Power BI 的性能还有不少潜力可挖。
如果你也想优化自己的模型,不妨从这两个数字开始。





