Metabase substring 表达式详解:从文本中精准截取子串的完整指南
【免费下载链接】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
substring是 Metabase 自定义表达式(Custom Expressions)中用于从文本中提取子串的核心函数。本文以substring(text, position, length)语法为主线,结合该开源仓库中 MBQL 表达式 schema 定义 与 SQL 编译实现,系统讲解其参数规则、左右截取实战技巧、支持的数据类型、底层工作原理,以及与regexExtract、SQL、Excel、Python 等工具的等价写法对比。读完本文,你将能够用substring高效处理 SKU 编号、ISO 编码、标准格式邮箱等定长格式文本的清洗与拆分。
函数语法与参数
substring的调用形式为:
substring(text, position, length)| 参数 | 说明 | 示例 |
|---|---|---|
text | 要提取子串的源文本(字符串或字符串类型的字段引用) | "user_id@email.com" |
position | 子串起始位置,从 1 开始计数 | 1 |
length | 要提取的字符数量,必须是正数 | 7 |
例如,从邮箱地址中提取用户 ID:
substring("user_id@email.com", 1, 7)结果为"user_id"。
参数规则要点:
- 字符串的第一个字符位于位置 1,而不是 0。这与很多编程语言(如 Python)的从 0 计数不同,但与 SQL 标准函数
SUBSTRING、Excel 的MID保持一致。 length必须是正数,即只能正向截取固定数量的字符,不支持负数长度反向截取。
从源码结构看,MBQL 层面对substring的参数做了严格的类型约束。在 src/metabase/lib/schema/expression/string.cljc#L35-L38 中,substring被定义为catn(concatenation)类型的子句,三个参数依次为:
(mbql-clause/define-catn-mbql-clause :substring :- :type/Text [:str [:schema [:ref ::expression/string]]] [:start [:schema [:ref ::expression/integer]]] [:length [:? [:schema [:ref ::expression/integer]]]])也就是说:str参数必须是字符串表达式,start参数必须是整数表达式,length参数是可选的整数表达式,整个表达式的返回类型为:type/Text(文本)。值得注意的是,position与length并不强制要求是字面量数字——只要最终解析为整数表达式即可,这意味着可以嵌套length([列])、count等返回整数的函数(见下文"从右侧截取")。前端表达式编译器(frontend/src/metabase/querying/expressions/test/generator.ts#L418-L424)同样将substring的参数定义为"字符串表达式 + 数字表达式 + 数字表达式"。
从左侧截取子串
当需要提取文本开头的固定长度片段时,直接设置position为1即可,这正是substring最常见的用法。
假设有一张任务表,Mission ID字段形如19951113006,前 8 位是日期/编号信息,末尾 3 位是特工编号:
| Mission ID | Agent |
|---|---|
| 19951113006 | 006 |
| 20061114007 | 007 |
| 19640917008 | 008 |
创建一个名为Agent的自定义列,表达式为:
substring([Mission ID], 9, 3)含义是:从第 9 个字符开始,连续取 3 个字符,得到006、007、008。
这类"定长、有固定格式"的字符串正是substring最擅长处理的场景——SKU 编号、ISO 国家代码、标准化的邮箱地址等,只要格式一致,就能用一条表达式稳定拆分。
从右侧截取子串
substring本身不直接支持"倒数第几个字符开始"的写法,但可以通过嵌套length函数把"从右往左数"换算成"从左往右的起始位置"。通用公式为:
1 + length([column]) - position_from_right其中position_from_right表示从右往左数起始位置(即倒数第几个字符),length([column])返回整列文本的总长度。
仍以上述任务表为例,Agent 是末尾 3 位字符。position_from_right = 3,代入公式:
substring([Mission ID], (1 + length([Mission ID]) - 3), 3)对19951113006而言,length为 11,起始位置为1 + 11 - 3 = 9,即从第 9 个字符起取 3 位,结果同样是006。表内计算过程如下:
| Mission ID | Agent(substring 结果) |
|---|---|
| 19951113006 | 006 |
| 20061114007 | 007 |
| 19640917008 | 008 |
这里的length也是 Metabase 的内置文本函数,其 MBQL 定义返回:type/Integer(见 src/metabase/lib/schema/expression/string.cljc#L20-L21),因此可以被substring直接当作整数表达式参数使用,印证了上面提到的"参数可以是表达式"这一特性。
接受的数据类型
substring只接受文本(String)类型作为第一个参数,其余数据类型均不支持:
| 数据类型 | 是否可与substring配合 |
|---|---|
| String | ✅ |
| Number | ❌ |
| Timestamp | ❌ |
| Boolean | ❌ |
| JSON | ❌ |
因此,如果源字段是数字、日期、布尔或 JSON 等类型,需要先用类型转换表达式将其转为文本,再交给substring截取。
底层实现:substring 如何被编译成 SQL
理解substring的底层编译过程,有助于判断它在不同数据库上的行为差异。Metabase 的查询表达式会先被解析为 MBQL(Metabase BI Query Language)语法树,再经由各数据库驱动编译为对应方言的 SQL。
在通用 SQL 驱动层(src/metabase/driver/sql/query_processor.clj#L1386-L1390)中,substring被编译为标准的 SQLSUBSTRING函数:
(defmethod ->honeysql [:sql :substring] [driver [_ _opts arg start length]] (if length [:substring (->honeysql driver arg) (->honeysql driver start) (->honeysql driver length)] [:substring (->honeysql driver arg) (->honeysql driver start)]))注意这里对可选参数length的分支处理:当未提供length时,生成的 SQL 只带两个参数(SUBSTRING(text, start),表示从起始位置取到末尾);提供length时生成三参数形式(SUBSTRING(text, start, length))。
部分数据库方言需要单独适配。例如 SQLite 没有三参数的SUBSTRING,Metabase 在 src/metabase/driver/sqlite.clj#L414-L418 中将其改写为SUBSTR:
(defmethod sql.qp/->honeysql [:sqlite :substring] [driver [_ _opts arg start length]] (if length [:substr (sql.qp/->honeysql driver arg) (sql.qp/->honeysql driver start) (sql.qp/->honeysql driver length)] [:substr (sql.qp/->honeysql driver arg) (sql.qp/->honeysql driver start)]))MySQL 驱动(src/metabase/driver/mysql.clj)则使用substring_index系列函数进行适配。也就是说:你在 Metabase 表达式中写下的substring([Mission ID], 9, 3)是数据库无关的统一写法,实际执行时由 Metabase 按目标数据库自动翻译成SUBSTRING、SUBSTR等对应方言。
限制与替代方案
substring按"固定字符数"提取文本,因此有两个天然局限:
- 无法基于模式匹配提取:当截取规则比较复杂(如"找到最后一个
00之后的所有字符")时,substring无能为力,此时应改用regexExtract配合正则表达式。 - 不处理空白字符:如果只是想去掉文本两端的多余空格,
substring不是合适工具,应改用trim/lTrim/rTrim系列表达式——这三个函数在 src/metabase/lib/schema/expression/string.cljc#L8-L10 中被统一定义为返回文本类型的一元函数。
与其他工具/函数的等价写法
RegexExtract:模式驱动的提取
若截取规则依赖模式而非固定位置,可用regexExtract实现同样的效果。例如要提取19951113006中最后一个00及其之后的内容:
regexExtract([Mission ID], ".+(00.+)$")其结果与substring([Mission ID], 9, 3)一致。二者取舍原则:文本格式固定 → 用substring(简单、可读性强);格式多变或需按模式匹配 → 用regexExtract(灵活、支持正则)。
SQL
当你在 Notebook 编辑器中运行查询时,Metabase 会把图形化的查询设置(筛选、汇总、自定义列等)转换为 SQL 并在数据库中执行。如果上述示例数据存放在 PostgreSQL 中:
SELECT mission_id, SUBSTRING(mission_id, 9, 3) AS agent FROM this_message_will_self_destruct;这段 SQL 与 Metabase 表达式substring([Mission ID], 9, 3)完全等价——这也正对应前面提到的"通用 SQL 驱动将 MBQLsubstring编译为标准SUBSTRING"的实现。
Spreadsheets(Excel / Google Sheets)
如果数据在电子表格中,且Mission ID位于 A 列:
=mid(A2,9,3)Excel 的MID函数与 Metabasesubstring等价,同为"从第 9 个字符起取 3 个字符",且二者都从位置 1 开始计数。
Python(pandas)
假设数据在名为df的 DataFrame 中:
df['Agent'] = df['Mission ID'].str.slice(8, 11)str.slice(8, 11)提取从索引 8(含)到索引 11(不含)的字符,即第 9~11 个字符,与substring([Mission ID], 9, 3)结果相同。注意 Python 字符串索引从 0 开始,因此这里写的是8, 11而不是9, 3,这也提醒你:跨工具迁移时务必留意计数基准的差异。
进一步阅读
- Metabase 自定义表达式完整列表
- 自定义表达式使用指南
- regexExtract 正则提取表达式详解
- 表达式底层编译链路:MBQL 字符串函数 schema、SQL 查询处理器、SQLite 驱动实现
【免费下载链接】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),仅供参考