在数据驱动的时代,数据库是存储和管理信息的核心,而与数据库“对话”的语言——SQL(Structured Query Language),则是每一位数据从业者和开发者必须掌握的技能,在SQL的所有命令中,SELECT语句无疑是使用频率最高、最重要的一环,它就像是通往数据宝库的大门,掌握如何编写SELECT语句,就等于掌握了从海量数据中精准提取所需信息的钥匙。

SELECT语句的基础结构
一个最基础的SELECT语句,其使命是“从某个表中选取某些列”,它的语法结构非常直观:
SELECT column1, column2, ... FROM table_name;
这里的 SELECT 关键字指定了你想要查询的列名,多个列名之间用逗号隔开。FROM 关键字则指明了这些数据来源于哪一张表,如果你想选取表中的所有列,可以使用星号 作为通配符,这是一种快速查看表结构的方式,但在生产环境中,明确指定列名是更好的实践,因为它能提高查询效率并使代码更易读。
我们有一个名为 students 的表,包含 id, name, age 和 grade 四个列,要查询所有学生的姓名和年龄,语句如下:
SELECT name, age FROM students;
使用 WHERE 子句进行数据筛选
现实场景中,我们很少需要获取表中的全部数据,更多时候,我们需要根据特定条件进行筛选,这时,WHERE 子句就派上了用场,它紧跟在 FROM 子句之后,用于设定过滤条件,只有满足条件的行才会被返回。
WHERE 子句支持多种操作符,以构建丰富的筛选逻辑:
- 比较操作符: ,
>,<,>=,<=, (或<>) - 逻辑操作符:
AND,OR,NOT - 范围操作符:
BETWEEN ... AND ... - 列表操作符:
IN (value1, value2, ...) - 模糊匹配:
LIKE,配合通配符 (匹配任意多个字符) 和_(匹配单个字符)
查询年龄大于18岁且年级为“二年级”的所有学生信息:
SELECT * FROM students WHERE age > 18 AND grade = '二年级';
查询姓名以“张”开头的学生:
SELECT name, grade FROM students WHERE name LIKE '张%';
使用 ORDER BY 对结果排序
获取数据后,我们通常希望它能按照某种规则排列,比如按成绩高低或年龄大小。ORDER BY 子句就是用来实现这一功能的,它默认按升序(ASC)排列,也可以指定降序(DESC)。

按学生年龄从小到大排序:
SELECT name, age FROM students ORDER BY age ASC;
如果需要先按年级排序,同年级的学生再按年龄从大到小排序,可以指定多个排序列:
SELECT name, grade, age FROM students ORDER BY grade, age DESC;
使用 LIMIT 限制返回行数
当表中的数据量非常大时,一次性返回所有结果不仅消耗资源,也可能超出我们的处理需求。LIMIT 子句可以限制查询结果返回的行数,这在实现分页功能时尤其有用。
只查询年龄最大的3名学生:
SELECT name, age FROM students ORDER BY age DESC LIMIT 3;
聚合与分组:GROUP BY 与 HAVING
SELECT 语句的强大之处还在于其数据分析能力,聚合函数(如 COUNT(), SUM(), AVG(), MAX(), MIN())可以对一组数据进行计算,而 GROUP BY 子句则能将具有相同值的行分组,然后对每个组应用聚合函数。
计算每个年级的学生人数:
SELECT grade, COUNT(id) AS student_count FROM students GROUP BY grade;
这里,AS 关键字用于给计算出的列起一个别名,使结果更具可读性。
如果需要对分组后的结果进行筛选,WHERE 子句就无能为力了,因为它作用于原始行,这时需要使用 HAVING 子句,查询学生人数超过20人的年级:

SELECT grade, COUNT(id) AS student_count FROM students GROUP BY grade HAVING COUNT(id) > 20;
连接多表查询 (JOIN)
在关系型数据库中,数据通常被分散存储在多个表中以减少冗余。JOIN 操作允许我们根据相关列将这些表组合起来,进行联合查询,最常用的是 INNER JOIN(内连接)和 LEFT JOIN(左连接)。
INNER JOIN: 只返回两个表中连接列相匹配的行。LEFT JOIN: 返回左表的所有行,以及右表中与左表匹配的行,如果右表中没有匹配项,则结果为 NULL。
假设我们还有一个 courses 表(包含 course_id, course_name)和一个 enrollments 表(包含 student_id, course_id),要查询每个学生选修的课程名称,就需要连接多个表。
| 连接类型 | 描述 |
|---|---|
INNER JOIN |
获取两个表中字段匹配关系的记录。 |
LEFT JOIN |
获取左表所有记录,即使右表没有对应匹配的记录。 |
一个综合了以上多个子句的复杂查询示例如下:查询“二年级”学生中,选修了“数学”课程的人数,并按人数降序排列,只显示结果最多的前5名。
SELECT
s.name,
COUNT(e.course_id) AS enrolled_courses
FROM
students s
INNER JOIN
enrollments e ON s.id = e.student_id
INNER JOIN
courses c ON e.course_id = c.course_id
WHERE
s.grade = '二年级' AND c.course_name = '数学'
GROUP BY
s.id, s.name
HAVING
COUNT(e.course_id) > 0
ORDER BY
enrolled_courses DESC
LIMIT 5;
相关问答FAQs
问题1:WHERE 和 HAVING 都可以用来筛选,它们有什么根本区别?
解答: WHERE 和 HAVING 的主要区别在于它们作用的对象和执行的时机不同。WHERE 子句在数据分组之前对原始表中的行进行过滤,它不能使用聚合函数,而 HAVING 子句在数据分组之后对由 GROUP BY 生成的结果组进行过滤,它通常与聚合函数一起使用,用来筛选满足特定聚合条件的组。WHERE 过滤行,HAVING 过滤组。
*问题2:在 SELECT 语句中使用 `` 和明确列出所有列名,哪种方式更好?**
解答: 明确列出所有列名是更推荐的做法,它具有更高的性能,数据库在解析 SELECT * 时需要额外的步骤去查询数据字典来确定所有列名,而明确列名则可以直接执行,它提高了代码的可读性和可维护性,其他开发者可以一目了然地知道查询具体返回了哪些字段,它能避免因表结构变更(如增加、删除或重排列)而导致应用程序出现意外错误。SELECT * 主要适用于临时的、交互式的数据探索场景。