Featured image of post DuckDB 字符串函数全家桶——数据清洗不再痛苦

DuckDB 字符串函数全家桶——数据清洗不再痛苦

DuckDB 20 个常用字符串函数完整指南,覆盖截取、拼接、大小写、正则替换、分割提取等数据清洗 90% 场景,附电话号码清洗、订单号标准化等实战案例。

DuckDB 字符串函数全家桶——数据清洗不再痛苦

DuckDB 字符串函数全家桶架构图

你有没有遇到过这种数据?

  • 订单号格式混乱:‘ORD-2026-001’、‘ord_001’、‘ORD2026001’
  • 用户姓名有空格:’ 张三 ‘、‘李四 ‘、’ 王五’
  • 手机号被包裹:’(010)12345678’、’+86-13800138000’
  • 地址包含多余信息:‘北京市海淀区中关村大街1号 3号楼’
  • 邮箱大小写混乱:‘[email protected]’、’[email protected]

用 Python 写正则?写一天都写不完。用 SQL 的 CASE WHEN?代码比数据还长。

今天讲 DuckDB 的字符串函数全家桶,20 个常用函数,覆盖 90% 的数据清洗场景。


一、基础截取与拼接

STRPOS / POSITION —— 找子串位置

SELECT 
    'ORD-2026-001' AS order_id,
    STRPOS('ORD-2026-001', '-') AS first_dash,      -- 3
    STRPOS('ORD-2026-001', '-', 4) AS second_dash;  -- 8

关键点STRPOS(str, substr) 返回子串第一次出现的位置,从 1 开始计数。第二个参数可以指定起始位置。


SUBSTRING / SUBSTR —— 截取子串

SELECT 
    'ORD-2026-001' AS order_id,
    SUBSTRING('ORD-2026-001' FROM 5 FOR 4) AS year,   -- '2026'
    SUBSTRING('ORD-2026-001' FROM 8) AS num;          -- '001'

关键点:DuckDB 支持 SQL 标准语法 SUBSTRING(str FROM start FOR length),也支持简写 SUBSTRING(str FROM start)


LEFT / RIGHT —— 从两端截取

SELECT 
    LEFT('2026-08-14 10:30:00', 10) AS date_part,   -- '2026-08-14'
    RIGHT('2026-08-14 10:30:00', 8) AS time_part;   -- '10:30:00'

CONCAT / || —— 拼接字符串

SELECT 
    CONCAT('hello', ' ', 'world') AS result1,       -- 'hello world'
    'hello' || ' ' || 'world' AS result2;           -- 'hello world'(推荐)

关键点|| 是 SQL 标准拼接符,比 CONCAT 更通用。DuckDB 中 || 遇到 NULL 返回 NULL,可以用 CONCAT 自动跳过 NULL。


CONCAT_WS —— 带分隔符拼接

SELECT 
    CONCAT_WS('-', '2026', '08', '14') AS date_str,  -- '2026-08-14'
    CONCAT_WS(',', '张三', '男', '北京') AS info;    -- '张三,男,北京'

关键点CONCAT_WS(separator, ...) 第一个参数是分隔符,自动跳过 NULL 值。


二、大小写与格式化处理

UPPER / LOWER / INITCAP —— 大小写控制

SELECT 
    UPPER('[email protected]') AS upper_email,       -- '[email protected]'
    LOWER('[email protected]') AS lower_email,       -- '[email protected]'
    INITCAP('hello world') AS capitalized;          -- 'Hello World'

关键点INITCAP 把每个单词的首字母大写,非常适合处理姓名、标题。


LPAD / RPAD —— 补零对齐

SELECT 
    LPAD('1', 5, '0') AS padded_left,    -- '00001'
    RPAD('1', 5, '0') AS padded_right;   -- '10000'

关键点LPAD(str, length, pad) 在左侧填充,RPAD 在右侧填充。常用于生成固定长度的编码。


TRIM / LTRIM / RTRIM —— 去空格

SELECT 
    TRIM('  张三  ') AS trimmed,      -- '张三'
    LTRIM('  张三  ') AS left_trim,   -- '张三  '
    RTRIM('  张三  ') AS right_trim;  -- '  张三'

关键点TRIM 默认去两端空格,也可以用 TRIM(LEADING/RTRAILING/BOTH) 控制方向。


三、替换与清理

REPLACE —— 全局替换

SELECT 
    REPLACE('ORD-2026-001', '-', '') AS no_dash,   -- 'ORD2026001'
    REPLACE('2026-08-14', '-', '/') AS changed;    -- '2026/08/14'

REGEXP_REPLACE —— 正则替换(杀手锏)

SELECT 
    REGEXP_REPLACE('(010)12345678', '[^0-9]', '') AS clean_phone,  -- '01012345678'
    REGEXP_REPLACE('hello123world456', '[0-9]', '-') AS no_digits; -- 'hello-world-'

关键点REGEXP_REPLACE(str, pattern, replacement) 用正则表达式匹配并替换,是处理非标准格式数据的终极武器。


四、分割与提取

SPLIT_PART —— 按分隔符取第 N 段

SELECT 
    order_id,
    SPLIT_PART(order_id, '-', 1) AS prefix,   -- 'ORD'
    SPLIT_PART(order_id, '-', 2) AS year,     -- '2026'
    SPLIT_PART(order_id, '-', 3) AS num;      -- '001'
FROM (VALUES 
    ('ORD-2026-001'),
    ('ORD-2026-002'),
    ('ORD-2025-100')
) AS t(order_id);

关键点SPLIT_PART(str, delimiter, field_num) 是 DuckDB 最常用的字符串分割函数,比 Python 的 split()[n] 简洁得多。


REGEXP_SPLIT_TO_TABLE —— 正则分割成行

SELECT * FROM REGEXP_SPLIT_TO_TABLE('Python,SQL,DuckDB', ',');

结果:

regex_split_to_table
---------------------
Python
SQL
DuckDB

REGEXP_EXTRACT —— 正则提取

SELECT 
    REGEXP_EXTRACT('ORD-2026-001', '(\\d{4})') AS year,    -- '2026'
    REGEXP_EXTRACT('ORD-2026-001', '(\\d{3})$') AS num;   -- '001'

关键点REGEXP_EXTRACT(str, pattern) 提取第一个匹配,配合 COALESCE 提供默认值。


五、长度与查找

LENGTH / CHAR_LENGTH —— 字符串长度

SELECT 
    LENGTH('DuckDB') AS byte_len,       -- 6
    LENGTH('张三') AS cn_byte_len;      -- 6(UTF-8 中文占 3 字节)
    
SELECT 
    CHAR_LENGTH('DuckDB') AS char_len,   -- 6
    CHAR_LENGTH('张三') AS cn_char_len;  -- 2(按字符数)

关键点LENGTH 按字节计算,CHAR_LENGTH 按字符计算。处理中文数据时用 CHAR_LENGTH


CONTAINS / LIKE —— 匹配检查

SELECT 
    'DuckDB' LIKE 'Duck%' AS starts_with_duck,  -- true
    'DuckDB' LIKE '%DB' AS ends_with_db,         -- true
    'DuckDB' LIKE '%uck%' AS contains_uck;       -- true
    
-- 更推荐的写法:CONTAINS(DuckDB 特有)
SELECT 
    CONTAINS('DuckDB', 'uck') AS has_uck;       -- true

关键点CONTAINS(str, substring) 是 DuckDB 的专用函数,比 LIKE '%xxx%' 更直观。


六、实战案例

案例一:电话号码清洗

真实场景:客户表里的手机号格式混乱,需要统一清洗。

CREATE TABLE raw_customers AS
SELECT * FROM VALUES
    ('Alice',   '(010)12345678'),
    ('Bob',     '+86-13800138000'),
    ('Charlie', '13800138000'),
    ('David',   '138 0013 8000'),
    ('Eve',     '13800138000 ext. 123')
AS t(name, phone_raw);

-- 清洗 SQL:去掉所有非数字字符
SELECT 
    name,
    phone_raw AS original,
    REGEXP_REPLACE(phone_raw, '[^0-9]', '') AS phone_clean
FROM raw_customers;

结果:

name    | original          | phone_clean
--------|-------------------|-------------
Alice   | (010)12345678     | 01012345678
Bob     | +86-13800138000   | 8613800138000
Charlie | 13800138000       | 13800138000
David   | 138 0013 8000     | 13800138000
Eve     | 13800138000...    | 13800138000

关键点:一行 REGEXP_REPLACE(phone, '[^0-9]', '') 搞定所有格式变体,比写 10 个 CASE WHEN 简洁 10 倍。


案例二:订单号标准化

CREATE TABLE orders AS
SELECT * FROM VALUES
    ('ORD-2026-001'),
    ('ord_001'),
    ('ORD2026001'),
    ('order-2026-002'),
    ('2026-003')
AS t(order_id_raw);

-- 标准化:统一格式为 'ORD-YYYY-NNN'
SELECT 
    order_id_raw,
    REGEXP_EXTRACT(order_id_raw, '(\\d{4})') AS year,
    REGEXP_EXTRACT(order_id_raw, '(\\d{3})$') AS num,
    'ORD-' 
    || COALESCE(REGEXP_EXTRACT(order_id_raw, '(\\d{4})'), '2026')
    || '-' 
    || LPAD(COALESCE(REGEXP_EXTRACT(order_id_raw, '(\\d{3})$'), '001'), 3, '0')
    AS order_id_std
FROM orders;

结果:

order_id_raw    | year  | num  | order_id_std
----------------|-------|------|-------------
ORD-2026-001    | 2026  | 001  | ORD-2026-001
ord_001         | NULL  | 001  | ORD-2026-001
ORD2026001      | 2026  | 001  | ORD-2026-001
order-2026-002  | 2026  | 002  | ORD-2026-002
2026-003        | 2026  | 003  | ORD-2026-003

七、与传统工具对比

操作Python + reSQL CASE WHENDuckDB 字符串函数
去空格s.strip()多个 CASETRIM(s)
转大写s.upper()多个 CASEUPPER(s)
正则替换re.sub()不支持REGEXP_REPLACE()
分割取值s.split()[n]不支持SPLIT_PART(s, '-', 2)
补零对齐自定义函数不支持LPAD(s, 5, '0')
包含检查sub in s多个 CASECONTAINS(s, sub)

结论:DuckDB 字符串函数让数据清洗从"写代码"变成"写 SQL",一行搞定原来需要多行的逻辑。


八、函数速查表

函数用途示例
TRIM(str)去两端空格TRIM(' hello ')'hello'
UPPER/LOWER大小写转换UPPER('abc')'ABC'
SUBSTRING(str, n, m)截取子串SUBSTRING('hello', 2, 3)'ell'
CONCAT_WS(sep, ...)带分隔符拼接CONCAT_WS('-', 2026, 8, 14)'2026-8-14'
SPLIT_PART(str, sep, n)按分隔符取第N段SPLIT_PART('a-b-c', '-', 2)'b'
REPLACE(str, old, new)全局替换REPLACE('abc', 'b', 'x')'axc'
REGEXP_REPLACE(str, pat, rep)正则替换REGEXP_REPLACE('a1b2', '[0-9]', '-')'a-b-'
REGEXP_EXTRACT(str, pat)正则提取REGEXP_EXTRACT('abc123', '(\d+)')'123'
LPAD/RPAD(str, len, pad)补零对齐LPAD('5', 3, '0')'005'
LENGTH/CHAR_LENGTH长度(字节/字符)CHAR_LENGTH('张三')2
CONTAINS(str, sub)包含检查CONTAINS('hello', 'ell')true
POSITION(sub IN str)查找位置POSITION('@' IN 'a@b')2

九、避坑指南

  1. NULL 传播:任何字符串函数遇到 NULL 输入返回 NULL,用 COALESCE(str, '') 先处理
  2. 字节 vs 字符LENGTH 按字节,CHAR_LENGTH 按字符。中文数据务必用 CHAR_LENGTH
  3. REGEXP 性能:复杂正则在大表上较慢,先用 LIKE 过滤再 REGEXP_REPLACE
  4. 空字符串 vs NULLTRIM('') 返回空字符串 '',不是 NULL。注意区分
  5. SPLIT_PART 越界:请求的段号超出实际段数时返回 NULL,不要假设一定存在

十、变现建议

1. 卖数据清洗服务

很多中小企业的数据"又脏又乱",但又没有数据团队。你可以用 DuckDB 字符串函数快速清洗客户数据、销售数据,按项目收费 3000-10000 元。

2. 做数据清洗 SaaS

把常用的清洗逻辑(手机号标准化、地址清洗、订单号格式化)封装成 API,按调用次数收费。月入 2000-5000 元不难。

3. 卖自动化报表模板

把字符串函数 + MERGE 增量更新的组合做成"日报自动生成器",面向电商、SaaS 公司销售,一次性收费 3000-8000 元。

4. 做 DuckDB 培训课程

字符串函数是 DuckDB 最实用的技能之一,可以做成系列课程,定价 99-299 元/人。

核心卖点:一行 SQL 搞定原来需要写一天 Python 的数据清洗工作。


总结

DuckDB 字符串函数覆盖数据清洗的 90% 场景:

  • 截取拼接SUBSTRINGCONCAT_WSLPAD
  • 格式化处理TRIMUPPER/LOWERINITCAP
  • 替换清理REPLACEREGEXP_REPLACE
  • 分割提取SPLIT_PARTREGEXP_EXTRACT

记住这个心法:标准格式用内置函数,混乱格式用正则

下次遇到数据清洗的痛点,先想想 DuckDB 字符串函数能不能一行搞定——大概率可以。

📖 详细图文教程和更多实战案例见 duckdblab.org

📺 Watch video tutorials → Olap Studio YouTube

Subscribe for more DuckDB & AI automation tutorials

使用 Hugo 构建
主题 StackJimmy 设计

⚠️ 本站为独立社区项目,与 DuckDB 基金会及 DuckDB 官方项目无任何从属、背书或赞助关系。

"DuckDB" 是 DuckDB 基金会的注册商标,本站仅以事实描述方式使用该名称。

本站内容仅供教育与社区推广用途,不构成任何商业服务。