初识MySQL——DQL数据查询语言详解

一、引言:从"存数据"到"查数据"的飞跃

在前面的博客中,我们学习了MySQL的安装和基本操作。但数据库的真正价值,并不在于"存储"数据,而在于高效、灵活地"检索"数据

想象一下:你手中有一张包含上万条记录的员工表,老板让你"找出工资高于平均水平的市场部员工,按入职时间排序,只显示前10名"。如果手动翻找,恐怕要花费几个小时。而使用数据查询语言(DQL,Data Query Language),只需一行SQL语句,几秒钟就能得到结果。

DQL是SQL语言中最核心、最常用的部分,它负责从数据库中查询数据。本篇博客将系统讲解DQL的完整语法,从基础查询到复杂的多表连接、子查询,配合大量实例,帮助大家真正掌握数据检索的精髓。

二、数据准备

为了便于演示,我们使用经典的"员工"数据库(myemployees.sql),包含以下四张表:

  • employees(员工表):employee_id, first_name, last_name, email, phone_number, job_id, salary, commission_pct, manager_id, department_id, hiredate

  • departments(部门表):department_id, department_name, manager_id, location_id

  • jobs(工种表):job_id, job_title, min_salary, max_salary

  • locations(位置表):location_id, street_address, postal_code, city, state_province, country_id

三、基础查询(SELECT)

3.1 语法结构

sql 复制代码
SELECT 查询列表 FROM 表名;
  • 查询列表可以是:字段、常量、表达式、函数

  • 查询结果是一个虚拟的表格

3.2 常见操作

sql 复制代码
-- 查询单个字段
SELECT last_name FROM employees;

-- 查询多个字段
SELECT employee_id, first_name, last_name FROM employees;

-- 查询所有字段(*)
SELECT * FROM employees;

-- 查询常量值
SELECT 66;
SELECT 'bigdata';

-- 查询表达式
SELECT 100 * 2;

-- 查询函数
SELECT VERSION();

-- 起别名(AS 或空格)
SELECT first_name AS 名, last_name 姓 FROM employees;

-- 去重(DISTINCT)
SELECT DISTINCT department_id FROM employees;

-- 拼接字符串(CONCAT)
SELECT CONCAT(last_name, first_name) AS 姓名 FROM employees;

特别注意 :MySQL中的+号只能用于数值相加,不能拼接字符串。拼接必须使用CONCAT()函数。对于NULL值,使用IFNULL()处理。

四、条件查询(WHERE)

4.1 语法

sql 复制代码
SELECT 查询列表
FROM 表名
WHERE 筛选条件;

4.2 筛选条件分类

类型 运算符/关键字
条件表达式 > < >= <= = != <>
逻辑表达式 AND OR NOT
模糊查询 LIKE BETWEEN AND IN IS NULL

4.3 案例演示

sql 复制代码
-- 条件表达式:工资大于12000
SELECT * FROM employees WHERE salary > 12000;

-- 逻辑表达式:部门编号不在90~110之间,或工资高于15000
SELECT * FROM employees 
WHERE (department_id < 90 OR department_id > 110) OR salary > 15000;

-- LIKE:姓名中包含字符a
SELECT * FROM employees WHERE first_name LIKE '%a%';

-- LIKE:第三个字符为e,第五个为a
SELECT first_name, salary FROM employees 
WHERE first_name LIKE '__e_a%';

-- 转义:查询姓中第二个字符为_的员工(使用\或ESCAPE)
SELECT last_name FROM employees 
WHERE last_name LIKE '_\_%';   -- 或 '_$_%' ESCAPE '$'

-- BETWEEN AND:工资在10000到20000之间(包含临界值)
SELECT * FROM employees WHERE salary BETWEEN 10000 AND 20000;

-- IN:工种编号为指定值
SELECT first_name, job_id FROM employees 
WHERE job_id IN ('IT_PROG', 'AD_VP', 'AD_PRES');

-- IS NULL / IS NOT NULL:查询没有奖金的员工
SELECT first_name, commission_pct FROM employees 
WHERE commission_pct IS NULL;

-- 安全等于(<=>):可判断NULL和普通值
SELECT * FROM employees WHERE salary <=> 12000;

五、排序查询(ORDER BY)

5.1 语法

sql 复制代码
SELECT 查询列表
FROM 表名
[WHERE 筛选条件]
ORDER BY 排序列表 [ASC | DESC];
  • ASC升序(默认),DESC降序

  • 支持单个字段、多个字段、表达式、别名、函数

5.2 案例

sql 复制代码
-- 按工资降序
SELECT * FROM employees ORDER BY salary DESC;

-- 按入职时间先后(升序)
SELECT * FROM employees WHERE department_id >= 90 ORDER BY hiredate ASC;

-- 按年薪排序(使用别名)
SELECT *, salary * 12 * (1 + IFNULL(commission_pct,0)) AS 年薪
FROM employees ORDER BY 年薪 DESC;

-- 按姓名长度排序(函数)
SELECT LENGTH(CONCAT(last_name,first_name)) 字节长度, 
       CONCAT(last_name,first_name) 姓名, salary
FROM employees ORDER BY 字节长度 DESC;

-- 多字段排序(先工资升序,再员工编号降序)
SELECT * FROM employees ORDER BY salary ASC, employee_id DESC;

六、常见函数

函数分为单行函数 (处理一行数据)和分组函数(统计多行)。

6.1 字符函数

函数 作用 示例
LENGTH(str) 返回字节数 LENGTH('hello') → 5
CONCAT(str1,str2) 拼接 CONCAT('a','b') → 'ab'
UPPER(str) / LOWER(str) 大小写转换
SUBSTR(str,pos,len) 截取子串(起始索引1) SUBSTR('abcde',2,2) → 'bc'
INSTR(str,sub) 返回子串首次位置 INSTR('abc','b') → 2
TRIM(str) 去除两端空格或指定字符 TRIM('s' FROM 'sssql') → 'sql'
LPAD(str,len,pad) / RPAD 左/右填充
REPLACE(str,old,new) 替换

6.2 数学函数

  • ROUND(number, decimals):四舍五入

  • CEIL(number) / FLOOR(number):向上/下取整

  • TRUNCATE(number, decimals):截断

  • MOD(N, M):取余

6.3 日期函数

sql 复制代码
-- 当前日期时间
SELECT NOW(), CURDATE(), CURTIME();

-- 提取年月日等
SELECT YEAR(NOW()), MONTH(NOW()), DAY(NOW()), HOUR(NOW());

-- 字符串转日期
SELECT STR_TO_DATE('4-3 1992', '%c-%d %Y');

-- 日期格式化
SELECT DATE_FORMAT(NOW(), '%y年%m月%d日');

6.4 流程控制函数

sql 复制代码
-- IF函数:如果条件成立返回value1,否则value2
SELECT IF(2<3, '大', '小');

-- CASE简单形式:等值判断
SELECT salary,
       CASE department_id
           WHEN 30 THEN salary * 1.1
           WHEN 40 THEN salary * 1.2
           ELSE salary
       END AS 新工资
FROM employees;

-- CASE搜索形式:条件判断
SELECT salary,
       CASE 
           WHEN salary > 20000 THEN 'A'
           WHEN salary > 15000 THEN 'B'
           WHEN salary > 10000 THEN 'C'
           ELSE 'D'
       END AS 工资级别
FROM employees;

七、分组函数(聚合函数)

函数 说明
SUM() 求和(数值型)
AVG() 平均值(数值型)
MAX() / MIN() 最大/最小值(任意类型)
COUNT() 计数(任意类型)

特点

  • 自动忽略NULL

  • 可与DISTINCT搭配去重

  • COUNT(*)统计行数,推荐使用

sql 复制代码
SELECT SUM(salary), AVG(salary), MAX(salary), MIN(salary), COUNT(salary)
FROM employees;

-- 去重统计
SELECT COUNT(DISTINCT salary) FROM employees;

-- 注意:与分组函数同查的字段,必须出现在GROUP BY中
SELECT AVG(salary), employee_id FROM employees;  -- 错误

八、分组查询(GROUP BY)

8.1 语法

sql 复制代码
SELECT 分组函数, 列(要求出现在GROUP BY后)
FROM 表名
[WHERE 筛选条件]        -- 分组前筛选
GROUP BY 分组列表
[HAVING 分组后筛选]     -- 分组后筛选
[ORDER BY 排序];

8.2 案例

sql 复制代码
-- 查询每个部门的平均工资
SELECT AVG(salary), department_id
FROM employees
GROUP BY department_id;

-- 查询哪个部门的员工个数大于2(分组后筛选使用HAVING)
SELECT COUNT(*), department_id
FROM employees
GROUP BY department_id
HAVING COUNT(*) > 2;

-- 查询领导编号>102的每个领导手下最低工资>5000的领导编号
SELECT MIN(salary), manager_id
FROM employees
WHERE manager_id > 102
GROUP BY manager_id
HAVING MIN(salary) > 5000;

-- 按姓名长度分组,筛选个数>5
SELECT COUNT(*) AS c, LENGTH(CONCAT(last_name,first_name)) len_name
FROM employees
GROUP BY len_name
HAVING c > 5;

-- 多字段分组(每个部门每个工种的平均工资)
SELECT AVG(salary), department_id, job_id
FROM employees
GROUP BY job_id, department_id;

-- 添加排序
SELECT AVG(salary) AS a, department_id, job_id
FROM employees
WHERE department_id IS NOT NULL
GROUP BY job_id, department_id
HAVING a > 10000
ORDER BY a DESC;

九、连接查询(多表查询)

当查询涉及多个表时,需要通过连接条件将表关联起来。

9.1 SQL99标准语法

sql 复制代码
SELECT 查询列表
FROM 表1 别名
[INNER | LEFT | RIGHT] JOIN 表2 别名
ON 连接条件
[WHERE 筛选]
[GROUP BY ...]
[HAVING ...]
[ORDER BY ...];

9.2 内连接(INNER JOIN)

只返回两个表中匹配的行。

sql 复制代码
-- 等值连接:员工名和部门名
SELECT first_name, department_name
FROM employees e
INNER JOIN departments d ON e.department_id = d.department_id;

-- 三表连接:员工名、部门名、工种名
SELECT first_name, department_name, job_title
FROM employees e
INNER JOIN departments d ON e.department_id = d.department_id
INNER JOIN jobs j ON e.job_id = j.job_id;

-- 非等值连接:员工的工资级别
SELECT salary, grade_level
FROM employees e
INNER JOIN job_grades g ON e.salary BETWEEN g.lowest_sal AND g.highest_sal;

-- 自连接:员工与其上级
SELECT e.first_name 员工, m.first_name 上级
FROM employees e
INNER JOIN employees m ON e.manager_id = m.employee_id;

9.3 外连接(LEFT / RIGHT JOIN)

返回主表全部记录,从表没有匹配则显示NULL。左外连接左表为主,右外连接右表为主。

sql 复制代码
-- 查询没有男朋友的女神(左外)
SELECT b.name, bo.*
FROM beauty b
LEFT OUTER JOIN boys bo ON b.boyfriend_id = bo.id
WHERE bo.id IS NULL;

-- 查询没有员工的部门(右外)
SELECT d.*, e.employee_id
FROM employees e
RIGHT OUTER JOIN departments d ON d.department_id = e.department_id
WHERE e.employee_id IS NULL;

9.4 交叉连接(CROSS JOIN)

即笛卡尔积,一般很少单独使用。

sql 复制代码
SELECT b.*, bo.* FROM beauty b CROSS JOIN boys bo;

十、子查询

子查询是嵌套在其他SQL语句中的SELECT查询,分为标量子查询(一行一列)、列子查询(一列多行)、行子查询(一行多列)和表子查询(多行多列)。

10.1 WHERE / HAVING 后面的子查询

sql 复制代码
-- 标量子查询:查询工资比Abel高的人
SELECT * FROM employees
WHERE salary > (SELECT salary FROM employees WHERE last_name = 'Abel');

-- 列子查询(IN / ANY / ALL)
-- 查询location_id为1400或1700的部门的所有员工
SELECT first_name FROM employees
WHERE department_id IN (
    SELECT DISTINCT department_id FROM departments
    WHERE location_id IN (1400, 1700)
);

-- ANY:比IT_PROG工种任一工资低的员工
SELECT first_name, salary
FROM employees
WHERE salary < ANY (SELECT DISTINCT salary FROM employees WHERE job_id = 'IT_PROG');

-- ALL:比IT_PROG所有工资都低的员工
SELECT first_name, salary
FROM employees
WHERE salary < ALL (SELECT DISTINCT salary FROM employees WHERE job_id = 'IT_PROG');

-- 行子查询:查询员工编号最小且工资最高的员工
SELECT * FROM employees
WHERE (employee_id, salary) = (
    SELECT MIN(employee_id), MAX(salary) FROM employees
);

10.2 SELECT 后面的子查询(标量)

sql 复制代码
-- 查询每个部门的员工个数
SELECT d.*, (SELECT COUNT(*) FROM employees e WHERE e.department_id = d.department_id) 个数
FROM departments d;

10.3 FROM 后面的子查询(表子查询)

sql 复制代码
-- 查询每个部门平均工资的工资等级
SELECT ag_dep.*, g.grade_level
FROM (
    SELECT AVG(salary) ag, department_id
    FROM employees
    GROUP BY department_id
) ag_dep
INNER JOIN job_grades g ON ag_dep.ag BETWEEN g.lowest_sal AND g.highest_sal;

10.4 EXISTS 后面的相关子查询

sql 复制代码
-- 查询有员工的部门
SELECT department_name
FROM departments d
WHERE EXISTS (SELECT * FROM employees e WHERE d.department_id = e.department_id);

十一、分页查询(LIMIT)

用于分页显示,必须放在SQL语句最后。

sql 复制代码
SELECT 查询列表
FROM 表名
...
LIMIT [offset,] size;
-- offset:起始索引(从0开始),size:条目数

-- 查询前5条
SELECT * FROM employees LIMIT 5;      -- 等价于 LIMIT 0,5

-- 查询第11~25条(第3页,每页10条)
SELECT * FROM employees LIMIT 10, 15;

-- 公式:(page-1)*size, size

十二、联合查询(UNION)

将多个查询结果合并成一个结果集,要求列数和类型一致。UNION默认去重,UNION ALL保留重复。

sql 复制代码
-- 查询部门编号>90或邮箱包含a的员工
SELECT * FROM employees WHERE email LIKE '%a%'
UNION
SELECT * FROM employees WHERE department_id > 90;

十三、总结

知识点 核心语法 适用场景
基础查询 SELECT ... FROM ... 简单数据提取
条件查询 WHERE + 条件 数据筛选
排序查询 ORDER BY 结果排序
常用函数 字符、数学、日期、流程控制 数据变换
分组函数 SUM/AVG/MAX/MIN/COUNT 统计计算
分组查询 GROUP BY + HAVING 分组统计
连接查询 INNER/LEFT/RIGHT JOIN 多表关联
子查询 嵌套SELECT 复杂条件依赖
分页查询 LIMIT 数据分页展示
联合查询 UNION 合并结果集
相关推荐
大模型码小白1 小时前
ChatGPT 的回答也能被结构化抓取?AI Bot Scraper 对比测评
服务器·开发语言·数据库·人工智能·spring·chatgpt
天涯柳絮1 小时前
SQL注入
数据库·sql·网络安全
API快乐传递者1 小时前
淘宝海外商品详情接口实战指南:从全球开放平台到跨境铺货的全链路方案
java·前端·数据库
tachibana22 小时前
复杂的 RAG 范式
数据库·人工智能·ai·大模型·agent
2501_931803752 小时前
MySQL 增删改查详解
数据库
Sagittarius_A*2 小时前
【LitCTF2026】lit_ezsql
android·java·数据库
ttwuai3 小时前
Go 后台初始化 MySQL 脚本失败怎么办?先查 DDL 还是生成配置
开发语言·mysql·golang
未秃头的程序猿3 小时前
分库分表一年后,我复盘了当时最该想清楚的三件事
java·数据库·后端
心易行者3 小时前
用html在线运行做数据可视化大屏,5个实战场景从入门到上线
大数据·前端·数据库·人工智能·python