欢迎光临
我们一直在努力

PostgreSQL LEFT 和 RIGHT 函数完全指南

一、一句话大白话

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

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


二、基础语法

2.1 LEFT 函数

LEFT(string, count)

参数说明:

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

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

2.2 RIGHT 函数

RIGHT(string, count)

参数说明:

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

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


三、快速上手示例

3.1 基本用法

— 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 在表查询中使用

假设有一张用户表:

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:隐藏手机号中间位(脱敏)

SELECT
phone,
LEFT(phone, 3) || '****' || RIGHT(phone, 4) AS masked_phone
FROM users;

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

示例 2:提取邮箱域名

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:提取身份证地区码和年份

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:手机号脱敏

— 方法 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:身份证号脱敏

SELECT
id_card,
LEFT(id_card, 6) || '********' || RIGHT(id_card, 4) AS masked_id
FROM users;

— 结果: 110101********1234

场景 3:银行卡号脱敏

SELECT
card_number,
LEFT(card_number, 4) || ' **** **** ' || RIGHT(card_number, 4) AS masked_card
FROM bank_cards;

— 结果: 6222 **** **** 1234

场景 4:邮箱脱敏

SELECT
email,
LEFT(email, 2) || '***@' || RIGHT(email, LENGTH(email) POSITION('@' IN email)) AS masked_email
FROM users;

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


4.2 数据清洗

场景 1:去除文件扩展名

— 假设有文件名列表
SELECT
filename,
LEFT(filename, LENGTH(filename) 4) AS name_without_ext
FROM files
WHERE filename LIKE '%.txt';

— 结果:
— report.txt -> report
— data.txt -> data

场景 2:提取文件扩展名

SELECT
filename,
RIGHT(filename, 4) AS extension
FROM files;

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

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

SELECT
filename,
RIGHT(filename, LENGTH(filename) POSITION('.' IN REVERSE(filename))) AS extension
FROM files;

— 或者使用正则表达式
SELECT
filename,
SUBSTRING(filename FROM '\\.([^.]+)$') AS extension
FROM files;

场景 3:标准化电话号码格式

— 原始数据格式混乱
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:按地区统计(身份证前缀)

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:按年份统计(身份证出生年)

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:按邮箱域名分组

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:订单号分段

— 订单号格式: 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:批次号解析

— 批次号格式: 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 编码提取

— 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:查找特定开头的记录

— 查找所有北京地区的用户(身份证以 110 开头)
SELECT *
FROM users
WHERE LEFT(id_card, 3) = '110';

— 等价于 LIKE,但性能更好(可以使用索引)
SELECT *
FROM users
WHERE id_card LIKE '110%';

场景 2:查找特定结尾的记录

— 查找所有 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' 不能使用索引

优化建议:

— 为 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 结合

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 拼接

— 生成格式化显示名称
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 配合

— 提取中间部分
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 动态长度提取

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

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


5.5 处理 NULL 值

— LEFT 和 RIGHT 遇到 NULL 会返回 NULL
SELECT
phone,
COALESCE(LEFT(phone, 3), 'N/A') AS phone_prefix
FROM users;

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


5.6 处理空字符串

— 当 count 为 0 或负数时
SELECT
LEFT('Hello', 0), — 结果: '' (空字符串)
LEFT('Hello', 1), — 结果: '' (空字符串)
RIGHT('Hello', 0), — 结果: '' (空字符串)
RIGHT('Hello', 1); — 结果: '' (空字符串)


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

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

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

验证:

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


六、性能优化

6.1 索引使用

问题:RIGHT 函数通常不能使用索引

— ❌ 慢查询: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 函数的索引优化

— 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 中使用函数

❌ 不推荐:

SELECT * FROM users WHERE LEFT(phone, 3) = '138';

✅ 推荐:

SELECT * FROM users WHERE phone LIKE '138%';

原因:

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

6.3 批量更新时的性能

— ❌ 慢:逐行计算
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 超过字符串长度

— 不会报错,只会返回整个字符串
SELECT LEFT('Hello', 100); — 结果: 'Hello'
SELECT RIGHT('Hello', 100); — 结果: 'Hello'

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


7.2 错误 2:忘记处理 NULL

— ❌ 可能返回 NULL
SELECT LEFT(phone, 3) FROM users;

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


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

— 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 混淆

— 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:数据类型问题

— LEFT/RIGHT 只能用于字符串类型
SELECT LEFT(12345, 2); — ❌ 错误!

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


八、与其他数据库的对比

8.1 MySQL

— MySQL 语法相同
SELECT LEFT('Hello World', 5); — 'Hello'
SELECT RIGHT('Hello World', 5); — 'World'

差异:

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

8.2 SQL Server

— SQL Server 语法相同
SELECT LEFT('Hello World', 5); — 'Hello'
SELECT RIGHT('Hello World', 5); — 'World'

几乎完全兼容!


8.3 Oracle

— 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 完整的用户信息脱敏视图

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 订单分析报表

— 按月统计订单分布
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 日志分析

— 从日志中提取 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 商品分类统计

— 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 性能建议

  • 优先使用 LIKE 代替 LEFT

    — ✅ 更好
    WHERE phone LIKE '138%'

    — ⚠️ 需要表达式索引
    WHERE LEFT(phone, 3) = '138'

  • 为常用表达式创建索引

    CREATE INDEX idx_phone_prefix ON users (LEFT(phone, 3));

  • 避免在大数据量上使用 RIGHT

    — ❌ 慢
    WHERE RIGHT(email, 9) = 'gmail.com'

    — ✅ 更快
    WHERE email LIKE '%gmail.com'


  • 10.3 代码规范

  • 始终处理 NULL 值

    COALESCE(LEFT(phone, 3), '')

  • 添加注释说明提取逻辑

    LEFT(id_card, 6) — 提取身份证地区码(前6位)

  • 使用有意义的别名

    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(phone,4)
    提取扩展名 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:

    — 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: 不支持。如果需要正则表达式,使用:

    — 使用正则表达式提取
    SELECT SUBSTRING(phone FROM '^(\\d{3})') AS prefix
    FROM users;


    Q4: 可以嵌套使用吗?

    A: 可以!

    — 先取前 10 位,再取其中的前 5 位
    SELECT LEFT(LEFT('Hello World', 10), 5); — 'Hello'

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


    Q5: 性能如何?

    A:

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

    十三、总结

    核心要点

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

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


    赞(0)
    未经允许不得转载:171主机测评 » PostgreSQL LEFT 和 RIGHT 函数完全指南
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址