Node系列 · 数据库:单表查询

发布时间:2026/8/23 7:53:02
Node系列 · 数据库:单表查询 Node系列 · 数据库单表查询SELECT是 SQL 里最常用也最容易写错的语句。本章从语法骨架出发覆盖 WHERE 过滤、ORDER BY 排序、LIMIT 分页、聚合函数——单表查询的所有核心场景。一、SELECT 完整语法骨架SELECT[DISTINCT]列[,列2,...]FROM表名[WHERE过滤条件][GROUPBY分组列][HAVING分组过滤][ORDERBY排序列[ASC|DESC]][LIMIT偏移,行数];最小可执行形式SELECT 1;返回常数。二、基本查询2.1 查询所有列SELECT*FROMstudent;::: warning生产项目避免SELECT *列数变化会破坏上层代码ORM 映射、JSON 序列化无用字段浪费网络和内存失去覆盖索引优化机会:::2.2 查询指定列SELECTid,stuno,nameFROMstudent;2.3 列别名ASSELECTidAS学号ID,stunoAS学号,nameAS姓名FROMstudent;别名让结果集更易读也方便应用层取值。2.4 去重DISTINCT-- 查询所有不同的班级 IDSELECTDISTINCTclass_idFROMstudent;::: tipDISTINCT作用于所有选中的列不是单个列。SELECT DISTINCT a, b FROM t表示ab组合去重。:::三、WHERE 过滤WHERE 是查询的核心决定返回哪些行。3.1 比较运算符运算符含义示例等于WHERE sex b1或!不等于WHERE class_id 3大小比较WHERE age 18BETWEEN ... AND ...范围含端点WHERE age BETWEEN 18 AND 30IN (...)在列表中WHERE class_id IN (1, 3, 5)NOT IN (...)不在列表中WHERE class_id NOT IN (2, 4)IS NULL为空WHERE phone IS NULLIS NOT NULL非空WHERE phone IS NOT NULLLIKE模糊匹配WHERE name LIKE 张%::: warningNULL不能用比较——必须用IS NULL或IS NOT NULL。 NULL永远返回 NULL不是 true。:::3.2 逻辑运算符-- AND全部满足SELECT*FROMstudentWHEREsexb1ANDclass_id3;-- OR任一满足SELECT*FROMstudentWHEREclass_id1ORclass_id2;-- NOT取反SELECT*FROMstudentWHERENOTclass_id3;AND优先级高于OR复杂的条件加括号更清晰WHERE(class_id1ORclass_id2)ANDsexb1;3.3 模糊查询 LIKE-- % 匹配任意长度字符含 0 个WHEREnameLIKE张%-- 张三、张三丰WHEREnameLIKE%三%-- 包含三WHEREnameLIKE%张-- 以张结尾-- _ 匹配单个字符WHEREnameLIKE张_-- 张三、张四不含张三丰WHEREnameLIKE张__-- 张三丰三个字::: warningLIKE %xxx%全表扫描无法走索引。数据量大时考虑全文索引FULLTEXT INDEX引入 Elasticsearch 做搜索反范式存储是否包含某关键词标记:::四、ORDER BY 排序-- 单列升序默认SELECT*FROMstudentORDERBYid;-- 单列降序SELECT*FROMstudentORDERBYcreated_atDESC;-- 多列排序先按 class_id 升序同 class_id 内按 score 降序SELECT*FROMstudentORDERBYclass_idASC,scoreDESC;::: tipORDER BY 的字段要么是索引列要么接受 filesort 性能开销。生产大表分页查询用延迟关联或游标分页避免深翻页。:::五、LIMIT 分页-- 前 10 条SELECT*FROMstudentLIMIT10;-- 第 11-20 条跳过 10 条取 10 条SELECT*FROMstudentLIMIT10,10;等价写法SELECT*FROMstudentLIMIT10OFFSET10;深翻页性能问题-- ❌ 越翻越慢offset 100000 时扫 100010 行SELECT*FROMstudentLIMIT100000,10;::: warning深翻页是性能大坑。LIMIT 100000, 10会扫描前 100010 行但只返回 10 行——资源浪费。生产方案游标分页推荐WHERE id last_id LIMIT 10索引直接定位记住最大 id业务上让用户上一页 / 下一页而不是任意跳页:::-- ✅ 游标分页每次只取当前 id 之后的 10 条SELECT*FROMstudentWHEREid1000ORDERBYidLIMIT10;六、聚合函数聚合函数对一组行做计算返回单值函数含义示例COUNT(*)总行数SELECT COUNT(*) FROM student;COUNT(col)col 非空的行数SELECT COUNT(phone) FROM student;SUM(col)求和SELECT SUM(score) FROM score;AVG(col)平均SELECT AVG(score) FROM score;MAX(col)最大SELECT MAX(score) FROM score;MIN(col)最小SELECT MIN(score) FROM score;::: tipCOUNT(*)和COUNT(col)不同COUNT(*)统计所有行包括 NULL 列COUNT(col)统计 col 非 NULL 的行:::七、GROUP BY 分组按列分组配合聚合函数用-- 每个班级的学生人数SELECTclass_id,COUNT(*)AScountFROMstudentGROUPBYclass_id;HAVING 过滤分组结果-- 找出学生人数超过 30 的班级SELECTclass_id,COUNT(*)AScountFROMstudentGROUPBYclass_idHAVINGcount30ORDERBYcountDESC;子句作用对象何时用WHERE行原始数据分组前过滤HAVING组聚合后分组后过滤::: warningHAVING 的字段必须是聚合函数或 GROUP BY 字段。HAVING name 张三会报错或产生不可预期结果。:::八、Node 端查询实战const mysql require(mysql2/promise); const pool mysql.createPool({ /* config */ }); // 查询第 11-20 条学生 const [rows] await pool.execute( SELECT id, stuno, name, class_id FROM student WHERE class_id ? ORDER BY id LIMIT ?, ?, [3, 10, 10] ); console.log(rows); // [{ id: 11, stuno: ..., name: ..., class_id: 3 }, ...] // 聚合每个班人数 const [stats] await pool.execute( SELECT class_id, COUNT(*) AS count FROM student GROUP BY class_id HAVING count ?, [30] );九、最佳实践场景推荐列选择永远列名而非*WHERE 索引高频过滤字段建索引模糊查询前缀匹配 (LIKE xxx%) 走索引%xxx%不走排序小数据集 ORDER BY大数据集游标分页分页浅页LIMIT N深页游标分页WHERE id ?聚合 过滤WHERE过滤行 →GROUP BY→HAVING过滤组NULL 判断IS NULL/IS NOT NULL不是 NULL十、小结SELECT 完整语法SELECT ... FROM ... WHERE ... GROUP BY ... HAVING ... ORDER BY ... LIMIT ...WHERE 过滤原始行HAVING 过滤聚合后的组比较运算BETWEENINIS NULLLIKE逻辑运算ANDORNOT复杂条件加括号排序默认升序LIMIT N, M跳 N 行取 M 行深翻页用游标分页WHERE id ?替代 offset聚合函数COUNT/SUM/AVG/MAX/MIN