一个写 ToB 端的前端仔,天天跟数据打交道,头皮发麻。看着后端同事代码中写着长串的SQL语句,好生羡慕。
装B的东西不应该都要去掌握一番,加上正好最近在学习 node.js,学到了数据库模块,那么SQL语句是该好好学一波。
接口 api 返回的数据,不能全听后端同事在那里吹。有些东西,该后端处理数据,就后端处理,不要徒劳的增加前端工作量(玩笑且认真)。
认识SQL,准备工作
SQL: Structured Query Language,翻译为结构化查询语句,简称SQL。
使用 SQL 编写出来的语句就是 SQL语句。该语句用于对数据库进行操作。
接下来认识一些SQL的基本知识。
SQL 语句的规范
- 关键词使用大写。比如:CREATE、 TABLE等(当然小写也行)
- 一条SQL语句以分号(;) 结束。
- 当遇到自己设计的表名或者字段是关键词的时候,可以使用
``包裹,这样做目的就是不会看成一个关键词。
SQL 语句的类型
注:上面标志小红旗的,是常用的SQL类型。
表的约束
主键(primary key)
为了区分每条记录的唯一性,必须有一个字段永远不会重复,并且不能为空,那么这个字段被称为主键。
- 唯一性
- 必须是
NOT NULL。如果没有手动设置,默认也会自动设置为NOT NULL。 - 主键也可以是多个字段组合成唯一性,被称为联合主键。
- 建议:最好不要是业务字段表主键。
唯一性(unique)
主键具有唯一性。但是针对一些字段,也是具有唯一性的,尽管这些字段不是主键。
比如说:用户名(name)在一些系统中是不能重复的。
不能为空(not null)
禁止某些字段是不能为空,必填的。
默认值(default)
当某些字段没有被填充时,自动加入默认值。
自动增长(auto_increment)
针对一些id,或者排序的字段,需要每添加一条数据,在原来的基础上增加1。
外键(foreign key)
当针对多张表进行关联时,需要外键进行链接。
创建数据库和对应表
喜欢看 NBA,喜欢老詹,老詹目前在紫金湖人队,那就拿湖人球员的数据来进行分析,举例。
第一步:创建一个分析数据库(analyze_db)
-- 创建数据库(analyze_db)
CREATE DATABASE analyze_db; -- 创建数据库,如果存在会保存
CREATE DATABASE IF NOT EXISTS analyze_db; -- 先判断该库是否存在,不存在再创建
-- 查看所有数据库
SHOW DATABASES;
-- 使用数据库
USE analyze_db;
-- 删除数据库
DROP DATABASE analyze_db; -- 删除数据库,如果不存在会报错
DROP DATABASE IF EXISTS analyze_db; -- 先判断该库是否存在,存在就删除
第二步:创建一张湖人球员表(lakers_teams)
在创建表执之前,先执行了 use analyze_db,确保创建的表就是在该库里面。
开始创建表之前,应该冷静分析一下,需要球员哪些信息,来供参考。
| 字段名称 | 字段描述 |
|---|---|
| id | 作为主键,表示数据的唯一性 |
| name | 球员的名称 |
| avg_time | 平均上场的时间(得到教练组的信任度) |
| avg_score | 场均得分(能力的体现) |
| avg_shooting_rate | 投篮命中率(效率的体现) |
| avg_three_shooting_rate | 三分投篮命中率(外线得分的体现) |
| position | 球队定位 |
| backboard | 篮板 |
| secondary_attack | 助攻 |
参考的数据还是挺多的,现在就来创建一张表。
-- 创建表(lakers_teams)
CREATE TABLE IF NOT EXISTS lakers_teams(
id INT PRIMARY KEY AUTO_INCREMENT, -- id 主键 自动增加1
name VARCHAR(50) UNIQUE NOT NULL, -- name 唯一性 不能为空
avg_time DOUBLE DEFAULT 0, -- 场均时间 double 类型 默认值为0
avg_score DOUBLE DEFAULT 0,
avg_shooting_rate DOUBLE DEFAULT 0,
avg_three_shooting_rate DOUBLE DEFAULT 0,
position VARCHAR(50),
backboard FLOAT DEFAULT 0,
secondary_attack FLOAT DEFAULT 0
);
#################### 学习对表的其他SQL语句(最右边就是该SQL语句的功能关键词) ####################
-- 删除表
DROP TABLE IF EXISTS lakers_teams; -- drop
-- 表字段描述
DESC lakers_teams; -- desc
-- 修改表的名称(lakers_teams 改为 new_lakers_teams)
ALTER TABLE lakers_teams RENAME TO new_lakers_teams; -- rename
-- 添加新的字段(薪资:salary int类型)
ALTER TABLE lakers_teams ADD salary INT; -- add
-- 修改字段名称(salary 改为 price)
ALTER TABLE lakers_teams CHANGE salary price INT; -- change
-- 修改表中字段类型(price INT 改为 BIGINT 类型)
ALTER TABLE lakers_teams MODIFY price BIGINT; -- modify
-- 删除表中某个字段(price)
ALTER TABLE lakers_teams DROP price; -- drop
#################### 可以自己试一试 ####################
表的数据操作(增、删、改)
在上面已经创建好了库和表,那么现在就是向表中插入数据。
-- SQL语句添加几个球员的数据,其他的直接手动添加
INSERT INTO
lakers_teams (name, avg_time, avg_score, avg_shooting_rate, avg_three_shooting_rate, position, backboard, secondary_attack)
VALUES ('詹姆斯', 36.1, 27.4, 0.49, 0.31, '前锋', 8.2, 6.7);
INSERT INTO
lakers_teams (name, avg_time, avg_score, avg_shooting_rate, avg_three_shooting_rate, position, backboard, secondary_attack)
VALUES ('戴维斯', 33.4, 27.4, 0.59, 0.29, '中锋', 12.1, 2.6);
INSERT INTO
lakers_teams (name, avg_time, avg_score, avg_shooting_rate, avg_three_shooting_rate, position, backboard, secondary_attack)
VALUES ('威斯布鲁克', 28.0, 14.6, 0.40, 0.28, '后卫', 6.1, 7.6);
#################### 学习操作数据的其他SQL语句 ####################
-- 删除数据(删除表中所有的数据,谨慎使用)
DELETE FROM lakers_teams;
-- 删除表中数据(条件删除)
DELETE FROM lakers_teams WHERE id = 11;
-- 修改表中数据(所有数据,表中的position字段都会变成中锋)
UPDATE lakers_teams SET position = '中锋';
-- 修改表中数据(根据条件修改)
UPDATE lakers_teams SET position = '中锋' WHERE id = 11;
-- 拓展:当修改某一条数据时,使用最新的时间记录
ALTER TABLE lakers_teams
ADD updateTime TIMESTAMP
DEFAULT CURRENT_TIMESTAMP
ON UPDATE CURRENT_TIMESTAMP
-- lakers_teams 表添加 updateTime 类型为 timestamp ,默认值为 current_timestamp, 更新时也会重新设置 current_timestamp
#################### 可以自己试一试 ####################
插入数据
插入数据的SQL语句的用法:
insert into 表名 (字段1,字段2,字段3,...)values (字段1的值,字段2的值,字段3的值,...)。
录入数据完成(大部分数据手动录入的)
表的数据查询(查)
万事都是从简开始,SQL语句也不例外。
-- 查询所有数据
SELECT * FROM lakers_teams;
-- 查询部分数据(id, name, avg_score)
SELECT id, name, avg_score FROM lakers_teams;
-- 查询部分数据(id, name, avg_score),对得分字段取别名
SELECT id,name, avg_score as score FROM lakers_teams;
where筛选条件
在简单的基础上,添加where筛选条件
-- 场均大于20分的球员
SELECT id, name, avg_score FROM lakers_teams WHERE avg_score > 20;
-- 场均篮板大于8个的球员
SELECT * FROM lakers_teams WHERE backboard > 8;
-- 场均助攻大于等于5个的球员
SELECT * FROM lakers_teams WHERE secondary_attack >= 5;
-- 场均大于10,但是小于20分的中坚力量 (&& 与 and 等价, between...and 也可以实现)
SELECT * FROM lakers_teams WHERE avg_score > 10 && avg_score <= 20;
SELECT * FROM lakers_teams WHERE avg_score > 10 and avg_score <= 20;
SELECT * FROM lakers_teams WHERE avg_score BETWEEN 10 AND 20;
-- 看看三分命中率高于40%或者低于20%的呢
SELECT * FROM lakers_teams WHERE avg_three_shooting_rate > 0.4 || avg_three_shooting_rate < 0.2;
SELECT * FROM lakers_teams WHERE avg_three_shooting_rate > 0.4 or avg_three_shooting_rate < 0.2;
-- 戴维斯伤了,看有不有其他的中锋球员可以接替,查看中锋球员个数
SELECT id, name FROM lakers_teams WHERE `position` = '中锋';
where 模糊查询
- 使用
like关键词
%表示任意个字符
_表示一个任意字符
-- 查询后卫的情况(8位,这是两桌麻将的节奏呀)
SELECT * FROM lakers_teams WHERE `position` LIKE '%后卫%';
order by 数据排序
- 使用公式
order by 字段名称 生序/降序
- desc:降序; asc: 生序;
-- 得分降序排序
SELECT * FROM lakers_teams ORDER BY avg_score DESC;
分页查询
- 使用公式1:
limit 数据条数 offset 偏移量(推荐写法) - 使用公式2:
limit 偏移量, 数据条数(不推荐写法) - 处理前端分页
// 前端传递参数
{current: 1, pageSize: 10}
// 后端处理参数
{
limitNum: pageSize
offsetNum: (current - 1) * pageSize
}
// SQL 实现
select * from lakers_teams limit limitNum offset offsetNum;
-- 查看所有球员的数据,看不过来,先五个的看吧,看看数据的均衡
SELECT * FROM lakers_teams LIMIT 5 OFFSET 0; -- 前面五个
SELECT * FROM lakers_teams LIMIT 5 OFFSET 6; -- 接下来的五个
SELECT * FROM lakers_teams LIMIT 11, 5 -- 最后的五个
聚合函数
先收集到一起,然后所有结果看成一组数据,进行操作
常见的聚合函数:
- avg: 平均值
- max: 最大值
- min: 最小值
- sum: 求和
- count:计算个数(count的参数必须是必填的,不然如果没有值,就会不计算在内,那么就不准确)
-- 查询球员最好的投篮命中率
SELECT MAX(avg_shooting_rate) AS max_shooting_rate FROM lakers_teams;
-- 统计球员得分在10以上的球员个数(7个)
SELECT COUNT(*) FROM lakers_teams WHERE avg_score >= 10;
-- 统计三分投篮的平均命中率(0.3, 惨不忍睹)
SELECT AVG(avg_three_shooting_rate) as rate FROM lakers_teams;
group by 分组
聚合函数都是看成一组数据。
group by 进行分组,看成多组数据。
-- 统计各个位置的人数
SELECT `position`, COUNT(*) FROM lakers_teams GROUP BY `position`;
having 筛选条件
针对没有分组的 SQL 语句,筛选条件使用 where
但是针对 group by 组合后的 SQL 语句,筛选条件需要使用 having,而不能使用 where,使用 where 报错。
-- 统计各个位置的人数, 并且人数大于3。
SELECT `position`, COUNT(*) as count FROM lakers_teams GROUP BY `position` HAVING count >= 3;
为什么需要多表?
为什么需要多表?看看下面的举例,也许你就懂了。
创建一张表(collect_players),统计一些球员的基本数据和所在球队信息。
再创建表之前,还是要先分析分析(这里就不表格列举了)。
CREATE TABLE IF NOT EXISTS collect_players(
id INT PRIMARY KEY AUTO_INCREMENT, -- id 主键 自动增长
name VARCHAR(50) UNIQUE NOT NULL, -- name 名称 不能为空,唯一性
score DOUBLE DEFAULT 0, -- 场均得分
shooting_rate DOUBLE DEFAULT 0, -- 投篮命中率
three_shooting_rate DOUBLE DEFAULT 0, -- 三分投篮命中率
salary DOUBLE, -- 薪资
position VARCHAR(50), -- 角色定位
team_name VARCHAR(100), -- 所在球队名称
team_address VARCHAR(200), -- 所在球队地址
team_note VARCHAR(300) -- 所在球队的备注
);
添加数据,这里可以使用SQL语句添加,自己动手试试。(我就直接数据库里面添加了)
发现没有,这张表里面的数据,有很对是重复的。
就比如:
id 为 1 和 2,球队信息是一样的(三个字段重复)
id 为 4、5 、6时,球队信息也是一样的(三个字段重复)。
那么想象一下,这里的数据量不是很大。NBA中的球员这么多,每支球队,正规球员都有15人,还别说线下联赛人员。
如果表这样设计的话,就会存在大量的重复数据。并且一旦数据发生变动,又有大量的数据需要维护。
所以,针对不合理的表,就需要重新设计。
设计新表,重构原表
设计球队表(team_infos)。
CREATE TABLE IF NOT EXISTS team_infos(
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(100) UNIQUE NOT NULL,
address VARCHAR(100),
note TEXT
);
插入数据
重构collect_players表
-- 删除三个字段
ALTER TABLE collect_players DROP team_name;
ALTER TABLE collect_players DROP team_address;
ALTER TABLE collect_players DROP team_note;
-- 新增一个 team_id 字段,该字段是一个外键
ALTER TABLE collect_players ADD team_id INT; -- 新增team_id字段
ALTER TABLE collect_players ADD FOREIGN KEY (team_id) REFERENCES team_infos(id); -- 设置外键
重新录入数据
悄悄的这里添加了一个自由人,目前失业的安东尼。
这样两张表就分开了,保存的数据也不重复,也好维护。
外键知识点
外键使用
使用格式:foreign key (字段名称) references 表名(字段名称)
- foreign key (字段名称) 设置字段名称为外键
- references 引用。表明外键的值,是引用
表名(字段名称)的值,不能随便填写。
初始设计表的外键设置
在上面,是在表已经存在的时候设计的外键,那么在最初设计表的时候就设计外键呢?
CREATE TABLE IF NOT EXISTS collect_players(
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(30) NOT NULL,
team_id INT, -- 外键
FOREIGN KEY (team_id) REFERENCES team_infos(id)
);
外键属性
在上面设置了外键,当关联表的数据发生变化会怎么样?
UPDATE team_infos SET id = 1000;
-- 会发现,直接报错,因为 team_infos 中的id,被其他表的字段关联了;当id被更新时,其他的表的字段不知道是否要更新。
外键的属性值
外键的默认值是 restrict。 当有值存在关联的时候,就不会让其修改。
所以,外键需要重新设置。
-- 查看外键信息(简单的来说,就是知道外键的名称)
SHOW CREATE TABLE collect_players;
查询的结果
CREATE TABLE `collect_players` (
`id` int NOT NULL AUTO_INCREMENT,
`name` varchar(50) NOT NULL,
`score` double DEFAULT '0',
`shooting_rate` double DEFAULT '0',
`three_shooting_rate` double DEFAULT '0',
`salary` double DEFAULT NULL,
`position` varchar(50) DEFAULT NULL,
`team_id` int DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `name` (`name`),
KEY `team_id` (`team_id`),
CONSTRAINT `collect_players_ibfk_1` FOREIGN KEY (`team_id`) REFERENCES `team_infos` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
从里面可以知道外键的名称为 collect_players_ibfk_1。
如果存在多个外键,数字就依次内推。
collect_players_ibfk_1collect_players_ibfk_2
-- 根据外键名字删除外键
ALTER TABLE collect_players DROP FOREIGN KEY collect_players_ibfk_1;
-- 重新创建一个新的外键
ALTER TABLE collect_players add FOREIGN KEY (team_id) REFERENCES team_infos(id)
ON UPDATE CASCADE
ON DELETE CASCADE;
这样就会实现一边更新,另外一边关联的数据也会更新。
表连接
上面设计了两张新表,减少数据量,维护更加的方便。但是也会有一个缺点,就是数据太分散了,查看数据时就看不全信息。
那么这时候,就需要把两张表连接在一起。
也许,SQL语句的 表连接 是一个很好的选择。
表连接分为四种:
- 左连接(left [outer] join,常用)
- 右连接(right [outer] join,不常用)
- 内连接 ([cross/inner] join,常用)
- 全连接(mysql不支持,需要使用union,不常用)
[]里面的是可以省略的
左连接
使用格式:
A LEFT JOIN B (展示A的全部数据)
-- 连接 collect_players 和 team_infos, 以collect_players为主
-- 这里分别为表取了别名
SELECT * FROM collect_players as cp LEFT JOIN team_infos as ti ON cp.team_id = ti.id;
-- 去掉 collect_players 表中 team_id 中为null的数据
SELECT * FROM collect_players as cp LEFT JOIN team_infos as ti ON cp.team_id = ti.id WHERE team_id IS NOT NULL;
右连接
使用格式:
A RIGHT JOIN B (展示B的全部数据)
-- 连接 collect_players 和 team_infos, 以team_infos为主
SELECT * FROM collect_players as cp RIGHT JOIN team_infos as ti ON cp.team_id = ti.id;
内连接
使用格式: A JOIN B (展示A 与 B的交集数据)
A CROSS JOIN B 和 A INTER JOIN B 也是同样的道理。
SELECT * FROM collect_players as cp JOIN team_infos as ti ON cp.team_id = ti.id;
全连接
使用格式:(A LEFT JOIN B) union (A RIGHT JOIN B)
(SELECT * FROM collect_players as cp LEFT JOIN team_infos as ti ON cp.team_id = ti.id)
union
(SELECT * FROM collect_players as cp RIGHT JOIN team_infos as ti ON cp.team_id = ti.id);
多表查询(多对多)
在上面球队和球员的关系,就是一对多的关系。
但是在很多实际的情况下,数据间的联系是处于多对多的关系。
比如,NBA交易期阶段,被交易的球员和交易的球队就是一种多对多的关系。
- 被交易的球员有想法去任何一个球队。
- 交易的球队也有想法接受任何一个球员。
这就是一种多对多的关系。
上面对于不熟悉NBA规则的,例子不是很形象,但是下面这个例子一定很熟悉。
大学的时期选课(经典例子),这就是一种多对多的体现。
学生表(student):
| id | name | sex |
|---|---|---|
| 1 | 小明 | 男 |
| 2 | 小红 | 女 |
| 3 | 小张 | 男 |
| 4 | 小李 | 男 |
课程表(course):
| id | name | score |
|---|---|---|
| 1 | 数据结构 | 4 |
| 2 | C++ | 3 |
| 3 | 算法导论 | 2 |
学生肯定可以选择多门课,课程也同时可以被多个学生选择。
那么如何统计一个学生选了多少课呢?(错误的设计)
| id | name | sex | select_id |
|---|---|---|---|
| 1 | 小明 | 男 | [1,2] |
设计的表存在数组形式,数组里面存放该学生选择的课。
这是错误的设计方案,因为当课程的信息变动的时候,数组的里面存放的信息是不好维护的。(只有通过代码强行维护),这是不合理的。
正确的设计就是创建一张中间表,来保存两者之间的对应关系。
实战演示
-- 创建学生表
CREATE TABLE IF NOT EXISTS students(
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50) NOT NULL,
sex VARCHAR(10)
);
-- 插入学生数据
INSERT INTO students (name, sex) VALUES ('小明', '男');
INSERT INTO students (name, sex) VALUES ('小红', '女');
INSERT INTO students (name, sex) VALUES ('小李', '男');
INSERT INTO students (name, sex) VALUES ('小张', '男');
-- 创建课程表
CREATE TABLE IF NOT EXISTS courses(
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50) NOT NULL,
score INT
);
-- 插入课程数据
INSERT INTO courses (name, score) VALUES ('数据结构',3);
INSERT INTO courses (name, score) VALUES ('C++', 2);
INSERT INTO courses (name, score) VALUES ('算法导论', 4);
-- 创建关系表
CREATE TABLE IF NOT EXISTS select_student_course(
id INT PRIMARY KEY AUTO_INCREMENT,
student_id INT NOT NULL,
course_id INT NOT NULL,
FOREIGN KEY (student_id) REFERENCES students(id) ON UPDATE CASCADE ON DELETE CASCADE,
FOREIGN KEY (course_id) REFERENCES courses(id) ON UPDATE CASCADE ON DELETE CASCADE
);
-- 插入数据
INSERT INTO select_student_course (student_id, course_id) VALUES (1,3);
INSERT INTO select_student_course (student_id, course_id) VALUES (2,2);
INSERT INTO select_student_course (student_id, course_id) VALUES (3,1);
INSERT INTO select_student_course (student_id, course_id) VALUES (3,2);
INSERT INTO select_student_course (student_id, course_id) VALUES (2,1);
创建了三张表,分别插入了数据,接下来就可以通过关联表,来查看选课的情况。
-- 查看所有学生的选课情况
SELECT * FROM students LEFT JOIN select_student_course as ssc ON students.id = ssc.student_id
LEFT JOIN courses ON ssc.course_id = courses.id;
可以清晰知道每个同学的选课情况。
但是也发现美中不足的地方,就是很多字段是重复的,那么这里就需要取一些别名,便于区分。
-- 简化后的所有学生选课情况
SELECT
students.id as id,
students.name as name,
sex,
courses.name as courseName,
score
FROM students LEFT JOIN select_student_course as ssc ON students.id = ssc.student_id
LEFT JOIN courses ON ssc.course_id = courses.id;
是不是看着就挺清爽的。
-- 查询小红选的课程(也就是id为2)
SELECT
students.id as id,
students.name as name,
sex,
courses.name as courseName,
score
FROM students LEFT JOIN select_student_course as ssc ON students.id = ssc.student_id
LEFT JOIN courses ON ssc.course_id = courses.id
WHERE students.id = 2;
-- 查询C++被多少学生选择
SELECT
students.id as id,
students.name as name,
sex,
courses.name as courseName,
score
FROM students LEFT JOIN select_student_course as ssc ON students.id = ssc.student_id
LEFT JOIN courses ON ssc.course_id = courses.id
WHERE courses.id = 2;
当我们查询到的数据,通过一层对象直接返回,感觉数据表达的信息是非常的混乱。
比如,返回给前端的数据格式:
[
// 这层对象中既包含了学生的信息,也包含了课程了信息(就感觉这层对象,没有层次感)
{
id: 2,
name: '小红',
sex: '女',
courseId: 2,
courseName: 'C++',
score: 2
}
]
[
// 是否可以实现 课程信息单独使用一个字段来表示
{
id: 2,
name: '小红',
sex: '女',
courseInfo: {
id: 2,
name: 'C++',
score: 2
}
}
]
那么转化成上面这种形式,SQL语句改怎么实现呢?
SQL语句提供了JOSN_OBJECT来实现上面的效果
使用公式:JOSN_OBJECT(key1, value1, key2, value2, key3, value3,...)
SELECT
students.id as id,
students.name as name,
sex,
JSON_OBJECT('id', courses.id, 'name', courses.name, 'score', courses.score) as courseInfo
FROM students LEFT JOIN select_student_course as ssc ON students.id = ssc.student_id
LEFT JOIN courses ON ssc.course_id = courses.id;
但是也发现不对劲,就比如小红,选择了两门课程,返回前端的数据,应该是这样的。
// 小红是选择了两门课程的,那么她的课程信息应该是一个数组
{
id: 2,
name: '小红',
sex: '女',
courseArr: [
{id: 2, name: 'C++', score: 2},
{id: 1, name: '数据结构', score: 3}
]
}
那么这种SQL语句应该如何实现?
SQL语句提供了JOSN_ARRAYAGG函数,来实现数组的形式,当然也需要配合 JOSN_OBJECT函数。
使用公式:JOSN_ARRAYAGG(object)
-- 要合并数据,肯定需要分组,所以根据students.id进行分组
-- 在结合JSON_ARRAYAGG 与 JSON_OBJECT 配合转化成数组
SELECT
students.id as id,
students.name as name,
sex,
JSON_ARRAYAGG(JSON_OBJECT('id', courses.id, 'name', courses.name, 'score', courses.score)) as courseArr
FROM students LEFT JOIN select_student_course as ssc ON students.id = ssc.student_id
LEFT JOIN courses ON ssc.course_id = courses.id
GROUP BY students.id;
nice。
多表查询,基本上大致就这样了。
结语
终于写完了,感觉 SQL 的语法是真的不少。
本来想着使用NBA的数据,创建表,一条路线走完的。但是写到后面,感觉状态越来越差,甚至针对多表设计,已经想不出怎么构建了。最后就使用了通用的学生与课程的关系,进行演示,可能为了大家更好的理解,也更好的为我将来做回忆吧。(烂尾操作)。
但是对于自己而言,用额外的时间抽出了大量的精力来学习SQL语句,还是挺满足的(针对前端而言,使用SQL的语句基本为零)。掌握一个知识点,增添了自己的技术色彩。(copyer 加油)。
如果上面有误,请指教。(虽然写的挺烂的,但是针对知识点也许还是香的)。