MySQL作为最流行的关系型数据库之一,其内置函数在日常开发中扮演着重要角色。这些函数能极大简化数据处理逻辑,提升开发效率。本文将系统讲解MySQL中最常用的四类函数:日期函数、字符串函数、数学函数和其他实用函数,并通过真实案例演示它们的应用场景。
日期和时间处理是数据库操作的常见需求,MySQL提供了一系列函数来满足不同场景:
sql复制-- 获取当前日期(不包含时间)
SELECT CURRENT_DATE(); -- 输出格式:YYYY-MM-DD
-- 获取当前时间(不包含日期)
SELECT CURRENT_TIME(); -- 输出格式:HH:MM:SS
-- 获取完整时间戳(日期+时间)
SELECT NOW(); -- 输出格式:YYYY-MM-DD HH:MM:SS
-- 提取日期部分
SELECT DATE('2023-05-15 14:30:22'); -- 返回:2023-05-15
-- 提取时间部分
SELECT TIME('2023-05-15 14:30:22'); -- 返回:14:30:22
提示:NOW()和SYSDATE()都返回当前日期时间,但NOW()在SQL执行开始时确定值,而SYSDATE()在函数调用时实时获取,这在存储过程和触发器中会有差异。
实际业务中经常需要对日期进行计算:
sql复制-- 日期加法
SELECT DATE_ADD('2023-01-01', INTERVAL 1 MONTH); -- 返回:2023-02-01
-- 日期减法
SELECT DATE_SUB('2023-12-31', INTERVAL 1 QUARTER); -- 返回:2023-09-30
-- 计算日期差
SELECT DATEDIFF('2023-12-31', '2023-01-01'); -- 返回:364
-- 时间戳转换
SELECT UNIX_TIMESTAMP('2023-05-15 00:00:00'); -- 返回秒级时间戳
SELECT FROM_UNIXTIME(1684108800); -- 将时间戳转为日期时间
INTERVAL支持的单位包括:YEAR, QUARTER, MONTH, WEEK, DAY, HOUR等,满足各种时间跨度需求。
sql复制CREATE TABLE user_birthdays (
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) NOT NULL,
birthdate DATE NOT NULL
);
-- 插入测试数据
INSERT INTO user_birthdays (username, birthdate) VALUES
('张三', '1990-05-20'),
('李四', '1985-10-15');
-- 查询下周过生日的用户
SELECT username, birthdate
FROM user_birthdays
WHERE WEEK(birthdate) = WEEK(DATE_ADD(NOW(), INTERVAL 1 WEEK));
sql复制CREATE TABLE messages (
id INT PRIMARY KEY AUTO_INCREMENT,
content TEXT NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
-- 插入测试留言
INSERT INTO messages (content) VALUES
('第一条留言内容'),
('第二条留言内容');
-- 只显示日期部分
SELECT id, content, DATE(created_at) AS post_date FROM messages;
-- 查询2分钟内发布的留言
SELECT id, content, created_at
FROM messages
WHERE created_at >= DATE_SUB(NOW(), INTERVAL 2 MINUTE);
注意事项:DATETIME和TIMESTAMP都存储日期时间,但TIMESTAMP范围更小(1970-2038),且会受时区影响,而DATETIME不会。根据业务需求谨慎选择。
字符串处理是数据库操作的核心需求之一:
sql复制-- 连接字符串
SELECT CONCAT('Hello', ' ', 'World'); -- 返回:Hello World
-- 带分隔符连接
SELECT CONCAT_WS('-', '2023', '05', '15'); -- 返回:2023-05-15
-- 大小写转换
SELECT LOWER('MySQL'); -- 返回:mysql
SELECT UPPER('mysql'); -- 返回:MYSQL
-- 字符串长度
SELECT CHAR_LENGTH('数据库'); -- 返回:3(字符数)
SELECT LENGTH('数据库'); -- 返回:9(字节数,UTF-8下)
sql复制-- 查找子串位置
SELECT INSTR('foobarbar', 'bar'); -- 返回:4(1-based)
-- 提取子串
SELECT SUBSTRING('MySQL Functions', 7, 8); -- 返回:Function
-- 模式匹配替换
SELECT
REGEXP_REPLACE('abc123def456', '[0-9]+', 'X'); -- 返回:abcXdefX
-- 普通替换
SELECT REPLACE('foo bar foo', 'foo', 'bar'); -- 返回:bar bar bar
sql复制-- 去除空格
SELECT TRIM(' MySQL '); -- 返回:MySQL
SELECT LTRIM(' MySQL'); -- 返回:MySQL
SELECT RTRIM('MySQL '); -- 返回:MySQL
-- 格式化输出
SELECT FORMAT(1234567.89, 2); -- 返回:1,234,567.89
-- 填充字符串
SELECT LPAD('5', 3, '0'); -- 返回:005
SELECT RPAD('Hi', 5, '!'); -- 返回:Hi!!!
sql复制-- 首字母小写显示姓名
SELECT
CONCAT(
LOWER(SUBSTRING(ename, 1, 1)),
SUBSTRING(ename, 2)
) AS formatted_name
FROM emp;
-- 姓名拼音首字母提取
SELECT
CONCAT_WS('',
UPPER(SUBSTRING(ename, 1, 1)),
IF(CHAR_LENGTH(ename)>1, UPPER(SUBSTRING(ename, 2, 1)), '')
) AS initials
FROM emp;
sql复制-- 手机号中间四位脱敏
SELECT
CONCAT(
SUBSTRING(phone, 1, 3),
'****',
SUBSTRING(phone, 8)
) AS masked_phone
FROM users;
-- 邮箱地址脱敏
SELECT
CONCAT(
SUBSTRING(email, 1, 1),
'****',
SUBSTRING(email, INSTR(email, '@')-1)
) AS masked_email
FROM users;
经验分享:处理多字节字符(如中文)时,优先使用CHAR_LENGTH而非LENGTH,因为LENGTH返回的是字节数。在UTF-8编码下,一个中文字符通常占3个字节。
sql复制-- 绝对值
SELECT ABS(-123); -- 返回:123
-- 取模运算
SELECT MOD(10, 3); -- 返回:1
SELECT 10 % 3; -- 同样返回1
-- 四舍五入
SELECT ROUND(3.14159, 2); -- 返回:3.14
-- 随机数
SELECT RAND(); -- 返回0-1之间的随机浮点数
sql复制-- 向上取整
SELECT CEIL(3.14); -- 返回:4
-- 向下取整
SELECT FLOOR(3.99); -- 返回:3
-- 截断小数
SELECT TRUNCATE(3.14159, 2); -- 返回:3.14
-- 符号判断
SELECT SIGN(-10); -- 返回:-1
SELECT SIGN(0); -- 返回:0
SELECT SIGN(10); -- 返回:1
sql复制-- 指数与对数
SELECT EXP(1); -- 返回e的近似值:2.718281828459045
SELECT LN(10); -- 自然对数
SELECT LOG(2,8); -- 以2为底8的对数,返回3
-- 三角函数
SELECT SIN(PI()/2); -- 返回:1
SELECT COS(PI()); -- 返回:-1
-- 幂运算
SELECT POWER(2, 10); -- 返回:1024
SELECT SQRT(16); -- 返回:4
sql复制-- 计算总页数(每页10条)
SELECT CEIL(COUNT(*) / 10) AS total_pages FROM products;
-- 获取随机样本
SELECT * FROM users ORDER BY RAND() LIMIT 5;
sql复制-- 精确小数计算(避免浮点误差)
SELECT
TRUNCATE(price * 0.1, 2) AS tax,
TRUNCATE(price * 1.1, 2) AS total_price
FROM orders;
-- 百分比计算
SELECT
CONCAT(ROUND((sold_quantity/total_quantity)*100, 2), '%') AS sold_percent
FROM inventory;
重要提示:金融计算务必使用DECIMAL类型而非FLOAT/DOUBLE,避免浮点数精度问题。ROUND和TRUNCATE在处理金额时非常有用,但要注意四舍五入规则是否符合业务需求。
sql复制-- 当前用户
SELECT USER(); -- 返回:root@localhost
-- 当前数据库
SELECT DATABASE(); -- 返回当前使用的数据库名
-- 连接ID
SELECT CONNECTION_ID(); -- 返回当前连接的唯一ID
-- 版本信息
SELECT VERSION(); -- 返回MySQL服务器版本
sql复制-- MD5哈希
SELECT MD5('password123'); -- 返回32位MD5哈希值
-- SHA加密
SELECT SHA1('password123'); -- 返回40位SHA1哈希值
SELECT SHA2('password123', 256); -- 指定长度的SHA2哈希
-- 密码加密(MySQL特有)
SELECT PASSWORD('secret'); -- 返回41位加密字符串
安全建议:MD5和SHA1已被证明存在弱点,生产环境建议使用SHA2系列算法。密码存储应结合盐值(salt)和多次哈希迭代。
sql复制-- IF函数
SELECT IF(score >= 60, '及格', '不及格') AS result FROM exams;
-- CASE WHEN
SELECT
name,
CASE
WHEN score >= 90 THEN '优秀'
WHEN score >= 80 THEN '良好'
WHEN score >= 60 THEN '及格'
ELSE '不及格'
END AS grade
FROM students;
-- NULL处理
SELECT IFNULL(NULL, '默认值'); -- 返回:默认值
SELECT COALESCE(NULL, NULL, '第一个非NULL值'); -- 返回:第一个非NULL值
sql复制-- 检查当前用户是否有权限
SELECT
IF(USER() = 'admin@localhost', '超级管理员', '普通用户') AS user_role;
-- 安全日志记录
INSERT INTO security_log (action, user, ip)
VALUES ('login', SUBSTRING_INDEX(USER(), '@', 1), '192.168.1.1');
sql复制-- 动态生成报表标题
SELECT
CONCAT(
'销售报表 - ',
DATE_FORMAT(NOW(), '%Y年%m月%d日'),
' - 生成人:',
SUBSTRING_INDEX(USER(), '@', 1)
) AS report_title;
-- 多条件统计
SELECT
product_id,
COUNT(*) AS total_orders,
SUM(CASE WHEN status = 'completed' THEN 1 ELSE 0 END) AS completed_orders,
SUM(amount) AS total_amount
FROM orders
GROUP BY product_id;
日期函数时区问题:
sql复制-- 显式指定时区
SELECT CONVERT_TZ(NOW(), 'SYSTEM', 'Asia/Shanghai');
字符串函数的多字节字符处理:
sql复制-- 安全截取中文字符
SELECT SUBSTRING('中文测试', 1, 2); -- 可能截断半个字符
NULL值处理不当:
sql复制-- 安全处理NULL值
SELECT IFNULL(column_name, 'N/A') FROM table;
避免在WHERE条件中使用函数:
sql复制-- 不推荐(无法使用索引)
SELECT * FROM orders WHERE DATE_FORMAT(create_time, '%Y-%m') = '2023-05';
-- 推荐写法
SELECT * FROM orders
WHERE create_time >= '2023-05-01' AND create_time < '2023-06-01';
减少函数嵌套层级:
sql复制-- 过度嵌套影响可读性和性能
SELECT UPPER(SUBSTRING(REPLACE(name, ' ', '_'), 1, 10)) FROM users;
合理使用存储过程封装复杂逻辑:
sql复制DELIMITER //
CREATE PROCEDURE calculate_stats()
BEGIN
-- 复杂函数逻辑封装
END //
DELIMITER ;
题目:计算字符串中逗号出现的次数
sql复制-- 解决方案1:利用长度差计算
SELECT
(LENGTH(str) - LENGTH(REPLACE(str, ',', '')))
/ LENGTH(',') AS comma_count
FROM strings;
-- 解决方案2:使用正则表达式(MySQL 8.0+)
SELECT
REGEXP_COUNT(str, ',') AS comma_count
FROM strings;
性能对比:在长字符串处理中,方案2(REGEXP_COUNT)通常性能更好,但需要MySQL 8.0及以上版本支持。方案1兼容性更好,但计算效率随字符串长度线性下降。
sql复制-- 计算用户最近活跃情况
SELECT
user_id,
username,
DATEDIFF(NOW(), last_login) AS days_since_login,
CASE
WHEN DATEDIFF(NOW(), last_login) <= 7 THEN '高活跃'
WHEN DATEDIFF(NOW(), last_login) <= 30 THEN '中活跃'
ELSE '低活跃'
END AS activity_level,
IFNULL(email, '未绑定') AS email_status
FROM users
ORDER BY days_since_login;
sql复制-- 生成月度销售报表
SELECT
DATE_FORMAT(order_date, '%Y-%m') AS month,
COUNT(*) AS order_count,
SUM(amount) AS total_amount,
ROUND(SUM(amount)/COUNT(*), 2) AS avg_order_value,
GROUP_CONCAT(DISTINCT product_name SEPARATOR ', ') AS products_sold
FROM orders
GROUP BY DATE_FORMAT(order_date, '%Y-%m')
ORDER BY month;
sql复制-- 用户密码安全升级
UPDATE users
SET password = SHA2(CONCAT(password_salt, ':', password), 256)
WHERE LENGTH(password) < 40; -- 识别未加密或弱加密密码
-- 密码强度检查
SELECT
username,
CASE
WHEN LENGTH(password) < 8 THEN '弱'
WHEN password REGEXP '[0-9]' AND password REGEXP '[A-Za-z]' THEN '强'
ELSE '中'
END AS password_strength
FROM users;
在实际项目中,我经常遇到开发者在WHERE条件中滥用函数导致索引失效的情况。一个典型的例子是使用DATE_FORMAT(create_time)做条件筛选,这会使MySQL无法使用create_time上的索引。正确的做法是使用范围查询,如create_time BETWEEN '2023-01-01' AND '2023-01-31'。
另一个常见误区是过度依赖数据库函数处理本应在应用层处理的逻辑。虽然MySQL函数强大,但复杂字符串处理或数学计算在应用层通常效率更高,也更容易维护。数据库函数的最佳使用场景是与数据直接相关的简单转换和计算。