一、一句话大白话
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 性能建议
-
优先使用 LIKE 代替 LEFT
sql-- ✅ 更好 WHERE phone LIKE '138%' -- ⚠️ 需要表达式索引 WHERE LEFT(phone, 3) = '138' -
为常用表达式创建索引
sqlCREATE INDEX idx_phone_prefix ON users (LEFT(phone, 3)); -
避免在大数据量上使用 RIGHT
sql-- ❌ 慢 WHERE RIGHT(email, 9) = 'gmail.com' -- ✅ 更快 WHERE email LIKE '%gmail.com'
10.3 代码规范
-
始终处理 NULL 值
sqlCOALESCE(LEFT(phone, 3), '') -
添加注释说明提取逻辑
sqlLEFT(id_card, 6) -- 提取身份证地区码(前6位) -
使用有意义的别名
sqlLEFT(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 是非常高效的内置函数
- 主要性能瓶颈在于是否使用索引
- 对于百万级数据,建议创建表达式索引
十三、总结
核心要点
- LEFT(str, n):从左边取 n 个字符
- RIGHT(str, n):从右边取 n 个字符
- 最常用场景:数据脱敏、编码解析、前缀/后缀提取
- 性能关键:合理使用索引,优先使用 LIKE 进行前缀匹配
- 安全第一:始终处理 NULL 值,使用 COALESCE
记忆口诀
LEFT 从左往右剪,RIGHT 从右往左剪
脱敏拼接最常用,索引优化别忘记
NULL 值要用 COALESCE,中文按字不按字节