目录
1.多对多
在数据库设计中,处理学生与课程之间的多对多关系,需要通过建立一张中间表来实现。
假设我们有两张表:学生表(student)和课程表(course)
-- 创建一个名为 `student` 的新表
CREATE TABLE student (
-- 定义一个名为 `id` 的字段,类型为 INT,自动递增,并设置为表的主键
id INT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
-- 定义一个名为 `name` 的字段,类型为 VARCHAR,最大长度为 10 个字符,用于存储学生姓名
name VARCHAR(10) COMMENT '姓名',
-- 定义一个名为 `no` 的字段,类型为 VARCHAR,最大长度为 10 个字符,用于存储学生学号
no VARCHAR(10) COMMENT '学号'
) -- 为整个表添加注释,说明这是一个学生表
COMMENT '学生表';
-- 创建一个名为 `course` 的新表
CREATE TABLE course (
-- 定义一个名为 `id` 的字段,类型为 INT,自动递增,并设置为表的主键
id INT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
-- 定义一个名为 `name` 的字段,类型为 VARCHAR,最大长度为 10 个字符,用于存储课程名称
name VARCHAR(10) COMMENT '课程名称'
) -- 为整个表添加注释,说明这是一个课程表
COMMENT '课程表';
如需给两张表中添加数据
-- 向 student 表中插入新数据
INSERT INTO student VALUES
(NULL, 'xx', 'xx'), -- 插入第一条记录,id 自动递增,姓名为xx,学号为xx
...
(NULL, 'xx', 'xx');
-- 向 course 表中插入新数据
INSERT INTO course VALUES
(NULL, 'xx'),
...
(NULL, 'xx');
中间表设计
-- 创建一个名为 `student_course` 的新表,用于存储学生和课程之间的关联
CREATE TABLE student_course (
-- 定义一个名为 `id` 的字段,类型为 INT,自动递增,并设置为表的主键
id INT AUTO_INCREMENT COMMENT '主键' PRIMARY KEY,
-- 定义一个名为 `studentid` 的字段,类型为 INT,不为空,用于存储学生ID
studentid INT NOT NULL COMMENT '学生ID',
-- 定义一个名为 `courseid` 的字段,类型为 INT,不为空,用于存储课程ID
courseid INT NOT NULL COMMENT '课程ID',
-- 为 `courseid` 字段添加外键约束,引用 `course` 表的 `id` 字段
CONSTRAINT fk_courseid FOREIGN KEY (courseid) REFERENCES course (id),
-- 为 `studentid` 字段添加外键约束,引用 `student` 表的 `id` 字段
CONSTRAINT fk_studentid FOREIGN KEY (studentid) REFERENCES student (id)
) -- 为整个表添加注释,说明这是一个学生课程中间表
COMMENT '学生课程中间表';
-- 向 `student_course` 表中插入关联记录
INSERT INTO student_course VALUES
(NULL, 1, 1), -- 插入第一条记录,自动递增ID,学生ID为1,课程ID为1
...
(NULL, 3, 4); -- 插入第n条记录,自动递增ID,学生ID为3,课程ID为4
查询某个学生的所有课程
SELECT c.name AS CourseName
FROM course c
JOIN student_course sc ON c.id = sc.courseid
JOIN student s ON sc.studentid = s.id
WHERE s.id = studentIdValue;
查询某门课程的所有学生
SELECT s.name AS StudentName
FROM student s
JOIN student_course sc ON s.id = sc.studentid
JOIN course c ON sc.courseid = c.id
WHERE c.id = courseIdValue;
2.一对一
创建两个表来表示用户与用户详情之间的一对一关系。
CREATE TABLE tb_user (
id INT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
name VARCHAR(10) COMMENT '姓名',
age INT COMMENT '年龄',
gender CHAR(1) COMMENT '性别,1:男,2:女',
phone CHAR(11) COMMENT '手机号'
) COMMENT '用户基本信息表';
CREATE TABLE tb_user_edu (
id INT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
degree VARCHAR(20) COMMENT '学历',
major VARCHAR(50) COMMENT '专业',
primaryschool VARCHAR(50) COMMENT '小学',
middleschool VARCHAR(50) COMMENT '中学',
university VARCHAR(50) COMMENT '大学',
userid INT UNIQUE COMMENT '用户ID',
CONSTRAINT fk_userid FOREIGN KEY (userid) REFERENCES tb_user(id)
) COMMENT '用户教育信息表';
插入数据同上...
--消除笛卡尔积无效部分
select * from emp , dept
where emp.dept_id = dept.id;
3.内连接
隐式内连接
隐式内连接不使用JOIN
关键字,而是通过WHERE
子句来指定连接条件。
SELECT 字段列表
FROM 表1, 表2
WHERE 表1.字段 = 表2.字段;
显式内连接
显式内连接使用INNER JOIN
关键字,并通过ON
子句来明确指定连接条件。
SELECT 字段列表
FROM 表1
[INNER] JOIN 表2 ON 表1.字段 = 表2.字段;
案例
查询每一个员工的姓名,及关联的部门的名称
表结构: emp , dept
连接条件: emp.dept_id = dept.id
隐式内连接
-- 未使用别名的查询
SELECT emp.name, dept.name
FROM emp, dept
WHERE emp.dept_id = dept.id;
-- 使用别名的查询,简化SQL编写
SELECT e.name, d.name
FROM emp e, dept d
WHERE e.dept_id = d.id;
显式内连接
-- 未使用别名的查询
SELECT e.name, d.name
FROM emp e
INNER JOIN dept d ON e.dept_id = d.id;
-- 使用别名的查询,简化SQL编写
SELECT e.name, d.name
FROM emp e
JOIN dept d ON e.dept_id = d.id;
4.外连接
左外连接
左外连接返回左表(表1)的所有记录,即使右表(表2)中没有匹配的记录。如果左表的记录在右表中没有匹配项,则结果集中的右表部分将为NULL。
SELECT 字段列表
FROM 表1 LEFT [OUTER] JOIN 表2 ON 条件;
右外连接
右外连接返回右表(表2)的所有记录,即使左表(表1)中没有匹配的记录。如果右表的记录在左表中没有匹配项,则结果集中的左表部分将为NULL。
SELECT 字段列表
FROM 表1 RIGHT [OUTER] JOIN 表2 ON 条件;
案例
左外连接
查询emp
表的所有数据以及对应的部门信息
表结构: emp , dept
连接条件: emp.dept_id = dept.id
-- 使用LEFT OUTER JOIN查询
SELECT e.*, d.name
FROM emp e
LEFT OUTER JOIN dept d ON e.dept_id = d.id;
-- 省略OUTER关键字的查询,效果相同
SELECT e.*, d.name
FROM emp e
LEFT JOIN dept d ON e.dept_id = d.id;
右外连接
查询dept
表的所有数据以及对应的员工信息
表结构: emp , dept
连接条件: emp.dept_id = dept.id
使用右外连接,以确保即使某些部门没有员工,这些部门的信息也会被包括在查询结果中。
-- 使用RIGHT OUTER JOIN查询
SELECT d.*, e.*
FROM emp e
RIGHT OUTER JOIN dept d ON e.dept_id = d.id;
-- 省略OUTER关键字的查询,效果相同
SELECT d.*, e.*
FROM emp e
RIGHT JOIN dept d ON e.dept_id = d.id;
5.自连接
SELECT a.字段1, b.字段2
FROM 表名 a, 表名 b
WHERE a.条件字段 = b.条件字段;
SELECT a.字段1, b.字段2
FROM 表名 a
JOIN 表名 b ON a.条件字段 = b.条件字段;
案例
-- 使用自连接查询员工及其所属领导的名字
SELECT a.name AS '员工', b.name AS '领导'
FROM emp a, emp b
WHERE a.managerid = b.id;
-- 使用左外连接查询所有员工及其领导的名字,包括没有领导的员工
SELECT a.name AS '员工', b.name AS '领导'
FROM emp a
LEFT JOIN emp b ON a.managerid = b.id;
注意事项:
自连接查询必须要为表起别名,不然不清楚所指定的条件、返回的字段,到底是哪─张表的字段。
6.联合查询
联合查询(UNION)用于将两个或多个SELECT
语句的结果集合并成一个结果集
SELECT 字段列表 FROM 表A
UNION [ALL]
SELECT 字段列表 FROM 表B;
以下是关于联合查询的要点:
- 使用
UNION
时,会自动去除结果集中的重复行。 - 使用
UNION ALL
时,不会去除重复行,会将两个或多个查询的结果集直接合并在一起。 - 联合查询中,每个
SELECT
语句的字段数量必须相同。 - 每个查询中对应的字段类型应该兼容,以便在合并结果时不会出现类型错误。
案例
使用UNION ALL联合查询薪资低于5000的员工和年龄大于50岁的员工
-- 使用UNION ALL查询薪资低于5000的员工
SELECT * FROM emp WHERE salary < 5000
UNION ALL
-- 使用UNION ALL查询年龄大于50岁的员工
SELECT * FROM emp WHERE age > 50;
去除可能重复的员工记录(即那些既薪资低于5000又年龄大于50岁的员工)
-- 使用UNION查询薪资低于5000的员工
SELECT * FROM emp WHERE salary < 5000
UNION
-- 使用UNION查询年龄大于50岁的员工
SELECT * FROM emp WHERE age > 50;
7.子查询
子查询外部的语句可以是INSERT / UPDATE / DELETE / SELECT的任何一个。
一些SQL中常用的操作符及其描述:
IN
- 描述:
IN
操作符用于确定给定的值是否与子查询或列表中的值匹配。如果值在指定的集合范围内,则返回TRUE
,否则返回FALSE
。 - 例子:
SELECT * FROM emp WHERE dept_id IN (1, 2, 3);
NOT IN
- 描述:
NOT IN
操作符用于确定给定的值是否不在子查询或列表中的值匹配。如果值不在指定的集合范围内,则返回TRUE
,否则返回FALSE
。 - 例子:
SELECT * FROM emp WHERE dept_id NOT IN (1, 2, 3);
ANY
- 描述:
ANY
操作符与子查询一起使用,用于比较外部查询的值与子查询返回的任何值。如果外部查询的值满足子查询返回的任意一个值,则返回TRUE
。 - 例子:
SELECT * FROM emp WHERE salary > ANY (SELECT salary FROM emp WHERE dept_id = 1);
SOME
- 描述:
SOME
操作符是ANY
的同义词,可以互换使用。它用于比较外部查询的值与子查询返回的任意值。如果外部查询的值满足子查询返回的任意一个值,则返回TRUE
。 - 例子:
SELECT * FROM emp WHERE salary > SOME (SELECT salary FROM emp WHERE dept_id = 1);
ALL
- 描述:
ALL
操作符与子查询一起使用,用于比较外部查询的值与子查询返回的所有值。如果外部查询的值满足子查询返回的所有值,则返回TRUE
。 - 例子:
SELECT * FROM emp WHERE salary > ALL (SELECT salary FROM emp WHERE dept_id = 1);
使用操作符时,需要注意子查询必须返回一个列的值,以便与外部查询进行比较。
ANY
和 SOME
在功能上是相同的,可以互换使用。ALL
则要求外部查询的值必须大于或小于(取决于比较操作符)子查询返回的所有值。
1.标量子查询
标量子查询返回单个值(一个标量)。这个值通常用于与外部查询的比较操作。
例子
查询薪资高于部门平均薪资的员工信息。
SELECT *
FROM emp
WHERE salary > (SELECT AVG(salary) FROM emp);
2.列子查询
列子查询返回一列值,这些值通常用于与外部查询中的对应列进行比较。
例子
查询与部门ID为1的员工薪资相同的所有员工。
SELECT *
FROM emp
WHERE salary IN (SELECT salary FROM emp WHERE dept_id = 1);
3.行子查询
行子查询返回一行或多行,每行包含多个列。这个子查询的返回结果通常用于与外部查询中的行进行比较。
例子
查询与部门ID为1且薪资最高的员工具有相同薪资和部门的所有员工。
SELECT *
FROM emp
WHERE (salary, dept_id) = (SELECT MAX(salary), dept_id FROM emp WHERE dept_id = 1);
4.表子查询
表子查询返回多行多列的结果集,通常这个结果集被外部查询当作一个临时表来使用。
例子
查询薪资高于部门平均薪资的所有员工。
SELECT e.*
FROM emp e
WHERE e.salary > (SELECT AVG(salary) FROM emp WHERE dept_id = e.dept_id);