PostgreSQL LEFT 和 RIGHT 函数完全指南

一、一句话大白话

LEFT(str, n) 从字符串左边取 n 个字符,RIGHT(str, n) 从字符串右边取 n 个字符。

就像用剪刀剪字符串:LEFT 从左边剪,RIGHT 从右边剪!


二、基础语法

2.1 LEFT 函数

sql 复制代码
LEFT(string, count)

参数说明:

  • string:要处理的字符串(可以是列名、常量或表达式)
  • count:要提取的字符数量(整数)

返回值: 从左边开始的前 count 个字符

2.2 RIGHT 函数

sql 复制代码
RIGHT(string, count)

参数说明:

  • string:要处理的字符串
  • count:要提取的字符数量(整数)

返回值: 从右边开始的后 count 个字符


三、快速上手示例

3.1 基本用法

sql 复制代码
-- LEFT 示例
SELECT LEFT('Hello World', 5);
-- 结果: 'Hello'

-- RIGHT 示例
SELECT RIGHT('Hello World', 5);
-- 结果: 'World'

图解:

复制代码
字符串: H e l l o   W o r l d
位置:   1 2 3 4 5 6 7 8 9 10 11

LEFT('Hello World', 5):  ↑↑↑↑↑ (取前5个)
RIGHT('Hello World', 5):           ↑↑↑↑↑ (取后5个)

3.2 在表查询中使用

假设有一张用户表:

sql 复制代码
CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    phone VARCHAR(20),
    email VARCHAR(100),
    id_card VARCHAR(18)
);

INSERT INTO users (phone, email, id_card) VALUES
('13800138000', 'zhangsan@example.com', '110101199001011234'),
('13900139000', 'lisi@example.com', '110101199505052345'),
('13700137000', 'wangwu@example.com', '110101200010103456');

示例 1:隐藏手机号中间位(脱敏)

sql 复制代码
SELECT 
    phone,
    LEFT(phone, 3) || '****' || RIGHT(phone, 4) AS masked_phone
FROM users;

-- 结果:
-- 13800138000 -> 138****8000
-- 13900139000 -> 139****9000
-- 13700137000 -> 137****7000

示例 2:提取邮箱域名

sql 复制代码
SELECT 
    email,
    RIGHT(email, LENGTH(email) - POSITION('@' IN email)) AS domain
FROM users;

-- 结果:
-- zhangsan@example.com -> example.com
-- lisi@example.com -> example.com
-- wangwu@example.com -> example.com

示例 3:提取身份证地区码和年份

sql 复制代码
SELECT 
    id_card,
    LEFT(id_card, 6) AS area_code,      -- 前6位:地区码
    SUBSTRING(id_card FROM 7 FOR 4) AS birth_year,  -- 第7-10位:出生年份
    RIGHT(id_card, 4) AS sequence       -- 后4位:顺序码+校验码
FROM users;

-- 结果:
-- 110101199001011234 -> 地区: 110101, 年份: 1990, 后缀: 1234

四、实际应用场景

4.1 数据脱敏(最常用)

场景 1:手机号脱敏
sql 复制代码
-- 方法 1:LEFT + RIGHT 拼接
SELECT 
    phone,
    LEFT(phone, 3) || '****' || RIGHT(phone, 4) AS masked_phone
FROM users;

-- 方法 2:使用 OVERLAY(更灵活)
SELECT 
    phone,
    OVERLAY(phone PLACING '****' FROM 4 FOR 4) AS masked_phone
FROM users;

-- 结果: 138****8000
场景 2:身份证号脱敏
sql 复制代码
SELECT 
    id_card,
    LEFT(id_card, 6) || '********' || RIGHT(id_card, 4) AS masked_id
FROM users;

-- 结果: 110101********1234
场景 3:银行卡号脱敏
sql 复制代码
SELECT 
    card_number,
    LEFT(card_number, 4) || ' **** **** ' || RIGHT(card_number, 4) AS masked_card
FROM bank_cards;

-- 结果: 6222 **** **** 1234
场景 4:邮箱脱敏
sql 复制代码
SELECT 
    email,
    LEFT(email, 2) || '***@' || RIGHT(email, LENGTH(email) - POSITION('@' IN email)) AS masked_email
FROM users;

-- 结果: zh***@example.com

4.2 数据清洗

场景 1:去除文件扩展名
sql 复制代码
-- 假设有文件名列表
SELECT 
    filename,
    LEFT(filename, LENGTH(filename) - 4) AS name_without_ext
FROM files
WHERE filename LIKE '%.txt';

-- 结果:
-- report.txt -> report
-- data.txt -> data
场景 2:提取文件扩展名
sql 复制代码
SELECT 
    filename,
    RIGHT(filename, 4) AS extension
FROM files;

-- 结果:
-- report.txt -> .txt
-- image.png -> .png
-- data.csv -> .csv

更通用的方法(处理不同长度的扩展名):

sql 复制代码
SELECT 
    filename,
    RIGHT(filename, LENGTH(filename) - POSITION('.' IN REVERSE(filename))) AS extension
FROM files;

-- 或者使用正则表达式
SELECT 
    filename,
    SUBSTRING(filename FROM '\.([^.]+)$') AS extension
FROM files;
场景 3:标准化电话号码格式
sql 复制代码
-- 原始数据格式混乱
SELECT 
    phone,
    -- 统一成 11 位格式
    LEFT(REPLACE(phone, '-', ''), 3) || '-' || 
    SUBSTRING(REPLACE(phone, '-', '') FROM 4 FOR 4) || '-' || 
    RIGHT(REPLACE(phone, '-', ''), 4) AS formatted_phone
FROM contacts;

-- 结果:
-- 13800138000 -> 138-0013-8000
-- 138-0013-8000 -> 138-0013-8000

4.3 数据分析

场景 1:按地区统计(身份证前缀)
sql 复制代码
SELECT 
    LEFT(id_card, 2) AS province_code,
    COUNT(*) AS user_count
FROM users
GROUP BY LEFT(id_card, 2)
ORDER BY user_count DESC;

-- 结果:
-- 11 (北京) -> 1500 人
-- 31 (上海) -> 1200 人
-- 44 (广东) -> 980 人
场景 2:按年份统计(身份证出生年)
sql 复制代码
SELECT 
    SUBSTRING(id_card FROM 7 FOR 4) AS birth_year,
    COUNT(*) AS user_count
FROM users
GROUP BY SUBSTRING(id_card FROM 7 FOR 4)
ORDER BY birth_year;

-- 结果:
-- 1990 -> 200 人
-- 1995 -> 350 人
-- 2000 -> 180 人
场景 3:按邮箱域名分组
sql 复制代码
SELECT 
    RIGHT(email, LENGTH(email) - POSITION('@' IN email)) AS domain,
    COUNT(*) AS user_count
FROM users
GROUP BY RIGHT(email, LENGTH(email) - POSITION('@' IN email))
ORDER BY user_count DESC;

-- 结果:
-- example.com -> 500 人
-- gmail.com -> 300 人
-- qq.com -> 250 人

4.4 编码/编号处理

场景 1:订单号分段
sql 复制代码
-- 订单号格式: ORD20260622001 (ORD + 日期 + 序号)
SELECT 
    order_no,
    LEFT(order_no, 3) AS prefix,           -- ORD
    SUBSTRING(order_no FROM 4 FOR 8) AS order_date,  -- 20260622
    RIGHT(order_no, 3) AS sequence         -- 001
FROM orders;
场景 2:批次号解析
sql 复制代码
-- 批次号格式: BATCH-2026-A-001
SELECT 
    batch_no,
    LEFT(batch_no, 5) AS batch_type,       -- BATCH
    SUBSTRING(batch_no FROM 7 FOR 4) AS year,  -- 2026
    SUBSTRING(batch_no FROM 12 FOR 1) AS category,  -- A
    RIGHT(batch_no, 3) AS serial           -- 001
FROM batches;
场景 3:SKU 编码提取
sql 复制代码
-- SKU: PROD-COLOR-SIZE-001
SELECT 
    sku,
    LEFT(sku, POSITION('-' IN sku) - 1) AS product_type,
    RIGHT(sku, 3) AS item_number
FROM products;

-- 结果:
-- PROD-RED-L-001 -> 类型: PROD, 编号: 001

4.5 字符串匹配和过滤

场景 1:查找特定开头的记录
sql 复制代码
-- 查找所有北京地区的用户(身份证以 110 开头)
SELECT *
FROM users
WHERE LEFT(id_card, 3) = '110';

-- 等价于 LIKE,但性能更好(可以使用索引)
SELECT *
FROM users
WHERE id_card LIKE '110%';
场景 2:查找特定结尾的记录
sql 复制代码
-- 查找所有 Gmail 用户
SELECT *
FROM users
WHERE RIGHT(email, 9) = 'gmail.com';

-- 或者使用 LIKE
SELECT *
FROM users
WHERE email LIKE '%gmail.com';

⚠️ 性能提示:

  • LEFT(column, n) = 'xxx' 可以使用索引(如果创建了表达式索引)
  • RIGHT(column, n) = 'xxx' 通常不能使用普通索引
  • LIKE 'xxx%' 可以使用索引
  • LIKE '%xxx' 不能使用索引

优化建议:

sql 复制代码
-- 为 LEFT 表达式创建索引
CREATE INDEX idx_users_id_card_prefix ON users (LEFT(id_card, 3));

-- 现在这个查询会很快
SELECT * FROM users WHERE LEFT(id_card, 3) = '110';

五、高级用法

5.1 与 CASE WHEN 结合

sql 复制代码
SELECT 
    phone,
    CASE 
        WHEN LEFT(phone, 3) = '138' THEN '中国移动'
        WHEN LEFT(phone, 3) = '139' THEN '中国移动'
        WHEN LEFT(phone, 3) = '137' THEN '中国移动'
        WHEN LEFT(phone, 3) = '186' THEN '中国联通'
        WHEN LEFT(phone, 3) = '185' THEN '中国联通'
        WHEN LEFT(phone, 3) = '133' THEN '中国电信'
        ELSE '其他'
    END AS carrier
FROM users;

-- 结果:
-- 13800138000 -> 中国移动
-- 18600186000 -> 中国联通

5.2 与 CONCAT 拼接

sql 复制代码
-- 生成格式化显示名称
SELECT 
    first_name,
    last_name,
    CONCAT(LEFT(first_name, 1), '. ', last_name) AS display_name
FROM employees;

-- 结果:
-- Zhang, San -> Z. San
-- Li, Si -> L. Si

5.3 与 SUBSTRING 配合

sql 复制代码
-- 提取中间部分
SELECT 
    id_card,
    LEFT(id_card, 6) AS area_code,
    SUBSTRING(id_card FROM 7 FOR 8) AS birthday,  -- YYYYMMDD
    RIGHT(id_card, 4) AS suffix
FROM users;

-- 结果:
-- 110101199001011234 -> 地区: 110101, 生日: 19900101, 后缀: 1234

5.4 动态长度提取

sql 复制代码
-- 根据条件动态提取不同长度
SELECT 
    code,
    CASE 
        WHEN LENGTH(code) > 10 THEN LEFT(code, 10)
        ELSE code
    END AS truncated_code
FROM products;

-- 超过 10 位的截断,不超过的保持原样

5.5 处理 NULL 值

sql 复制代码
-- LEFT 和 RIGHT 遇到 NULL 会返回 NULL
SELECT 
    phone,
    COALESCE(LEFT(phone, 3), 'N/A') AS phone_prefix
FROM users;

-- 如果 phone 是 NULL,返回 'N/A' 而不是 NULL

5.6 处理空字符串

sql 复制代码
-- 当 count 为 0 或负数时
SELECT 
    LEFT('Hello', 0),   -- 结果: '' (空字符串)
    LEFT('Hello', -1),  -- 结果: '' (空字符串)
    RIGHT('Hello', 0),  -- 结果: '' (空字符串)
    RIGHT('Hello', -1); -- 结果: '' (空字符串)

5.7 处理多字节字符(中文)

sql 复制代码
-- PostgreSQL 的 LEFT/RIGHT 按字符计数,不是字节
SELECT 
    LEFT('你好世界', 2),   -- 结果: '你好' (2个字符)
    RIGHT('你好世界', 2);  -- 结果: '世界' (2个字符)

-- 即使中文字符占 3 个字节(UTF-8),也按字符数计算

验证:

sql 复制代码
SELECT 
    '你好世界' AS original,
    LEFT('你好世界', 2) AS left_2,
    LENGTH('你好世界') AS char_length,          -- 4 (字符数)
    OCTET_LENGTH('你好世界') AS byte_length;    -- 12 (字节数,UTF-8)

六、性能优化

6.1 索引使用

问题:RIGHT 函数通常不能使用索引
sql 复制代码
-- ❌ 慢查询:RIGHT 无法使用普通索引
SELECT * FROM users WHERE RIGHT(phone, 4) = '8000';

-- ✅ 解决方案 1:使用 LIKE
SELECT * FROM users WHERE phone LIKE '%8000';  -- 仍然慢,但可以接受

-- ✅ 解决方案 2:创建表达式索引
CREATE INDEX idx_users_phone_suffix ON users (RIGHT(phone, 4));

-- 现在这个查询会变快
SELECT * FROM users WHERE RIGHT(phone, 4) = '8000';
LEFT 函数的索引优化
sql 复制代码
-- LEFT 可以使用前缀索引
CREATE INDEX idx_users_id_card_prefix ON users (LEFT(id_card, 6));

-- 查询会使用索引
SELECT * FROM users WHERE LEFT(id_card, 6) = '110101';

6.2 避免在 WHERE 中使用函数

❌ 不推荐:

sql 复制代码
SELECT * FROM users WHERE LEFT(phone, 3) = '138';

✅ 推荐:

sql 复制代码
SELECT * FROM users WHERE phone LIKE '138%';

原因:

  • LIKE '138%' 可以直接使用普通索引
  • LEFT(phone, 3) 需要创建表达式索引才能高效

6.3 批量更新时的性能

sql 复制代码
-- ❌ 慢:逐行计算
UPDATE users 
SET masked_phone = LEFT(phone, 3) || '****' || RIGHT(phone, 4);

-- ✅ 快:分批处理
UPDATE users 
SET masked_phone = LEFT(phone, 3) || '****' || RIGHT(phone, 4)
WHERE id BETWEEN 1 AND 10000;

UPDATE users 
SET masked_phone = LEFT(phone, 3) || '****' || RIGHT(phone, 4)
WHERE id BETWEEN 10001 AND 20000;

七、常见错误和陷阱

7.1 错误 1:count 超过字符串长度

sql 复制代码
-- 不会报错,只会返回整个字符串
SELECT LEFT('Hello', 100);  -- 结果: 'Hello'
SELECT RIGHT('Hello', 100); -- 结果: 'Hello'

这是安全的,不需要额外检查!


7.2 错误 2:忘记处理 NULL

sql 复制代码
-- ❌ 可能返回 NULL
SELECT LEFT(phone, 3) FROM users;

-- ✅ 安全做法
SELECT COALESCE(LEFT(phone, 3), '') FROM users;

7.3 错误 3:中英文混用时的长度计算

sql 复制代码
-- LENGTH 返回字符数
SELECT LENGTH('Hello你好');  -- 结果: 7 (5个英文 + 2个中文)

-- OCTET_LENGTH 返回字节数
SELECT OCTET_LENGTH('Hello你好');  -- 结果: 11 (5 + 6)

-- LEFT 按字符数截取
SELECT LEFT('Hello你好', 6);  -- 结果: 'Hello你'

7.4 错误 4:与 SUBSTRING 混淆

sql 复制代码
-- LEFT(str, n) = SUBSTRING(str FROM 1 FOR n)
SELECT LEFT('Hello World', 5);              -- 'Hello'
SELECT SUBSTRING('Hello World' FROM 1 FOR 5); -- 'Hello' (等价)

-- RIGHT(str, n) 没有直接的 SUBSTRING 等价写法
SELECT RIGHT('Hello World', 5);             -- 'World'
SELECT SUBSTRING('Hello World' FROM 7);     -- 'World' (需要计算起始位置)

7.5 错误 5:数据类型问题

sql 复制代码
-- LEFT/RIGHT 只能用于字符串类型
SELECT LEFT(12345, 2);  -- ❌ 错误!

-- 需要先转换
SELECT LEFT(12345::TEXT, 2);  -- ✅ 结果: '12'
SELECT LEFT(CAST(12345 AS TEXT), 2);  -- ✅ 结果: '12'

八、与其他数据库的对比

8.1 MySQL

sql 复制代码
-- MySQL 语法相同
SELECT LEFT('Hello World', 5);   -- 'Hello'
SELECT RIGHT('Hello World', 5);  -- 'World'

差异:

  • MySQL 的 LEFT/RIGHT 也可以用于数字(自动转换)
  • PostgreSQL 更严格,必须显式转换

8.2 SQL Server

sql 复制代码
-- SQL Server 语法相同
SELECT LEFT('Hello World', 5);   -- 'Hello'
SELECT RIGHT('Hello World', 5);  -- 'World'

几乎完全兼容!


8.3 Oracle

sql 复制代码
-- Oracle 没有 LEFT/RIGHT 函数,需要用 SUBSTR
SELECT SUBSTR('Hello World', 1, 5) FROM dual;     -- 'Hello' (等价 LEFT)
SELECT SUBSTR('Hello World', -5) FROM dual;        -- 'World' (等价 RIGHT)

注意: Oracle 使用负数表示从右边开始。


九、综合实战案例

9.1 完整的用户信息脱敏视图

sql 复制代码
CREATE VIEW v_users_masked AS
SELECT 
    id,
    -- 姓名脱敏:只显示姓
    LEFT(real_name, 1) || '**' AS masked_name,
    
    -- 手机号脱敏
    LEFT(phone, 3) || '****' || RIGHT(phone, 4) AS masked_phone,
    
    -- 邮箱脱敏
    LEFT(email, 2) || '***@' || 
    RIGHT(email, LENGTH(email) - POSITION('@' IN email)) AS masked_email,
    
    -- 身份证脱敏
    LEFT(id_card, 6) || '********' || RIGHT(id_card, 4) AS masked_id_card,
    
    -- 保留完整信息供内部使用(需要权限控制)
    created_at,
    updated_at
FROM users;

-- 使用
SELECT * FROM v_users_masked;

9.2 订单分析报表

sql 复制代码
-- 按月统计订单分布
SELECT 
    LEFT(order_no, 7) AS order_month,  -- 假设订单号包含年月
    COUNT(*) AS order_count,
    SUM(amount) AS total_amount,
    AVG(amount) AS avg_amount
FROM orders
GROUP BY LEFT(order_no, 7)
ORDER BY order_month;

-- 结果:
-- ORD2026-01 -> 1500 单, ¥150,000, ¥100
-- ORD2026-02 -> 1800 单, ¥180,000, ¥100

9.3 日志分析

sql 复制代码
-- 从日志中提取 IP 地址段
SELECT 
    LEFT(ip_address, INSTR(ip_address, '.', 1, 2) - 1) AS ip_segment,
    COUNT(*) AS access_count
FROM access_logs
GROUP BY LEFT(ip_address, INSTR(ip_address, '.', 1, 2) - 1)
ORDER BY access_count DESC;

-- 结果:
-- 192.168 -> 5000 次访问
-- 10.0 -> 3000 次访问

9.4 商品分类统计

sql 复制代码
-- SKU 格式: CAT-SUBCAT-ITEM-001
SELECT 
    LEFT(sku, POSITION('-' IN sku) - 1) AS category,
    COUNT(*) AS product_count,
    MIN(price) AS min_price,
    MAX(price) AS max_price,
    AVG(price) AS avg_price
FROM products
GROUP BY LEFT(sku, POSITION('-' IN sku) - 1)
ORDER BY product_count DESC;

十、最佳实践总结

10.1 何时使用 LEFT/RIGHT

推荐使用:

  • 数据脱敏(手机号、身份证、银行卡)
  • 提取固定长度的前缀/后缀
  • 解析编码/编号
  • 简单的字符串截取

不推荐使用:

  • 复杂的位置提取(用 SUBSTRING 或正则)
  • 可变长度的提取(用 POSITION + SUBSTRING)
  • 模式匹配(用 LIKE 或正则)

10.2 性能建议

  1. 优先使用 LIKE 代替 LEFT

    sql 复制代码
    -- ✅ 更好
    WHERE phone LIKE '138%'
    
    -- ⚠️ 需要表达式索引
    WHERE LEFT(phone, 3) = '138'
  2. 为常用表达式创建索引

    sql 复制代码
    CREATE INDEX idx_phone_prefix ON users (LEFT(phone, 3));
  3. 避免在大数据量上使用 RIGHT

    sql 复制代码
    -- ❌ 慢
    WHERE RIGHT(email, 9) = 'gmail.com'
    
    -- ✅ 更快
    WHERE email LIKE '%gmail.com'

10.3 代码规范

  1. 始终处理 NULL 值

    sql 复制代码
    COALESCE(LEFT(phone, 3), '')
  2. 添加注释说明提取逻辑

    sql 复制代码
    LEFT(id_card, 6) -- 提取身份证地区码(前6位)
  3. 使用有意义的别名

    sql 复制代码
    LEFT(phone, 3) AS phone_prefix

十一、速查表

场景 推荐写法 示例
取前 n 个字符 LEFT(str, n) LEFT('Hello', 2)'He'
取后 n 个字符 RIGHT(str, n) RIGHT('Hello', 2)'lo'
手机号脱敏 LEFT + RIGHT `LEFT(phone,3)
提取扩展名 RIGHT + LENGTH RIGHT(file, 4)'.txt'
前缀匹配 LIKE WHERE code LIKE 'ABC%'
后缀匹配 LIKE WHERE email LIKE '%.com'
中间提取 SUBSTRING SUBSTRING(str FROM 3 FOR 5)
动态位置 POSITION + SUBSTRING 提取 @ 后的域名

十二、常见问题 FAQ

Q1: LEFT 和 SUBSTRING 有什么区别?

A:

  • LEFT(str, n)SUBSTRING(str FROM 1 FOR n) 的简写
  • LEFT 更简洁,SUBSTRING 更灵活
  • 性能上几乎没有差别

Q2: 如何处理空字符串?

A:

sql 复制代码
-- LEFT/RIGHT 对空字符串返回空字符串
SELECT LEFT('', 5);   -- ''
SELECT RIGHT('', 5);  -- ''

-- 可以用 CASE 处理
SELECT 
    CASE 
        WHEN phone = '' THEN 'N/A'
        ELSE LEFT(phone, 3)
    END
FROM users;

Q3: LEFT/RIGHT 支持正则表达式吗?

A: 不支持。如果需要正则表达式,使用:

sql 复制代码
-- 使用正则表达式提取
SELECT SUBSTRING(phone FROM '^(\d{3})') AS prefix
FROM users;

Q4: 可以嵌套使用吗?

A: 可以!

sql 复制代码
-- 先取前 10 位,再取其中的前 5 位
SELECT LEFT(LEFT('Hello World', 10), 5);  -- 'Hello'

-- 实际意义不大,但可以这样用

Q5: 性能如何?

A:

  • LEFT/RIGHT 是非常高效的内置函数
  • 主要性能瓶颈在于是否使用索引
  • 对于百万级数据,建议创建表达式索引

十三、总结

核心要点

  1. LEFT(str, n):从左边取 n 个字符
  2. RIGHT(str, n):从右边取 n 个字符
  3. 最常用场景:数据脱敏、编码解析、前缀/后缀提取
  4. 性能关键:合理使用索引,优先使用 LIKE 进行前缀匹配
  5. 安全第一:始终处理 NULL 值,使用 COALESCE

记忆口诀

复制代码
LEFT 从左往右剪,RIGHT 从右往左剪
脱敏拼接最常用,索引优化别忘记
NULL 值要用 COALESCE,中文按字不按字节