SQL 语法速查手册:前端开发者的学习笔记

作者:copyer_xyf日期:2025/12/4

最近在写 Next.js + Supabase 的一个练手项目,突然又接触到了 SQL(虽然 Supabase 是通过类似 ORM 来操作数据库的),突然感觉 SQL 语法又陌生了。

真是学了不用,等于白学。

之前写过一篇一个前端小白,学习 SQL 语句,现在回头看,自己都看懵了,真的尬住了。

算了,重新整理一遍吧,并通过一个实战案例来巩固基础。

邂逅

SQL: Structured Query Language,称为结构化查询语句,简称 SQL。

SQL 编写规范

  1. 关键词建议使用大写,比如 CREATETABLE(小写也可以,但大写更规范)
  2. 语句末尾加分号
  3. 如果表名或字段名是 SQL 关键词,用反引号 ``` 包裹
  4. 表名用单数,字段名用蛇形命名(下划线分隔,如 user_id

SQL 分类

分类描述常见命令/关键字
DDL定义修改数据库结构(库、表、索引、视图等)CREATE、DROP、ALTER、TRUNCATE、RENAME
DML表中的记录进行增、删、改INSERT、UPDATE、DELETE、MERGE
DQL表中的记录进行查询SELECT(以及配套子句 WHERE、GROUP BY、ORDER BY、JOIN 等)
DCL权限访问控制(授予、回收)GRANT、REVOKE
  • DDL(Data Definition Language):数据定义语言,-----> 管"结构"
  • DML(Data Manipulation Language):数据操作语言,-----> 管"改数据"
  • DQL(Data Query Language):数据查询语言,-----> 管"查数据"
  • DCL(Data Control Language):数据控制语言,-----> 管"权限"

为了阅读方便,下面的 SQL 语句都用小写,但实际开发中推荐关键词用大写

SQL 类型

实际开发中常用的类型:

  • int:存储整数
  • varchar(100): 存储变长字符串,可以指定长度
  • char:定长字符串,不够的自动在末尾填充空格
  • double:存储浮点数
  • date:存储日期 2023-05-27
  • time:存储时间 10:13
  • datetime:存储日期和时间 2023-05-27 10:13
  • timestamp:存储日期时间的,但是范围小一点,而且会转为中央时区 UTC 的时间来存储
  • text:存储长文本

SQL 设计了这么多数据类型,一是为了存储更丰富的信息,二是为了节省存储空间。不常用的类型,用到时再查就行

表约束

  • primary key: 主键
    • 主键是表中唯一的索引
    • 必须是 not null; 如果没有设置,mysql 也会自动设置;
    • 联合主键,多个字段合成的主键
    • 尽量不要使用业务主键
  • unique: 唯一;除了主键以外,针对某些字段,也是唯一的。
  • not null: 字段不能为空
  • default: 默认值
  • auto_increment: 自动增长;
  • foreign key: 外键,与其他表的字段关联
1create table if not exists yyy(
2  id int primary key,
3  `name` varchar(50) unique not null,
4  age int default 18,
5  num int auto_increment,
6  homeId int, -- 外键
7  foreign key (homeId) references home(id) -- 关联 home 表的 id
8)
9

DDL(管结构)

库操作

1-- 查看所有数据库
2show databases;
3
4-- 使用数据库 co_blog
5use co_blog;
6
7-- 查看选择的数据库
8select database();
9
10-- 创建数据库
11create database if not exists co_blog; -- 先判断 co_blog 库是否存在,不存在则创建
12create database co_blog; -- 若存在,会报错;
13
14-- 删除数据库
15drop database if exists co_blog;
16drop database co_blog; -- 数据库不存在,会报错
17use co_blog; -- 使用 co_blog 库,后续表操作都在这个库下
18

表操作

1-- 查看库中所有的表
2show tables;
3
4-- 创建一张表
5create table if not exists user(
6  id int primary key auto_increment,
7  name varchar(10),
8  age int
9);
10
11-- 查看表结构
12desc user; -- 显示表的字段定义信息
13
14-- 删除 user 
15drop table if exists user;
16
17-- 修改 user 
18alter table user rename to users;             -- 修改表名 user 变成 users
19alter table user add height int;              -- 添加 height 字段,为 int 类型;
20alter table user change height newHeight int; -- height 改为 newHeight, 类型也可以重新定义
21alter table user modify height bigint;        -- 类型 int 变为 bigint
22alter table user drop height;                 -- 删除 height 字段
23

DML(管改数据)

增删改

插入数据

1-- 插入单条数据
2insert into user(username, emails) values('admin', '[email protected]');
3
4-- 插入多条数据
5insert into user(username, emails)
6  values
7    ('admin', '[email protected]'),
8    ('admin2', '[email protected]'),
9    ('admin3', '[email protected]');
10

更新数据

1update `user` set name = '张三1', age = 19 where id = 1;
2

删除数据

1delete from `user` where id = 1;
2

DQL(管查询)

查询语句的执行顺序:先筛选数据,再分组过滤,然后排序,最后分页。

1SELECT *
2  FROM table_name
3  WHERE    xxx
4  GROUP BY xxx
5  HAVING   xxx
6  ORDER BY xxx
7  LIMIT    xxx;
8

取别名 as

1-- 查询所有字段
2select * from user;
3
4-- 查询指定字段并取别名
5select name, age as c_age from user;
6
7-- 别名的作用:多表查询时避免字段名冲突
8

比较运算符

1-- 比较运算符:>、<、=、!=、>=、<=
2
3select * from users where age > 20;  -- 年龄大于 20
4select * from users where age != 20; -- 年龄不等于 20
5

逻辑运算符

1-- name  张三,且 age 大于 10
2select * from users where name = '张三' and age > 10;
3select * from users where name = '张三' && age > 10;
4
5-- name  张三,或者 age 大于 10
6select * from users where name = '张三' or age > 10;
7select * from users where name = '张三' || age > 10;
8
9-- age  10  20 岁间的
10select * from users where age between 10 and 20;
11select * from users where age >=10 && age <= 20;
12
13-- age  10  20 的(in 的相反是 not in)
14select * from users where age in (10, 20);
15select * from users where age = 10 or age = 20;
16

模糊搜索 like

  • %: 任意多个字符
  • _: 任意一个字符
1select * from users where name like 't%';   --  t 开头
2select * from users where name like '%t%';   -- 包含 t
3select * from users where name like '_t%';   -- 第二个字符是 t
4

排序 order by

  • asc:升序(默认,可省略)
  • desc:降序
1select * from users order by age asc;  -- 年龄升序
2select * from users order by age desc; -- 年龄降序
3

限制分页 limit

  • limit 数据条数 offset 偏移量 (推荐)
  • limit 偏移量,数据条数 (不推荐)
1-- limit 数据条数 offset 偏移量 (推荐)
2select * from users limit 30 offset 10;
3
4-- limit 偏移量,数据条数 (不推荐)
5select * from users limit 10, 30;
6
7-- 前端格式
8const pages = {
9  current: 3,
10  pageSize: 10,
11};
12
13-- 后端查询
14const offsetNum = (pages.current - 1) * pages.pageSize;
15const limitNum = pages.pageSize;
16
17-- sql
18const sql = [`select * from users limit ${limitNum} offset ${offsetNum}`](https://xplanc.org/primers/document/zh/10.Bash/90.%E5%B8%AE%E5%8A%A9%E6%89%8B%E5%86%8C/EX.users.md);
19

聚合函数

聚合函数: 先收集到一起,然后对收集的结果进行操作。(看成一组)

  • avg:平均值
  • max:最大值
  • min:最小值
  • sum:求和
  • count:计数(统计行数)
1-- 平均值
2select avg(age) as avg_age from users;
3
4-- 计算人数
5select count(*) as count from users;
6
7-- 最大值
8select max(age) as maxAge from users;
9
10-- 求和
11select sum(age) as sumAge from users;
12

SQL 还有很多内置函数:

  • 聚合函数:avg、count、sum、min、max
  • 字符串函数:concat、substr、length、upper、lower
  • 数值函数:round、ceil、floor、abs、mod
  • 日期函数:year、month、day、date、time
  • 条件函数:if、case
  • 系统函数:version、database、user
  • 类型转换函数:convert、cast、date_format、str_to_date
  • 其他函数:nullif、coalesce、greatest、least

实际开发中,聚合函数最常用的是统计分组,其他函数用到时再查就行。

分组 group by

聚合函数看成一组;有时需要进行分组,然后进行操作。

使用建议:group by 一定要配合聚合函数使用,不然分组没有意义(除非整张表都看成一个聚合函数,就不需要使用 group by)。

1-- 统计男女个数
2select sex, count(*) as num from users group by sex;
3

分组筛选条件 having

在进行分组的时候,有时也需要过滤条件。

但是 group bywhere 不能一起使用,语法报错。取而代之的是 having

1-- 根据 sex 性别进行分组,然后筛选出 count 大于 2
2select count(*) as count, sex from users group by sex having count > 2;
3

去重 distinct

1select distinct age from users;
2

多表

外键添加

创建表时添加外键:

1create table if not exists users(
2  id int primary key,
3  name varchar(255) not null,
4  role_id varchar(255) not null,
5  foreign key (role_id) references role(id)
6);
7

已有表添加外键:

1alter table users add role_id int;
2alter table users add foreign key (role_id) references role(id);
3
外键删除(更新)

表之间有关联后,不能直接删除或更新,否则会影响关联表。需要设置级联操作:要么一起更新,要么一起删除。

1-- 一起更新一起删除
2alter table user add foreign key (role_id) references role(id) ON DELETE CASCADE ON UPDATE CASCADE;
3

删除外键

1show create table users; -- 查看外键名称
2alter table users drop foreign key users_ibfk_1;
3

如果存在多个外键:

  • users_ibfk_1
  • users_ibfk_2
  • ...

重新绑定外键

1alter table users add foreign key (role_id) references role(id)
2                                           on delete cascade
3                                           on update cascade;
4
多表查询

多表查询时,表之间必须有联系(通常是外键)。

1select * from users, team;
2

这样查询会产生大量无用数据,因为这是笛卡尔积(每行数据都会组合)。

表连接

左右指的是以哪张表为主,展示该表的所有数据。

1from 左表 [left/right] join 右表
2

其实 left joinright join 可以等价,换一下表的顺序就行

左连接

1-- 左连接 LEFT JOIN. ON 连接条件
2select * from user left join role
3                    on user.role_id = role.id;
4
5-- 筛选出 role_id 不为 null 的(左连接以左表为主,如果右表没有匹配数据,右表字段为 null)
6select * from user left join role
7                           ON user.role_id = role.id
8                           WHERE role_id IS NOT NULL;
9

内连接

内连接只返回两表都匹配的数据,不以哪张表为主。

1select * from user join role on user.role_id = role.id;
2

针对多对多的查询出现时,就是多次采用连接即可。

子查询

SQL 支持嵌套查询,也就是在查询中嵌套另一个查询(子查询)。

1-- 查询年龄最大的人
2-- 方式一:分两步
3select max(age) from user;  -- 先查出最大年龄
4select name, age from user where age = xxx; -- 再根据年龄查数据
5
6-- 方式二:用子查询(推荐)
7select name, age from user where age = (select max(age) from user);
8

子查询还有个特有的语法 EXISTSNOT EXISTS

1-- 查询所有有角色的用户
2select name from user where EXISTS (select * from role where role.id = user.role_id);
3
4-- 查询所有没有角色的用户
5select name from user where NOT EXISTS (select * from role where role.id = user.role_id);
6

子查询可以在 selectinsertupdatedelete 中使用。

事务

修改多个关联表时,必须使用事务,保证要么全部成功,要么全部失败(回滚)。

1start transaction;    -- 开启事务
2insert into role(id, name) VALUES (4, '群众');
3insert into user(name, age, sex) VALUES ('james', 18, 4);
4
5-- 提交事务,执行之后就不能 rollback 
6commit;
7-- 回滚事务
8rollback;
9

如果想回滚到某个中间点,可以使用 savepoint 设置保存点。

savepoint a1;:回滚到 a1 时,a1 之前的操作保留,a1 之后的操作被撤销。

1start transaction;  -- 开启事务
2savepoint a1;  -- savepoint 这一刻前面的依旧活着,这一刻后面的都被抹除
3insert into role(id, name) VALUES (4, '群众');
4savepoint a2;
5insert into user(name, age, sex) VALUES ('james', 18, 4);
6savepoint a3;
7
8rollback to a2; -- 回滚到 a2 的那一刻
9

事务隔离级别(有四种级别,比较复杂,自己也有点懵,一般用默认的就行)

1select @@transaction_isolation; -- 查看隔离级别
2

DCL(管权限)

权限管理在实际开发中较少用到,等遇到时再补充(其实自己也不知道)。

案例演示(Blog 的 CRUD)

用一个博客系统的增删改查来演示 SQL 的实际应用。

先设计表结构

1CREATE TABLE `user`(
2  id INT NOT NULL PRIMARY KEY AUTO_INCREMENT COMMENT '主键',
3  username VARCHAR(100) UNIQUE NOT NULL COMMENT '用户名',
4  password VARCHAR(100) NOT NULL COMMENT '密码',
5  email VARCHAR(100) UNIQUE NOT NULL COMMENT '邮箱',
6  created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间'
7);
8
9-- 标签表
10CREATE TABLE `tag`(
11  id int NOT NULL PRIMARY KEY AUTO_INCREMENT COMMENT '主键',
12  name varchar(100) UNIQUE NOT NULL COMMENT '标签名',
13  article_count int DEFAULT 0 COMMENT '文章数量',
14  created_at datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间'
15);
16
17-- 文章表
18CREATE TABLE [`article`](https://xplanc.org/primers/document/zh/03.HTML/EX.HTML%20%E5%85%83%E7%B4%A0/EX.article.md)(
19  id int NOT NULL PRIMARY KEY AUTO_INCREMENT COMMENT '主键',
20  title varchar(100) NOT NULL COMMENT '标题',
21  content text NOT NULL COMMENT '内容',
22  user_id int NOT NULL COMMENT '作者id',
23  like_count int NOT NULL DEFAULT '0' COMMENT '点赞数',
24  comment_count int NOT NULL DEFAULT '0' COMMENT '评论数',
25  created_at datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
26  FOREIGN KEY (user_id) REFERENCES user(id) ON DELETE CASCADE ON UPDATE CASCADE
27);
28
29-- 文章标签表(中间表的级联方式要设置为 CASCADE,这个是固定的)
30CREATE TABLE `article_tag`(
31  id int NOT NULL PRIMARY KEY AUTO_INCREMENT COMMENT '主键',
32  article_id int NOT NULL COMMENT '文章id',
33  tag_id int NOT NULL COMMENT '标签id',
34  FOREIGN KEY (article_id) REFERENCES article(id) ON DELETE CASCADE ON UPDATE CASCADE,
35  FOREIGN KEY (tag_id) REFERENCES tag(id) ON DELETE CASCADE ON UPDATE CASCADE
36);
37
38-- 点赞表
39CREATE TABLE `like`(
40  id int NOT NULL PRIMARY KEY AUTO_INCREMENT COMMENT '主键',
41  article_id int NOT NULL COMMENT '文章id',
42  user_id int NOT NULL COMMENT '用户id',
43  created_at datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
44  FOREIGN KEY (article_id) REFERENCES article(id) ON DELETE CASCADE ON UPDATE CASCADE,
45  FOREIGN KEY (user_id) REFERENCES user(id) ON DELETE CASCADE ON UPDATE CASCADE
46)
47
48-- 评论表
49CREATE TABLE `comment`(
50  id int NOT NULL PRIMARY KEY AUTO_INCREMENT COMMENT '主键',
51  article_id int NOT NULL COMMENT '文章id',
52  user_id int NOT NULL COMMENT '用户id',
53  target_user_id int NOT NULL COMMENT '被回复人id',
54  content varchar(255) NOT NULL COMMENT '内容',
55  parent_id int DEFAULT 0 COMMENT '父级id',
56  created_at datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
57  FOREIGN KEY (article_id) REFERENCES article(id) ON DELETE CASCADE ON UPDATE CASCADE,
58  FOREIGN KEY (user_id) REFERENCES user(id) ON DELETE CASCADE ON UPDATE CASCADE
59)
60

ER 图为:

用户和标签的 CRUD

以用户表(user)为例,标签表(tag)同理

1-- 创建用户
2insert into user(username, password, email) VALUES ('admin', '123456', '[email protected]');
3
4-- 查询用户
5select * from user;
6
7-- 更新用户
8update user set password = '123456' where id = 1;
9
10-- 删除用户
11delete from user where id = 1;
12

文章的 CRUD

创建文章

1start transaction;
2
3-- 创建文章,获取文章 id(代码中获取 id 后执行下一步)
4insert into article(title, content, user_id, like_count, comment_count) VALUES ('标题', '内容', 1, 0, 0);
5
6-- 遍历 tagIds,与文章 id 一起插入中间表(代码中遍历 tagIds,组装 SQL)
7insert into article_tag(article_id, tag_id)
8  VALUES
9    ('文章id', '标签id1'),
10    ('文章id', '标签id2');
11
12-- 更新 tag 表的文章数量
13update tag set article_count = article_count + 1 where id in ('标签id1', '标签id2');
14
15commit;
16

删除文章

1delete from article where id = '文章id';
2-- 因为采用的是 cascade,所以 article_tag 等会自动删除
3

查询文章列表

1SELECT id,
2       title,
3       user_id,
4       like_count,
5       comment_count,
6       created_at
7FROM   article
8ORDER  BY created_at DESC
9LIMIT  10 OFFSET 0;
10
11-- 分页查询,按创建时间倒序
12-- like_count  comment_count 采用计数器冗余方案,简单性能好,但可能数据不一致
13

更新文章

1-- 1. 开启事务
2START TRANSACTION;
3
4-- 2. 更新文章表
5UPDATE article
6  SET
7    title = '新标题',
8    content = '新内容'
9WHERE id = 123 AND article.user_id = '用户id';
10
11-- 3. 根据文章 id,在中间表 article_tag 中拿到旧标签(a,b), 然后根据用户传递的新标签(a,c), 找出删除的标签(b),然后再 tag 表中更新文章数量(-1)
12UPDATE tag
13SET    article_count = article_count - 1
14WHERE  id IN (
15        SELECT tag_id
16        FROM   article_tag
17        WHERE  article_id = '文章id'
18          AND  tag_id NOT IN ('新标签id1', '新标签id2')   -- 新标签列表
19      );
20
21-- 4. 查找旧标签,然后使用 in  not in 交集,找出新增的标签,然后在 tag 表中更新文章数量(+1)
22UPDATE tag
23SET    article_count = article_count + 1
24WHERE  id IN ('新标签id1', '新标签id2')
25  AND  id NOT IN (SELECT tag_id FROM article_tag WHERE article_id = '文章id');
26
27-- 5. 更新中间表:先删除文章的所有标签,再插入新标签
28DELETE FROM article_tag WHERE article_id = '文章id';
29INSERT INTO article_tag(article_id, tag_id) VALUES
30('文章id', '新标签id1'),
31('文章id', '新标签id2');
32
33COMMIT;
34

点赞

无论是点赞还是取消点赞, 第一步是先判断是否已经点过赞

1SELECT * FROM `like` WHERE article_id = '文章id' AND user_id = '用户id' LIMIT 1;
2

如果查询到记录,说明已点赞,执行取消点赞;否则执行点赞。

1-- 点赞
2
3START TRANSACTION;
4--  1. 更新 like 表数据
5INSERT INTO `like`(article_id, user_id) VALUES ('文章id', '用户id');
6--  2. 更新 article 表数据(+1)
7UPDATE article SET like_count = like_count + 1 WHERE id = '文章id';
8COMMIT;
9-- 取消点赞
10
11START TRANSACTION;
12--  1. 删除 like 表数据
13DELETE FROM `like` WHERE  article_id = '文章id' AND user_id = '用户id';
14--  2. 更新 article 表数据(-1)
15UPDATE article SET like_count = like_count - 1 WHERE id = '文章id';
16COMMIT;
17

评论

写到这里发现,最初表设计有问题。本想用一个 parent_id 实现无限层级,但看了掘金和 B 站的评论设计,发现评论最多 2 层,其他都是平铺回复。

B 站的评论:

掘金的评论:

都是两层模型

  • 第 1 层:顶级评论(parent_id = NULL
  • 第 2 层:对顶级评论的回复(parent_id = 顶级.id
  • 第 3 层及以后:不再嵌套,把目标人 @nick 写进内容,parent_id 仍等于顶级评论 id,按时间平铺

新增评论

1START TRANSACTION;
2-- 情况一:顶级评论
3INSERT INTO comment(article_id, user_id, parent_id, target_user_id, content)
4  VALUES ('文章id', '用户id', NULL, NULL, '一级评论');
5
6-- 情况二:回复顶级评论或回复某人
7INSERT INTO comment(article_id, user_id, parent_id, target_user_id, content)
8  VALUES ('文章id', '用户id', '顶级评论id', '被回复人id', '二级评论');
9
10-- 3. 更新 article 表评论数(+1)
11UPDATE article SET comment_count = comment_count + 1 WHERE id = '文章id';
12
13COMMIT;
14

删除评论

1-- 1. 查看是否有子评论
2SELECT 1 FROM comment WHERE parent_id = '评论id' LIMIT 1;
3-- 情况一:如果没有子评论
4
5-- 情况一:没有子评论,直接删除
6START TRANSACTION;
7DELETE FROM comment WHERE id = '评论id' AND user_id = '用户id';
8-- 2. 更新 article 表评论数(-1)
9UPDATE article SET comment_count = comment_count - 1 WHERE id = '文章id';
10COMMIT;
11
12
13-- 情况二:有子评论,一起删除
14START TRANSACTION;
15-- 1. 获取所有子评论 id(二级评论的 parent_id 都是顶级评论 id)
16select id as ids from comment where parent_id = '评论id';
17
18-- 2. 代码中组装 ids:[...ids, '评论id'](包含所有子评论 id 和顶级评论 id)
19
20-- 3. 根据 id 批量删除
21delete from comment where id in ('ids');
22
23-- 4. 更新 article 表评论数(减去 ids.length)
24UPDATE article SET comment_count = comment_count - 'ids.length' WHERE id = '文章id';
25COMMIT;
26

这里使用代码 + SQL 语法的组合,纯粹用 SQL 实现需要使用存储过程,目前还不会

查询评论列表

这里查询主要还是看评论的交互流程是怎么样设计的。

  • 情况一:直接查询所有评论(在代码中组装成一个二级树结构),缺点数据量不能太大
  • 情况二:分页查询(先分页查询顶级的,展示时再不分页查询子评论),可能存在交互的迟钝感
  • 情况三:xxxx(我也不知道了,总感觉上面的两种方案都不是最佳的, 我看了掘金返回的结构是顶级评论分页(20 条)+ 子评论随着顶级评论一起返回,万一子评论的量也很多呢?)
1-- 情况一:查询所有评论
2
3-- 1. 获取所有顶级评论的 ids
4SELECT id from comment where article_id = '文章id' AND parent_id IS NULL;
5
6-- 2. 获取所有评论
7SELECT c.id,
8       c.parent_id,
9       c.user_id,
10       u.name                   AS user_name,
11       c.target_user_id,
12       tu.name                  AS target_user_name,
13       c.content,
14       c.created_at
15FROM comment c
16LEFT JOIN user u  ON u.id  = c.user_id    -- 连接用户表,查询用户名称
17LEFT JOIN user tu ON tu.id = c.target_user_id -- 连接用户表,查询用户名称
18WHERE  c.article_id = '文章id'
19  AND (c.parent_id IS NULL  -- 找出顶级评论(parent_id = NULL)和子评论(parent_id  ids 里面)
20       OR
21       c.parent_id IN ('ids'))
22ORDER BY c.parent_id IS NULL DESC,
23         c.created_at ASC;
24

通过代码进行组装成树

1// 针对情况一:拿到所有数据之后,在代码中组装成一个树结构
2
3function buildTree(list) {
4 const topMap = new Map(); // 顶级评论容器
5 const replyMap = new Map(); // 二级评论容器
6
7 list.forEach((item) => {
8   if (item.parent_id === null) {
9     item.replies = []; // 创建一个空数组,用来存放子评论
10     topMap.set(item.id, item);
11   } else {
12     const arr = replyMap.get(item.parent_id) || [];
13     arr.push(item);
14     replyMap.set(item.parent_id, arr);
15   }
16 });
17
18 topMap.forEach((top) => {
19   // 添加子评论
20   top.replies = replyMap.get(top.id) || [];
21 });
22
23 const tree = Array.from(topMap.values()).sort(
24   (a, b) => new Date(b.created_at) - new Date(a.created_at)
25 );
26
27 return tree;
28}
29
30
1-- 情况二: 先分页查询顶级的,展示时再不分页查询子评论
2
3-- 1. 分页获取顶层评论
4SELECT c.id,
5       c.parent_id,
6       c.user_id,
7       u.name               AS user_name,
8       c.target_user_id,
9       tu.name              AS target_user_name,
10       c.content,
11       c.created_at
12FROM comment c
13LEFT JOIN user u  ON u.id  = c.user_id
14LEFT JOIN user tu ON tu.id = c.target_user_id
15WHERE c.article_id = '文章id' AND c.parent_id IS NULL
16ORDER BY c.created_at DESC
17LIMIT 20 OFFSET '页码 * 20';
18
19-- 2. 获取所有子评论
20SELECT c.id,
21       c.parent_id,
22       c.user_id,
23       u.name               AS user_name,
24       c.target_user_id,
25       tu.name              AS target_user_name,
26       c.content,
27       c.created_at
28FROM comment c
29LEFT JOIN user u  ON u.id  = c.user_id
30LEFT JOIN user tu ON tu.id = c.target_user_id
31WHERE c.article_id = '文章id' AND c.parent_id  = '顶层评论id'
32ORDER BY c.created_at ASC;
33

其他的情况,就先不考虑了。

最后

SQL 这东西,多写几遍就熟了。语法虽然多,但常用的就那些,上面这些算是入门。

前端平时接触不到,但想往全栈发展,SQL 是必须了解的。虽然实际开发用 ORM,但底层还是 SQL(就像用 Vue,JavaScript 还是要懂的)。


SQL 语法速查手册:前端开发者的学习笔记》 是转载文章,点击查看原文


相关推荐


Vue3 + Element Plus 动态菜单实现:一套代码完美适配多角色权限系统
刘大华2025/12/1

今天分享一个基于Vue3和Element Plus的动态菜单实现。这个方案很适用于需要权限管理的后台系统,能够根据用户角色权限显示不同的菜单项。 一、什么是动态菜单?为什么需要它? 在管理后台系统中,不同角色的用户通常需要不同的功能权限。比如: 管理员可以访问所有功能 编辑者只能管理内容 查看者只能浏览数据 如果为每个角色单独开发一套界面,显然效率低下。动态菜单就是解决这个问题的方案——一套代码,根据不同用户角色显示不同的菜单结构。 二、实现效果预览 我们先来看看最终实现的效果: 角色切


从客户端自适应码率流媒体迁移到服务端自适应码率流媒体
AKAMAI2025/11/28

流媒体平台需要扩展以容纳数百万在不同设备和网络条件下同时观看的观众,这使得高效的自适应码率(ABR)流媒体技术至关重要。在这篇博文中,我们将探讨从客户端 ABR 过渡到服务端 ABR 的技术细节,重点关注实现细节和性能优化。 如您所在的企业也在考虑采购云服务或进行云迁移, 点击链接了解Akamai Linode解决方案,现在申请试用可得高达500美元专属额度 自适应码率流媒体基础 自适应码率流媒体究竟是如何工作的?让我们一步步来看。首先,视频内容需要经过准备,即将其编码为多种码率(例如 5


大模型瘦身术:量化与蒸馏技术全解析
居然JuRan2025/11/26

为什么要给大模型"瘦身"? 在AI技术飞速发展的今天,大语言模型已经成为各行各业的得力助手。但你是否知道,部署一个大模型的成本有多高? 一个千亿参数级别的模型,不仅需要占用大量的存储空间,在实际运行时更是需要惊人的计算资源。对于企业来说,这意味着高昂的硬件成本和运营开支。因此,如何在保持模型性能的同时降低部署成本,成为了AI工程师们必须面对的挑战。 今天,我们就来聊聊大模型压缩的两大主流技术——量化(Quantization)和蒸馏(Distillation)。 量化:用更少的位数存储参数 什


go ants pool 协程池学习笔记
用户7227868123442025/11/24

概述 使用 Go 开发并发程序很容易,一个 go 关键字就可以启动协程处理任务。Go 创建一个 goroutine 只需要 2K 内存空间,并且 go 协程上下文信息仅存储在两个寄存器中,对于 Go 运行时来说,切换上下文特别快。 不过凡事不加限制就会出问题,如果不加节制的滥用 goroutine 就可能导致内存泄露,增加 Go 运行时调度负担等。 下面看一段 Go http 标准库的代码: func (s *Server) Serve(l net.Listener) error { for


Cursor 2.1 版本更新日志
是魔丸啊2025/11/22

转载 发布日期: 2025年11月21日 改进的计划模式、编辑器中的 AI 代码审查,以及即时 Grep 主要功能 改进的计划模式 (Improved Plan Mode) 当创建计划时,Cursor 会通过澄清性问题来回应,以提高计划质量。Cursor 现在显示一个交互式 UI,让回答问题变得简单。 你也可以使用 ⌘+F 在生成的计划内进行搜索。 AI 代码审查 (AI Code Reviews) 你现在可以直接在 Cursor 中通过 AI 代码审查来发现和修复 bug。它会检查你的更改并


JMeter 自动化测试 + 飞书通知完整指南
兔子蟹子2025/11/20

JMeter 自动化测试 + 飞书通知完整指南 从零开始搭建 JMeter 自动化测试系统,并通过飞书机器人实时推送测试报告 📖 目录 项目简介系统架构环境准备配置参数说明实施步骤代码实现常见问题最佳实践 项目简介 背景 在持续集成/持续部署(CI/CD)流程中,自动化测试是保证代码质量的重要环节。本项目实现了: ✅ 自动化执行:批量运行 JMeter 测试脚本✅ 智能分析:自动统计成功率、响应时间、失败详情✅ 实时通知:测试完成后立即推送飞书消息✅ 可视化报告:美


Gemini 3深夜来袭:力压GPT 5.1,大模型谷歌时代来了
机器之心2025/11/19

Gemini 3 还没现身,推特先崩为敬。 没有哪家模型的发布比 Gemini 3 更万众瞩目,根据 Gemini 之前 3 个月更新一次的频率,AI 社区自 9 月起便对 Gemini 3 翘首以盼。 今天,谷歌开发者关系负责人、Google AI Studio 负责人一条仅含「Gemini」一词的推文,积蓄了数月的期待终于迎来了爆发点,推特相关话题瞬间沸腾。 有趣的是,临近发布节点,推特竟「应景」地崩了几次。尽管「幕后黑手」是 Cloudflare,但这崩溃的时机简直精准得让人怀疑有人背后


AI 为啥能回答你的问题?大模型 5 步工作流程,看完秒懂!
印刻君2025/11/18

如今,大语言模型(LLM)已成为我们学习和工作中的常用工具。当你在对话框输入问题时,或许会好奇,这些模型为何能精准、迅速地生成回答?本文将用通俗易懂的语言,为你拆解背后的核心工作流程。 简单来说,大模型处理问题主要包含五个关键环节,分别是: 分词(Tokenization) 词嵌入(Word Embedding) 位置编码(Positional Encoding) 自注意力机制(Self-Attention) 自回归生成(Autoregressive Generation) 这些专业名词虽然


整数序列权重排序——基于深度优先搜索(DFS)以及记忆化搜索
w24167178402025/11/16

提示:本文适合对算法设计感兴趣的道友一起互相学习,如有疑问,欢迎评论区或者私信讨论。 文章目录 前言 一、题目介绍 二、前置知识扩展 1.深度优先遍历 1.1递归DFS 1.1非递归DFS 2.@cache装饰器 3.range()函数 4.sorted()函数 三、解题思路及代码解读


前端开发小技巧-【JavaScript】- 获取元素距离 document 顶部的距离
禁止摆烂_才浅2025/11/15

获取元素距离 document 顶部的距离 方案1:使用 offsetTop(最简单) const element = document.getElementById('myDiv') const distance = element.offsetTop console.log(distance) // 500(像素) 方案2:使用 getBoundingClientRect() + scrollY(最准确) const element = document.getElementById(

首页编辑器站点地图

本站内容在 CC BY-SA 4.0 协议下发布

Copyright © 2025 聚合阅读