Skip to content

PostgreSQL 模式匹配完全指南

PostgreSQL 提供了三种强大的模式匹配方法,每种都适用于不同的应用场景。本指南将深入探讨这三种方法的原理、语法和实际应用。

📋 概述

PostgreSQL 支持三种独立的模式匹配方法:

选择指南

  • LIKE:适用于简单的通配符匹配,安全性最高
  • SIMILAR TO:适用于需要基本正则表达式功能的场景
  • POSIX 正则表达式:适用于复杂的模式匹配需求

安全提醒

正则表达式可能消耗大量时间和内存。在处理来自不可信源的模式时,建议:

  • 设置语句超时
  • 优先使用 LIKE 操作符
  • 谨慎处理用户输入的正则表达式

🔍 LIKE 操作符详解

基本语法

sql
string LIKE pattern [ESCAPE escape-character]
string NOT LIKE pattern [ESCAPE escape-character]

通配符规则

通配符含义示例
%匹配零个或多个字符'hello%' 匹配以 "hello" 开头的字符串
_匹配任意单个字符'h_llo' 匹配 "hello", "hallo" 等

实战示例:用户搜索功能

假设我们有一个用户表,需要实现用户搜索功能:

sql
-- 创建示例表
CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    username VARCHAR(50),
    email VARCHAR(100),
    phone VARCHAR(20)
);

-- 插入测试数据
INSERT INTO users (username, email, phone) VALUES
    ('john_doe', '[email protected]', '123-456-7890'),
    ('jane_smith', '[email protected]', '987-654-3210'),
    ('bob_wilson', '[email protected]', '555-123-4567'),
    ('alice_brown', '[email protected]', '444-555-6666');

场景 1:模糊搜索用户名

sql
-- 搜索包含 'john' 的用户名
SELECT username, email FROM users
WHERE username LIKE '%john%';

输出结果:

 username |      email
----------+------------------
 john_doe | [email protected]

分析过程:

  • %john% 模式匹配任何包含 "john" 子字符串的用户名
  • 前后的 % 确保匹配字符串中任意位置的 "john"

场景 2:搜索特定格式的邮箱

sql
-- 搜索以特定域名结尾的邮箱
SELECT username, email FROM users
WHERE email LIKE '%@email.com';

输出结果:

  username   |       email
-------------+-------------------
 john_doe    | [email protected]
 alice_brown | [email protected]

场景 3:搜索特定格式的电话号码

sql
-- 搜索特定格式的电话号码(xxx-xxx-xxxx)
SELECT username, phone FROM users
WHERE phone LIKE '___-___-____';

输出结果:

  username   |    phone
-------------+--------------
 john_doe    | 123-456-7890
 jane_smith  | 987-654-3210
 bob_wilson  | 555-123-4567
 alice_brown | 444-555-6666

分析过程:

  • 每个 _ 匹配一个字符
  • 模式 ___-___-____ 确保电话号码格式为 "三位数-三位数-四位数"

转义字符的使用

当需要匹配字面上的 %_ 字符时:

sql
-- 创建包含特殊字符的测试数据
INSERT INTO users (username, email) VALUES
    ('test_user', 'test%[email protected]'),
    ('user_100%', '[email protected]');

-- 搜索包含字面 % 字符的邮箱
SELECT username, email FROM users
WHERE email LIKE '%\%%' ESCAPE '\';

输出结果:

 username |       email
----------+--------------------
 test_user| test%[email protected]

转义字符技巧

  • 默认转义字符是反斜杠 \
  • 可以使用 ESCAPE 子句指定自定义转义字符
  • 使用 ESCAPE '' 禁用转义功能

ILIKE:不区分大小写的匹配

sql
-- 不区分大小写的搜索
SELECT username FROM users
WHERE username ILIKE 'JOHN%';

输出结果:

 username
----------
 john_doe

🔧 SIMILAR TO 操作符详解

基本语法

sql
string SIMILAR TO pattern [ESCAPE escape-character]
string NOT SIMILAR TO pattern [ESCAPE escape-character]

支持的正则表达式元字符

| 元字符 | 含义 | 示例 | | ------- | ---------- | --------------------------------- | --- | ------------------- | | | | 或操作 | (a | b) 匹配 'a' 或 'b' | | * | 零次或多次 | a* 匹配 '', 'a', 'aa' 等 | | + | 一次或多次 | a+ 匹配 'a', 'aa', 'aaa' 等 | | ? | 零次或一次 | a? 匹配 '' 或 'a' | | {m} | 精确 m 次 | a{3} 匹配 'aaa' | | {m,} | 至少 m 次 | a{2,} 匹配 'aa', 'aaa' 等 | | {m,n} | m 到 n 次 | a{2,4} 匹配 'aa', 'aaa', 'aaaa' |

实战示例:数据验证

场景 1:验证邮箱格式

sql
-- 验证基本邮箱格式
SELECT email,
       email SIMILAR TO '%@%.%' AS is_valid_email
FROM users;

输出结果:

       email        | is_valid_email
--------------------+----------------
 [email protected] | t
 [email protected]| t
 [email protected]    | t
 [email protected]| t

场景 2:验证复杂模式

sql
-- 验证用户名格式:字母开头,可包含字母、数字、下划线,长度3-20
SELECT username,
       username SIMILAR TO '[a-zA-Z][a-zA-Z0-9_]{2,19}' AS is_valid_username
FROM users;

输出结果:

  username   | is_valid_username
-------------+-------------------
 john_doe    | t
 jane_smith  | t
 bob_wilson  | t
 alice_brown | t

场景 3:提取数据模式

sql
-- 查找包含数字的用户名
SELECT username FROM users
WHERE username SIMILAR TO '%[0-9]%';

SUBSTRING 函数与 SIMILAR TO

sql
-- 从字符串中提取匹配模式的部分
SELECT
    phone,
    SUBSTRING(phone SIMILAR '([0-9]{3})-.*' ESCAPE '') AS area_code
FROM users;

输出结果:

    phone     | area_code
--------------+-----------
 123-456-7890 | 123
 987-654-3210 | 987
 555-123-4567 | 555
 444-555-6666 | 444

🚀 POSIX 正则表达式详解

基本操作符

操作符含义示例
~匹配'abc' ~ 'a.c' 返回 true
~*不区分大小写匹配'ABC' ~* 'a.c' 返回 true
!~不匹配'abc' !~ 'x.z' 返回 true
!~*不区分大小写不匹配'ABC' !~* 'x.z' 返回 true

实战示例:日志分析系统

假设我们有一个日志表需要分析:

sql
-- 创建日志表
CREATE TABLE access_logs (
    id SERIAL PRIMARY KEY,
    ip_address VARCHAR(15),
    request_path VARCHAR(200),
    user_agent TEXT,
    response_code INTEGER,
    log_time TIMESTAMP
);

-- 插入测试数据
INSERT INTO access_logs (ip_address, request_path, user_agent, response_code, log_time) VALUES
    ('192.168.1.100', '/api/users/123', 'Mozilla/5.0 (Windows NT 10.0)', 200, NOW()),
    ('10.0.0.50', '/admin/dashboard', 'Chrome/91.0.4472.124', 403, NOW()),
    ('203.0.113.10', '/api/data.json', 'PostmanRuntime/7.28.0', 200, NOW()),
    ('192.168.1.200', '/login', 'Mozilla/5.0 (Macintosh)', 302, NOW());

场景 1:识别可疑 IP 地址

sql
-- 查找私有 IP 地址范围
SELECT ip_address, request_path FROM access_logs
WHERE ip_address ~ '^(192\.168\.|10\.|172\.(1[6-9]|2[0-9]|3[01])\.)';

输出结果:

  ip_address  | request_path
--------------+------------------
 192.168.1.100| /api/users/123
 10.0.0.50    | /admin/dashboard
 192.168.1.200| /login

分析过程:

  • ^ 匹配字符串开始
  • \. 转义点字符,匹配字面上的点
  • | 表示或操作
  • [6-9] 匹配 6-9 范围内的数字

场景 2:提取 API 端点

sql
-- 提取 API 路径中的资源 ID
SELECT
    request_path,
    regexp_match(request_path, '/api/(\w+)/(\d+)') AS extracted_parts
FROM access_logs
WHERE request_path ~ '/api/\w+/\d+';

输出结果:

 request_path   |  extracted_parts
----------------+-------------------
 /api/users/123 | {users,123}

场景 3:分析用户代理

sql
-- 识别不同类型的客户端
SELECT
    user_agent,
    CASE
        WHEN user_agent ~* 'Mozilla.*Chrome' THEN 'Chrome Browser'
        WHEN user_agent ~* 'Mozilla.*Firefox' THEN 'Firefox Browser'
        WHEN user_agent ~* 'PostmanRuntime' THEN 'API Testing Tool'
        ELSE 'Other'
    END AS client_type
FROM access_logs;

输出结果:

         user_agent          |   client_type
-----------------------------+------------------
 Mozilla/5.0 (Windows NT 10.0)| Other
 Chrome/91.0.4472.124       | Chrome Browser
 PostmanRuntime/7.28.0      | API Testing Tool
 Mozilla/5.0 (Macintosh)    | Other

高级正则表达式函数

regexp_count:计算匹配次数

sql
-- 统计路径中的分段数量
SELECT
    request_path,
    regexp_count(request_path, '/') AS path_segments
FROM access_logs;

输出结果:

 request_path     | path_segments
------------------+---------------
 /api/users/123   | 3
 /admin/dashboard | 2
 /api/data.json   | 2
 /login           | 1

regexp_replace:文本替换

sql
-- 将私有 IP 地址匿名化
SELECT
    ip_address,
    regexp_replace(ip_address, '(\d+\.\d+\.\d+\.)\d+', '\1***') AS anonymized_ip
FROM access_logs;

输出结果:

  ip_address  | anonymized_ip
--------------+---------------
 192.168.1.100| 192.168.1.***
 10.0.0.50    | 10.0.0.***
 203.0.113.10 | 203.0.113.***
 192.168.1.200| 192.168.1.***

regexp_split_to_array:字符串分割

sql
-- 分割请求路径
SELECT
    request_path,
    regexp_split_to_array(request_path, '/') AS path_parts
FROM access_logs
WHERE request_path != '/login';

输出结果:

 request_path     |      path_parts
------------------+-----------------------
 /api/users/123   | {"",api,users,123}
 /admin/dashboard | {"",admin,dashboard}
 /api/data.json   | {"",api,data.json}

📊 性能优化与最佳实践

索引优化

sql
-- 为常用的模式匹配创建索引
CREATE INDEX idx_users_username_pattern ON users
USING gin (username gin_trgm_ops);

-- 对于前缀匹配,可以使用 B-tree 索引
CREATE INDEX idx_users_email_prefix ON users (email text_pattern_ops);

性能对比测试

sql
-- 测试不同匹配方法的性能
EXPLAIN ANALYZE
SELECT * FROM users WHERE username LIKE 'john%';

EXPLAIN ANALYZE
SELECT * FROM users WHERE username ~ '^john';

EXPLAIN ANALYZE
SELECT * FROM users WHERE username SIMILAR TO 'john%';

性能建议

  1. 优先使用 LIKE:对于简单模式匹配,LIKE 通常最快
  2. 合理使用索引:为频繁查询的模式创建适当的索引
  3. 避免前导通配符%pattern 无法使用标准索引
  4. 考虑全文搜索:对于复杂文本搜索,考虑使用 PostgreSQL 的全文搜索功能

🔄 实际应用场景总结

用户输入验证

sql
-- 综合验证系统
CREATE OR REPLACE FUNCTION validate_user_input(
    p_username VARCHAR(50),
    p_email VARCHAR(100),
    p_phone VARCHAR(20)
) RETURNS TABLE (
    field_name TEXT,
    is_valid BOOLEAN,
    error_message TEXT
) AS $$
BEGIN
    -- 验证用户名
    RETURN QUERY SELECT
        'username'::TEXT,
        p_username SIMILAR TO '[a-zA-Z][a-zA-Z0-9_]{2,49}',
        CASE WHEN p_username SIMILAR TO '[a-zA-Z][a-zA-Z0-9_]{2,49}'
             THEN 'Valid'
             ELSE 'Username must start with letter, 3-50 characters'
        END;

    -- 验证邮箱
    RETURN QUERY SELECT
        'email'::TEXT,
        p_email ~ '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$',
        CASE WHEN p_email ~ '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$'
             THEN 'Valid'
             ELSE 'Invalid email format'
        END;

    -- 验证电话号码
    RETURN QUERY SELECT
        'phone'::TEXT,
        p_phone ~ '^\d{3}-\d{3}-\d{4}$',
        CASE WHEN p_phone ~ '^\d{3}-\d{3}-\d{4}$'
             THEN 'Valid'
             ELSE 'Phone must be in format xxx-xxx-xxxx'
        END;
END;
$$ LANGUAGE plpgsql;

-- 使用验证函数
SELECT * FROM validate_user_input('john_doe', '[email protected]', '123-456-7890');

数据清理和标准化

sql
-- 清理和标准化电话号码
UPDATE users
SET phone = regexp_replace(
    regexp_replace(phone, '[^\d]', '', 'g'),
    '(\d{3})(\d{3})(\d{4})',
    '\1-\2-\3'
)
WHERE phone IS NOT NULL;

关键要点总结

  1. LIKE 操作符:简单快速,适合基本通配符匹配
  2. SIMILAR TO:SQL 标准,结合了 LIKE 和正则表达式的特性
  3. POSIX 正则表达式:功能最强大,适合复杂模式匹配
  4. 安全性考虑:处理用户输入时要小心,避免正则表达式攻击
  5. 性能优化:合理使用索引,选择合适的匹配方法

通过掌握这三种模式匹配方法,你可以在 PostgreSQL 中高效地处理各种文本匹配需求,从简单的用户搜索到复杂的数据验证和清理任务。