当前位置: 首页 > news >正文

6.15 PowerBI DAX函数精讲:从CONCATENATEX实战看值、列、表合并的艺术

1. 从“拼接”说起:为什么你需要CONCATENATEX?

做报表的时候,你是不是经常遇到这样的场景?老板指着你的PowerBI看板问:“这个区域明明卖了手机、电脑、平板好几种产品,你这里怎么只显示一个‘电子产品’?我想一眼看到具体是哪些。” 或者,你想在一个单元格里,把筛选后所有销售人员的名字用顿号隔开,形成一个清晰的列表。又或者,你需要把来自不同数据表的客户姓名和最近购买日期,合并成一句“张三(2023-10-01),李四(2023-09-15)”这样的备注信息。

这些需求,本质上都是“合并”。在Excel里,你可能会用&连接符,或者TEXTJOIN函数。但在PowerBI的DAX世界里,面对动态变化的数据模型和复杂的筛选上下文,简单的&往往力不从心。这时候,CONCATENATEX就该登场了。我刚开始用PowerBI时,也总觉得这个函数名字又长又怪,用起来参数也多,有点发怵。但后来在好几个实际项目里被它“拯救”之后,我才发现,这简直是处理动态文本合并的“瑞士军刀”。

简单来说,CONCATENATEX的核心就一句话:它能把一个表里某一列(或某个表达式计算出的值),按照你指定的顺序,用你指定的分隔符,串成一个文本字符串。听起来和TEXTJOIN有点像?最大的区别在于,CONCATENATEX的第一个参数是一个“表”。这个“表”可以是你的数据表,也可以是VALUESFILTERSSUMMARIZE等函数返回的一个表。正是因为这个“表”参数,让它能完美地响应报表上的切片器筛选、视觉对象筛选,实现真正动态的、上下文感知的文本合并。

举个例子,你的报表上有一个“大区”切片器。当你选择“华北区”时,CONCATENATEX可以自动合并华北区下所有城市的名字;当你选择“华东区”时,它又动态地变成华东区的城市列表。这种智能,是静态的&连接符完全做不到的。接下来,我们就从一个最常见的销售分析看板场景出发,看看CONCATENATEX如何优雅地解决“值合并”、“列合并”和“表合并”这三类头疼问题。

2. 基础实战:构建一个销售产品分类汇总看板

为了把概念讲清楚,我们设定一个具体的、你可能每天都会碰到的场景:构建一个销售产品分类汇总看板。假设我们有一张销售订单表order_2,里面包含这些字段:产品类别(如“电子产品”、“家具”)、产品子类别(如“手机”、“沙发”)、销售额销售日期等。

我们的看板需要几个关键的文本合并展示:

  1. 静态的列合并:创建一个新列,把“产品类别”和“产品子类别”固定地连接起来,比如“电子产品-手机”。
  2. 动态的值合并:在一个卡片图或表格里,动态地展示当前筛选条件下(比如某个特定月份)所有出现过的“产品子类别”,用分号隔开。
  3. 跨表的表合并:假设我们还有一张客户表,我们需要在销售汇总信息旁,合并显示相关的主要客户名称。

这个看板将是我们贯穿全文的“试验田”。我会一步步带你操作,并解释每一步背后的DAX逻辑。别担心参数复杂,我们先从最简单的“列合并”开始,就像热身一样。

2.1 列合并:创建静态的分类标签

“列合并”是最直观的一种。它的目的不是动态响应筛选,而是在数据建模阶段,就生成一个新的、固定的文本列。比如,你想把产品类别产品子类别合并成一个完整的分类标签,方便后续在图表中作为轴标签使用。

在PowerBI Desktop里,你可以在“表视图”中,通过“新建列”来实现。DAX公式非常简单,用的就是我们熟悉的&连接符:

产品分类标签 = 'order_2'[产品类别] & "-" & 'order_2'[产品子类别]

这行代码为order_2表的每一行都创建了一个新列。如果某行数据是“电子产品”类别下的“手机”子类,那么这一列的值就是“电子产品-手机”。这个值是静态的,一旦数据刷新计算完成,它就固定下来了,不会因为报表页面的切片器选择而变化。

什么时候用列合并?我个人的经验是,当你需要创建一个稳定的、用于分组或分类的维度时,就用它。比如,在做数据透视表或者作为图表轴标签时,这种合并后的字段比单独的两个字段更清晰。但它的局限性也很明显:不灵活。如果老板突然想看到“子类别-类别”的格式,你就得回去修改公式并刷新整个数据模型。

2.2 值合并:动态拼接当前可见的子类列表

这才是CONCATENATEX大显身手的地方,也是新手最容易感到困惑的地方。所谓“值合并”,是指根据当前报表的筛选上下文,动态地将一列中的“值”合并成一个字符串

回到我们的看板。我们想在报表上放一个卡片图,或者在一个表格的汇总行里,显示“当前销售涉及的产品子类别有哪些”。比如,当用户用切片器筛选了“2023年10月”的数据,这个卡片就应该动态显示10月份有销售的所有产品子类别,比如“手机;电脑;平板;沙发;椅子”。

这时候,你需要创建一个“度量值”,而不是“新建列”。因为度量值是动态计算的,会随着筛选上下文改变而改变。

当前产品子类列表 = CONCATENATEX( VALUES('order_2'[产品子类别]), // 第一部分:要遍历的表 'order_2'[产品子类别], // 第二部分:要合并的表达式 ";" // 第三部分:分隔符 )

我们来拆解这个公式:

  1. VALUES('order_2'[产品子类别]):这是CONCATENATEX的第一个参数,也是最关键的部分。VALUES函数会返回当前筛选上下文中,产品子类别这一列的所有“可见值”(去重后的列表)。也就是说,如果筛选了10月份的数据,VALUES返回的就是10月份销售记录里出现过的所有子类别的唯一值表。
  2. 'order_2'[产品子类别]:这是第二个参数,即对上面那个表中的每一行,要取什么值来合并。这里我们直接取子类别本身。
  3. ";":这是第三个参数,分隔符。你可以用逗号、顿号、换行符UNICHAR(10)等等。

把这个度量值拖入一个卡片图,你就会发现它的魔力了:切换不同的时间筛选器,卡片里的文本内容会实时变化。这就是“动态”的含义。VALUES函数在这里起到了“桥梁”作用,它把当前的筛选上下文转化成了一个CONCATENATEX可以处理的“表”。

踩坑提醒:这里有个初学者常犯的错误。第二个参数‘order_2‘[产品子类别],它必须能够在你第一个参数VALUES返回的那个“表”的上下文中被正确计算。因为VALUES返回的表,其行上下文就是产品子类别的每一个值。所以直接写列名是没问题的。但如果你在这里写一个复杂的、需要其他列参与的表达式,就要特别注意计算上下文了。

2.3 进阶:VALUES vs. FILTERS,一字之差天壤之别

上面我们用到了VALUES,但DAX里还有个兄弟函数叫FILTERS。在CONCATENATEX的第一个参数里,用VALUES还是FILTERS,结果可能完全不同。这是理解DAX筛选上下文的一个绝佳案例。

我画个简单的图帮你理解:

  • VALUES(列):返回的是当前筛选上下文中,该列实际存在于数据表里的值。它关注“数据里有什么”。
  • FILTERS(列):返回的是当前筛选上下文直接施加在该列上的筛选器值。它关注“切片器选了啥”。

听起来有点绕?我们举个实例。假设我们的产品子类别切片器里有“手机”、“电脑”、“平板”、“沙发”、“椅子”五个选项,但10月份的实际销售数据只包含“手机”、“电脑”和“沙发”。

  • 场景A:用户在切片器里什么都没选(即全选状态)。

    • VALUES('order_2'[产品子类别])返回:{"手机", "电脑", "沙发"}(因为10月数据只有这三个)。
    • FILTERS('order_2'[产品子类别])返回:{"手机", "电脑", "平板", "沙发", "椅子"}(因为切片器全选,所有选项都是生效的筛选器)。
    • 此时,用VALUES的度量值显示“手机;电脑;沙发”,而用FILTERS的会显示“手机;电脑;平板;沙发;椅子”。后者包含了未产生销售的数据“平板”和“椅子”。
  • 场景B:用户在切片器里只选择了“手机”和“平板”。

    • VALUES('order_2'[产品子类别])返回:{"手机"}(因为筛选后,数据里只有“手机”符合,“平板”没有销售记录)。
    • FILTERS('order_2'[产品子类别])返回:{"手机", "平板"}(切片器选了什么就返回什么)。
    • 此时,用VALUES的度量值只显示“手机”,而用FILTERS的显示“手机;平板”。

看出区别了吗?VALUES更贴近“事实”,而FILTERS更反映“意图”。在实际应用中:

  • 如果你想合并实际发生了业务的值(比如已销售的产品、有交易的客户),用VALUES
  • 如果你想合并用户当前主动筛选的值(比如切片器里勾选的项目,无论是否有数据),用FILTERS

理解了这个,你就能避免出现“为什么我选了三个,只合并出两个?”这类疑惑了。

3. 玩转参数:排序与复杂表达式合并

掌握了基础用法和VALUES/FILTERS的区别,你已经能解决80%的问题了。但CONCATENATEX的强大之处在于它的可定制性,特别是排序合并复杂表达式的能力。

3.1 让合并结果井然有序:排序参数详解

默认情况下,CONCATENATEX合并值的顺序是不确定的,通常取决于数据底层存储顺序。这对于展示来说很不友好。幸好,它提供了第四、第五个参数来指定排序。

语法是这样的:CONCATENATEX(表, 表达式, 分隔符, [排序依据表达式], [排序方式])

其中,排序方式可以是ASC(升序,默认)或DESC(降序)。排序依据表达式可以是你想合并的列本身,也可以是其他任何相关的列或度量值。

场景1:按产品子类别名称字母排序

有序产品子类列表 = CONCATENATEX( VALUES('order_2'[产品子类别]), 'order_2'[产品子类别], ";", 'order_2'[产品子类别], // 按子类别名字本身排序 ASC )

这样,输出就会是“电脑;沙发;手机”这样按拼音或字母顺序排列的列表。

场景2:按销售额从高到低排序,合并产品子类这个需求更常见也更有业务意义:把卖得最好的几个产品子类列出来。

按销售额排序的子类列表 = CONCATENATEX( SUMMARIZE( FILTER('order_2', [销售额] > 0), 'order_2'[产品子类别] ), // 先按子类别汇总 'order_2'[产品子类别], ";", [销售额], // 排序依据是当前上下文下的销售额度量值! DESC )

这里有几个关键点:

  1. 第一个参数不再是简单的VALUES,而是先用SUMMARIZEFILTER构建了一个包含产品子类别和对应销售额汇总的表。因为CONCATENATEX的排序依据需要是一个能在其迭代的每一行中计算的值。
  2. 第二个参数依然是合并的内容。
  3. 第四个参数[销售额]是一个度量值,它会在SUMMARIZE生成的每一行(每个子类别)的上下文中计算该子类别的总销售额,并以此作为排序依据。
  4. 第五个参数DESC表示降序,销售额最高的排前面。

这样,输出的字符串可能就是“手机;电脑;平板”,完美反映了销售贡献度排名。

3.2 超越简单列:合并自定义表达式

CONCATENATEX的第二个参数表达式非常灵活,它不限于直接引用列,可以是任何返回标量值的DAX表达式。这意味着你可以合并更丰富的信息。

场景:合并“产品子类别(销售额)”比如,你想合并成“手机(¥15,000);电脑(¥12,000);沙发(¥8,000)”这样的格式。

子类及销售额合并 = CONCATENATEX( FILTER( SUMMARIZE('order_2', 'order_2'[产品子类别]), [销售额] > 0), 'order_2'[产品子类别] & "(" & FORMAT([销售额], "¥#,##0") & ")", ";", [销售额], DESC )

这个公式做了几件事:

  1. 第一个参数,我们构建了一个包含有效销售额的子类别表。
  2. 第二个参数是一个表达式:它将产品子类别、格式化后的销售额度量值用括号连接起来。FORMAT函数用来将数字转换成带货币符号和千位分隔符的文本。
  3. 第四、五个参数指定按销售额降序排列。

这个度量值生成的结果,信息量就比单纯合并名字大得多,可以直接用在报表的标题或说明文字中,让读者一目了然。

4. 高阶应用:表合并与跨表信息拼接

“表合并”这个概念,在这里不是指合并查询(Merge),而是指基于更复杂的表关系,从多个相关表中提取信息,合并成一个字符串。这是CONCATENATEX更高级的用法,能实现一些非常酷的效果。

4.1 跨表合并:拼接客户与订单信息

假设我们除了order_2销售表,还有一张customer客户表,两者通过客户ID关联。现在,我们想在每个产品类别的汇总行,显示购买过该类产品的主要客户名单

这个需求无法通过简单的列合并或值合并实现,因为它涉及跨越两个表的逻辑。我们需要:

  1. 确定当前上下文(比如某个产品类别)。
  2. 找到这个类别下所有的销售记录。
  3. 通过这些销售记录对应的客户ID,去customer表找到客户姓名。
  4. 将这些客户姓名合并起来。
购买客户列表 = VAR CurrentCategory = SELECTEDVALUE('order_2'[产品类别]) // 获取当前上下文的产品类别 VAR RelatedCustomers = CALCULATETABLE( VALUES(customer[客户姓名]), // 获取相关的客户姓名 TREATAS( VALUES('order_2'[客户ID]), customer[客户ID] ) // 建立临时的关系筛选 ) RETURN IF( NOT ISBLANK(CurrentCategory), CONCATENATEX( RelatedCustomers, customer[客户姓名], ",", customer[客户姓名], ASC ), "(未选择类别)" )

这个公式稍微复杂一些,我们拆解一下:

  • VAR用于定义变量,让公式更清晰。
  • CurrentCategory变量获取当前单元格或视觉对象所在的产品类别。
  • RelatedCustomers变量是核心。它使用CALCULATETABLETREATAS函数,模拟了一个临时的关系:将当前order_2表中涉及到的客户ID,作为筛选器应用到customer表上,从而得到相关的客户姓名表。这是一种在度量值中动态建立表关系的常用技巧。
  • 最后,用CONCATENATEX合并RelatedCustomers表中的客户姓名,并按姓名升序排列。
  • IF判断用于处理未选择类别时的显示。

把这个度量值放在一个以产品类别为行的矩阵表中,你就能在每个类别旁边看到对应的客户列表了。

4.2 生成动态的摘要或注释文本

CONCATENATEX的另一个强大用途是生成动态的文本摘要,直接作为报表的标题、副标题或注释。比如,你可以创建一个度量值,用来动态生成当前报表的筛选状态描述:

报表筛选状态 = "当前报表数据范围:" & CONCATENATEX( VALUES('Date'[Year]), 'Date'[Year], "年、") & "年,产品类别包括:" & CONCATENATEX( VALUES('order_2'[产品类别]), 'order_2'[产品类别], "、") & "。"

这个度量值会随着你对年份和产品类别的筛选,自动更新文本内容。例如,当你筛选了2023年和2024年,以及“电子产品”和“家具”类别时,它会显示:“当前报表数据范围:2023年、2024年,产品类别包括:电子产品、家具。” 这对于制作交互式报告、增强报告的可读性和用户体验非常有帮助。

5. 避坑指南与性能优化

功能强大也意味着使用不当会带来问题。根据我多年的实战经验,这里有几个常见的“坑”和优化建议。

坑1:合并结果出现空白或重复的项这通常是因为第一个参数(表)中包含了空白行。在DAX中,如果关系缺失或计算返回空值,可能会产生空白行。你可以在CONCATENATEX外层使用FILTER来过滤掉空值:

CONCATENATEX( FILTER( VALUES('order_2'[产品子类别]), NOT ISBLANK('order_2'[产品子类别]) ), ... )

对于重复项,确保你使用的表函数(如VALUES,DISTINCT)本身是返回唯一值的。VALUES在关系完整时通常返回唯一值。

坑2:数据量巨大时性能变慢CONCATENATEX需要迭代第一个参数表中的每一行进行计算。如果这个表有上万行,且用在多个视觉对象中,可能会影响报表性能。

  • 优化建议1:尽量避免对非常大的表直接使用CONCATENATEX。先通过筛选上下文或SUMMARIZE等函数将数据缩减到必要的最小集合。
  • 优化建议2:考虑是否真的需要合并所有值。有时,合并前N个(例如销售额前5的产品)可能更有意义,这可以通过TOPN函数配合CONCATENATEX实现。
  • 优化建议3:将结果缓存为计算列(如果合并逻辑相对静态)。但这牺牲了动态性,需权衡。

坑3:在迭代函数中使用不当CONCATENATEX本身是一个迭代器。如果你把它放在另一个迭代函数(如SUMX,FILTER内部)中,可能会造成嵌套迭代,导致性能急剧下降。在设计度量值时,要思考清楚计算逻辑的层次。

一个实用的调试技巧:当你写的CONCATENATEX公式没有返回预期结果时,可以分步调试。先单独创建一个度量值,只返回第一个参数的表(比如= COUNTROWS(VALUES(...))),看看这个表里是不是你期望的行。再检查第二个表达式在每行里计算是否正确。逐层排查,能快速定位问题所在。

6. 融会贯通:一个综合案例

让我们把所有知识串起来,解决一个更复杂的真实需求。假设老板想要一个销售仪表板,其中有一个关键指标卡,需要显示以下信息: “本期核心销售贡献来自[按销售额降序排列的前3个子类别],其销售额占比为XX%。主要购买客户包括:[相关客户列表]。”

这个需求包含了动态筛选、排序、取前N项、跨表合并和文本格式化。我们可以创建如下度量值:

核心销售摘要 = VAR TotalSales = [销售额] // 定义总销售额度量值 VAR Top3Subcategories = TOPN( 3, SUMMARIZE( FILTER(ALLSELECTED('order_2'), [销售额] > 0), 'order_2'[产品子类别] ), [销售额], DESC ) // 获取销售额前三的子类别表 VAR SalesFromTop3 = CALCULATE( [销售额], KEEPFILTERS(Top3Subcategories) ) // 计算前三子类别的销售额 VAR Top3Names = CONCATENATEX( Top3Subcategories, 'order_2'[产品子类别], "、", [销售额], DESC ) // 合并前三名称 VAR Percentage = DIVIDE(SalesFromTop3, TotalSales, 0) // 计算占比 VAR RelatedCustomers = CALCULATETABLE( DISTINCT(customer[客户姓名]), TREATAS( VALUES('order_2'[客户ID]), customer[客户ID] ), KEEPFILTERS(Top3Subcategories) // 只关联前三子类别的客户 ) VAR CustomerList = IF( NOT ISEMPTY(RelatedCustomers), CONCATENATEX( TOPN(5, RelatedCustomers, [销售额], DESC), customer[客户姓名], "、"), "(暂无客户数据)" ) // 合并销售额前5的主要客户 RETURN "本期核心销售贡献来自 " & Top3Names & ",其销售额占比为 " & FORMAT(Percentage, "0.0%") & "。主要购买客户包括:" & CustomerList & "。"

这个度量值虽然长,但结构清晰,每一步都用VAR变量拆解。它综合运用了TOPNSUMMARIZECALCULATETREATAS以及CONCATENATEX。把它放入一个卡片图,它就能根据全局筛选器(如时间、区域),动态生成一段完整的、数据驱动的业务描述。这种动态文本生成能力,能将你的报表从简单的图表展示,提升到智能业务叙述的层次。

从我自己的项目经验来看,CONCATENATEX这类文本函数用得好,能极大提升报表的自动化水平和可读性。它让报表不再是冷冰冰的数字和图形,而是能“说话”、能“解释”数据的智能文档。刚开始接触时多写几个例子,遇到报错时耐心拆解每个参数返回的表是什么,慢慢你就会发现,处理复杂的文本合并需求时,思路会变得非常清晰。

http://www.cnnetsun.cn/news/1253355.html

相关文章:

  • 基于CH334R的USB 2.0四端口有源集线器设计
  • cv_resnet101_face-detection_cvpr22papermogface 跨平台部署实践:从Windows到Linux的迁移指南
  • GD32VW553驱动夏普GP2Y0A02YK0F红外测距传感器:ADC采集与非线性校准实战
  • HeyGem数字人视频生成系统:提供单个和批量两种模式,满足不同需求
  • ESP32定时器中断实战:从零到一构建精准时间触发器
  • 【ICCV2023】Scale-Aware Modulation与Transformer的融合:多尺度视觉任务的新突破
  • ZadigUSB驱动神器 v2.8:一键解决Windows设备识别难题
  • 利用VS2017与Qt开发安捷伦信号源自动化控制工具
  • WarcraftHelper:革新性魔兽争霸III增强工具全攻略
  • 从零到一:在Windows上手动部署PySide2开发环境
  • yz-女生-角色扮演-造相Z-Turbo与Python爬虫结合:自动化角色数据采集实战
  • LiuJuan20260223Zimage部署教程:Docker Compose一键编排Xinference+Gradio+Redis缓存
  • UV贴图与展开:3D建模新手的必备技能解析
  • 比迪丽LoRA效果对比:不同LoRA权重(0.6/0.8/1.0)对还原度影响
  • 用快马平台快速生成高级动态爱心代码原型,验证你的图形创意
  • OFA模型在工业质检中的实战应用:缺陷识别与原因分析
  • 瀚高数据库自动化部署与定时备份实战(脚本化解决方案)
  • 超级千问语音设计世界:魔法威力与跳跃精准,两个滑块调出好声音
  • AIGC工作流整合:使用cv_unet_image-colorization为文生图结果进行风格化着色
  • 专科生收藏!千笔,抢手爆款的AI论文写作软件
  • 构建企业级知识库问答:基于InternLM2-Chat-1.8B与向量数据库
  • 【IDE实战】PyCharm与VSCode双环境配置Arcpy:从零到一打通GIS开发链路
  • Spring Boot + Vue 全栈应用云端部署实战:从零到一上云指南
  • GTE-Chinese-Large一文详解:中文词粒度与短语语义在向量空间的分布特征
  • 企业级Dify Rerank架构设计(含可观测性埋点规范):覆盖Embedding对齐、Query改写、Score归一化全链路的8层校验机制
  • Unity资产处理全流程解析:从环境搭建到高级应用
  • Qwen2.5-7B-Instruct快速上手:基于vllm部署,chainlit可视化界面调用
  • EVA-01作品分享:基于Qwen2.5-VL的视觉神经同步系统效果展示
  • MT5中文改写工具效果实测:对抗样本生成能力与鲁棒性压力测试
  • STM32U5 Stop与Standby低功耗模式深度解析与工程实践