Metabase Concat 表达式完全指南:列拼接、类型转换与 SQL/Spreadsheet/Python 等价实现
【免费下载链接】metabaseThe easy-to-use open source Business Intelligence and Embedded Analytics tool that lets everyone work with data :bar_chart:项目地址: https://gitcode.com/GitHub_Trending/me/metabase
导读
concat是 Metabase 自定义表达式(Custom Expressions)中最常用的字符串函数之一,用于将两个及以上列或值拼接为一个字符串,典型场景包括合并"城市 + 国家"生成完整地址、拼接订单号前缀、组合姓名与称谓等。本文以官方文档docs/questions/query-builder/expressions/concat.md为核心骨架,结合 Metabase 开源仓库中 MBQL(Metabase BI Query Language)的 schema 定义、查询转换器与 SQL 驱动实现,完整讲解concat的语法、参数规则、非字符串列的自动类型转换行为、可接受数据类型,以及它与 SQLCONCAT、ExcelCONCATENATE、Python 字符串相加的等价写法。读完本文,你将能在 Metabase 查询构建器中熟练使用concat生成自定义列,并理解其底层执行机制。
什么是 concat
concat(拼接)将两个或多个列(字段)或值按顺序连接在一起,并返回一个字符串。它是查询构建器表达式编辑器中"字符串函数"一族的代表成员,与substring、regexextract、replace等函数一起,在metabase.lib.schema.expression.string中被统一定义。
从 MBQL 的 schema 定义看,concat是一个"catn"(concat-and)类型的子句,其返回类型被固定为:type/Text,参数由[:args [:repeat {:min 2} [:schema [:ref ::expression/expression]]]]约束——即至少需要两个参数,每个参数可以是任意表达式(列引用、字面量或嵌套函数):
(mbql-clause/define-catn-mbql-clause :concat :- :type/Text [:args [:repeat {:min 2} [:schema [:ref ::expression/expression]]]])该定义位于 src/metabase/lib/schema/expression/string.cljc,是前端表达式校验与后端 MBQL 解析共享的唯一事实来源(.cljc双平台文件)。这意味着无论你在查询构建器中书写还是通过 API 提交 MBQL,concat的参数数量与类型都会先经过这层 schema 校验。
语法
concat(value1, value2, ...)value1、value2… 可以是列引用或字面值;- Metabase 会在拼接前将非字符串类型的列自动转换为字符串;
- 表达式的结果始终是字符串,无论传入值的类型如何;
- 参数之间按书写顺序依次连接,不自动插入任何分隔符——需要分隔符(如逗号、空格)时,必须作为独立的字符串参数显式写出。
列引用使用方括号语法,例如[City]、[Country];字符串字面量使用双引号,例如" is in "。关于列引用、连接表列[ConnectedTableName.Column]以及引用已保存的指标/分段的通用规则,可参阅 自定义表达式总览。
示例
| 表达式 | 结果 |
|---|---|
concat("Vienna", "Austria") | "ViennaAustria" |
concat("Vienna", " is in " ,"Austria") | "Vienna is in Austria" |
concat([City], " is in " ,[Country]) | "Vienna is in Austria" |
第一行演示了纯字面量拼接(注意没有自动加空格);第二行演示了用字面量插入分隔文本;第三行演示了实战中最常见的形态——拼接两个列并在中间插入分隔符。
非字符串列:Metabase 使用未格式化(raw)值
这是使用concat时最容易踩的坑:当你在concat中使用非字符串列时,Metabase会忽略你在列上配置的任何格式化(Formatting)设置,直接使用底层数据库返回的原始值进行转换拼接。
例如,你在表格结果中把一个数字列格式化为只显示两位小数,concat的结果仍会包含原始数据里(如果有的话)更多的小数位。
| 格式化后的显示 | 原始值 | concat("Result:", " ", [Value]) |
|---|---|---|
Kitten | Kitten | Result: Kitten |
17 | 17 | Result: 17 |
31.25 | 31.24823945 | Result: 31.24823945 |
42% | 0.42 | Result: 0.42 |
January 1, 2024 | 2025-02-11 21:40:27.892 | Result: 31.24823945 |
要点:
- 数字
31.25的格式化显示与concat结果不一致,因为拼接使用的是31.24823945这个原始值; - 百分比
42%在拼接结果中变成0.42(原始的比值小数); - 日期列也是如此——即使显示为
January 1, 2024,拼接输出的是完整时间戳2025-02-11 21:40:27.892。
因此,若你需要"格式化后"的文本效果(如保留两位小数、百分比符号、特定日期格式),应在拼接前先用format相关的字符串函数或对原始值做预处理,而不是依赖列的展示格式化。列格式化本身的管理方式见 数据建模 - 格式化。
可接受的数据类型
| 数据类型 | 是否可用于concat |
|---|---|
| String | ✅ |
| Number | ✅ |
| Timestamp | ✅ |
| Boolean | ✅ |
| JSON | ✅ |
所有非字符串类型都会被转换为字符串;无论传给concat的值是什么类型,结果始终是字符串。这一点与 schema 定义中返回类型固定为:type/Text完全一致:类型系统在 MBQL 层面就保证了concat的输出一定是文本。
从查询转换链路看,这一"结果必为字符串"的约束还体现在 src/metabase/lib/convert.cljc——该文件将:concat与:substring、:replace、:regex-match-first、:split-part、:collate等字符串算子归为一类进行统一转换处理,确保这些表达式在进入 SQL 编译阶段前保持一致的语义。
在查询构建器中使用 concat
concat最常见的用途是在**自定义列(Custom Column)**中创建新的文本字段:
- 在查询构建器中点击自定义列(Custom Column);
- 在表达式编辑器中输入,例如
concat([City], ", ", [Country]); - 为新列命名(如
Location),点击Done完成。
concat也可以作为**过滤器(Filter)或汇总(Summarize)**表达式的一部分(虽然过滤器场景通常配合contains等谓词使用)。关于表达式编辑器的完整操作方式(函数浏览器、自动格式化、聚合与函数的区别等),参见 自定义表达式文档。
快捷操作:合并列(Combine Columns)
值得一提的是,Metabase 还为字符串列提供了Combine Columns(合并列)的快捷操作:点击列头时,如果当前列是字符串列,会触发:drill-thru/combine-columns动作,底层自动生成一个concat表达式,把点击的列与一个或多个"分隔符 + 列"对拼接起来。该功能在 src/metabase/lib/drill_thru/combine_columns.cljc 中实现:
- 触发条件:列存在、
value为空(非聚合场景)、当前 stage 是 MBQL stage、且该列的语义类型为字符串; - 效果:等价于手动写出
concat([Column], [separator], [nextColumn], ...)的自定义表达式。
这意味着大多数"合并两列"的需求你甚至不需要手写表达式,直接点击列头选择 Combine Columns 即可,生成的结果就是一个concat表达式。
底层执行:从 MBQL 到 SQL
当你在查询构建器中写完concat([City], ", ", [Country])并运行后,Metabase 会把它编译成针对底层数据库的 SQL。以关系型数据库为例,最终执行的语句形如:
SELECT CONCAT(City, ', ', Country) AS "Location" FROM richard_linklater_films;在 SQL 驱动的查询处理器 src/metabase/driver/sql/query_processor.clj 中,MBQL 的:concat子句会被编译为 HoneySQL 形式的[:concat ...]表达式,再交由具体驱动生成各数据库方言的拼接语法(如 MySQL/PostgreSQL 的CONCAT、SQLite 的||等)。同一个concat表达式在 generate-pattern 等内部逻辑中也被复用——例如实现contains/starts-with/ends-with这类LIKE匹配时,非字面量模式会通过[:concat pre arg post]构造匹配串,可见concat是 SQL 后端的基础算子。
因此可以推断:concat的拼接工作在数据库端完成,Metabase 的自动类型转换最终表现为 SQL 中的隐式/显式字符串转换,这也是为什么展示格式化不会生效——格式化是前端展示层的行为,而拼接发生在 SQL 层。
与其他工具函数的等价实现
SQL
如果你的数据存在关系型数据库中,notebook 编辑器生成的查询最终都会转成 SQL。以下 SQL:
SELECT CONCAT(City, ", ", Country) AS "Location" FROM richard_linklater_films;等价于 Metabase 表达式:
concat([City], ", ", [Country])注意:不同数据库的拼接语法略有差异(MySQL/PostgreSQL/SQL Server 使用CONCAT,SQLite/PostgreSQL 也支持||运算符),Metabase 驱动层会屏蔽这些差异,让你始终用统一的concat书写。
Spreadsheets(电子表格)
如果样本数据在电子表格中,"City" 在 A 列、"Country" 在 B 列,可以用公式在 C 列生成 "Location":
=CONCATENATE(A2, ", ", B2)等价于 Metabase 表达式:
concat([City], ", ", [Country])(Excel/Google Sheets 中的&运算符、CONCAT函数同样可达到类似效果,Metabase 的concat语义与它们一致:按参数顺序拼接、不自动加分隔符。)
Python
假设样本数据在名为df的 DataFrame 中:
df["Location"] = df["City"] + ", " + df["Country"]等价于 Metabase 表达式:
concat([City], ", ", [Country])Metabase 表达式与编程语言写法的共同点是:所有操作都是按行进行的向量化操作——对每一行独立计算拼接结果,这正是表达式(Functions)与聚合(Aggregations)的本质区别,详见 自定义表达式文档。
常见问题与最佳实践
- 不要依赖自动分隔符:
concat不会自动插入空格或逗号。concat("Vienna", "Austria")的结果是ViennaAustria。始终把分隔符写成独立的字符串参数。 - 分隔符用字面量:分隔符属于固定文本,应写成双引号字符串字面量,如
", "、" - "、" "。 - 注意类型转换语义:数字、布尔、时间戳、JSON 都会转换为原始字符串。若需要格式化后的文本(两位小数、百分比符号、
YYYY-MM-DD日期),先格式化再拼接。 - 参数至少两个:MBQL schema 强制要求
concat至少 2 个参数;单参数拼接没有意义,可考虑直接引用列或使用其他函数。 - 空值与拼接:当参与拼接的列含
NULL时,不同数据库的行为可能不同(有的数据库CONCAT(NULL, ...)直接返回NULL)。若需要将空值视为空字符串,可结合coalesce使用,例如concat(coalesce([City], ""), ", ", coalesce([Country], ""))。 - 优先使用 Combine Columns 快捷操作:仅拼接现有字符串列时,点击列头选择 Combine Columns 即可自动生成
concat表达式,减少手写出错的可能。
延伸阅读
- 自定义表达式文档(总览) —— 表达式编辑器、函数浏览器、聚合与函数的区别
- Substring(子串截取) —— 与
concat搭配的字符串处理函数 - Coalesce(空值兜底) —— 处理拼接中的 NULL 值
- 数据建模 - 格式化 —— 了解列格式化与
concat原始值行为的关系 - 表达式 schema 定义 ——
concat在 MBQL 层的最小参数与返回类型约束 - 合并列 drill-thru 实现 —— Combine Columns 快捷操作的底层原理
【免费下载链接】metabaseThe easy-to-use open source Business Intelligence and Embedded Analytics tool that lets everyone work with data :bar_chart:项目地址: https://gitcode.com/GitHub_Trending/me/metabase
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考