安装
下一步等待安装
下一步
连接数据库协议以及接口,默认保持不动
密码强度
设置密码
默认保持不变
简介
MySQL 是一款广泛应用的开源关系型数据库管理系统(RDBMS),由瑞典 MySQL AB 公司开发,后来被甲骨文(Oracle)公司收购。它以其高性能、可靠性和易用性,成为了 Web 应用程序开发中的首选数据库之一。
特点
-
开源免费:MySQL 遵循开源许可协议,用户可以自由地使用、修改和分发,这大大降低了企业和开发者的成本。
-
高性能:经过优化的 MySQL 能够处理大量数据和高并发访问,在各种硬件环境下都能提供出色的性能表现。它采用了高效的查询优化器和索引机制,能够快速响应用户的查询请求。
-
可靠性:MySQL 具备完善的数据备份和恢复机制,支持事务处理和数据完整性约束,确保数据的安全性和一致性。即使在面对意外情况时,也能最大程度地保证数据的完整性。
-
易用性:MySQL 提供了简单易懂的 SQL 接口,方便开发者进行数据库的创建、查询、更新和删除操作。同时,它还拥有丰富的文档和社区支持,初学者可以快速上手。
-
跨平台性:MySQL 可以在多种操作系统上运行,如 Linux、Windows、Mac OS 等,具有良好的跨平台兼容性。这使得开发者可以根据自己的需求选择合适的操作系统进行开发和部署。
-
扩展性:MySQL 支持多种存储引擎,如 InnoDB、MyISAM 等,用户可以根据不同的应用场景选择合适的存储引擎。此外,它还支持集群和分区技术,能够轻松应对大规模数据的存储和处理需求。
在初次安装 MySQL 后,默认会存在几个系统数据库,包括 sys
、 mysql
、 information_schema
和 performance_schema
等
1. sys
数据库
-
作用:
- 是 MySQL 5.7+ 版本引入的系统数据库。
- 提供了一系列预定义的视图和存储过程,用于简化对
performance_schema
和information_schema
的查询。 - 主要用于性能分析和故障排查(例如查看锁、I/O 负载、内存使用等)。
-
能否删除:
- 可以删除,但通常不建议这样做。
- 删除后可能会影响某些性能分析工具的功能。
- 如果需要恢复,可以通过执行
mysql_sys_schema.sql
脚本重新创建(位于 MySQL 安装目录的scripts
文件夹)。
2. mysql
数据库
-
作用:
- 存储 MySQL 的核心数据,包括用户权限、存储过程、事件、时区信息等。
- 包含
user
、db
、tables_priv
等关键系统表。
-
能否删除:
- 绝对不能删除!删除会导致 MySQL 服务无法正常运行,甚至崩溃。
3. information_schema
数据库
-
作用:
- 提供对数据库元数据的访问(如表、列、索引、权限等信息)。
- 是 ANSI SQL 标准的一部分,所有数据均为只读视图。
-
能否删除:
- 不能删除,也无法删除。它是 MySQL 内部实现的虚拟数据库。
4. performance_schema
数据库
-
作用:
- 收集 MySQL 服务器的运行时性能数据(如锁、线程、内存使用等)。
- 用于监控和优化数据库性能。
-
能否删除:
- 不能删除 ,但可以通过配置参数关闭其功能(设置
performance_schema=OFF
)。
- 不能删除 ,但可以通过配置参数关闭其功能(设置
SQL语句
SQL是Structured Query Language ,称之为结构化查询语言,简称SQL
使用SQL****编写出来的语句 ,就称之为SQL语句
SQL语句可以用于对数据库进行操作
SQL语句的常用规范:
-
通常关键字使用大写的,比如CREATE、TABLE、SHOW等等;
-
一条语句结束后,需要以;结尾;
-
如果遇到关键字作为表明或者字段名称,可以使用``包裹;
数据类型
一、数字类型
-
整数类型
TINYINT
:1 字节,范围-128~127
或0~255
(无符号)。 用途 :状态标记(如0/1
表示布尔值)。INT
:4 字节,范围-2^31 ~ 2^31-1
。最常用的主键、计数器等。BIGINT
:8 字节,超大范围,用于 分布式 ID(如雪花算法) 。
-
浮点数
FLOAT(M, D)
:4 字节,近似值,科学计算(可容忍误差时用)。DOUBLE(M, D)
:8 字节,更大范围/精度。DECIMAL(M, D)
:精确计算 (如金额、金融数据),M
为总位数,D
为小数位。
-
高频 :
DECIMAL(10,2)
表示金额(如99999999.99
)。
二、日期与时间类型
DATE
:仅日期,格式YYYY-MM-DD
。TIME
:仅时间,格式HH:MM:SS
。DATETIME
:日期+时间,格式YYYY-MM-DD HH:MM:SS
,无时区 。 高频:记录用户注册时间、订单创建时间。TIMESTAMP
:日期+时间,带时区 (自动转 UTC 存储),范围1970-2038
年。 高频 :需要自动更新时间戳的字段(如ON UPDATE CURRENT_TIMESTAMP
)。
三、字符串类型
-
定长/变长
CHAR(N)
:固定长度(0 - 255字节),存储定长数据(如 MD5 哈希值)。VARCHAR(N)
:可变长度(0 - 65535 字节),高频用于可变长文本(用户名、地址)。
-
对比:
CHAR
查询更快,但浪费空间(适合短且固定的值)。VARCHAR
更省空间(适合长度波动大的文本)。
-
长文本
TEXT
:存储大段文本(如文章内容),最多 65KB。LONGTEXT
:最大 4GB 文本。
-
二进制数据
BLOB
:存储二进制文件(如图片、音频),但实际开发中通常存文件路径。
-
枚举与集合
ENUM('A','B','C')
:单选值(如性别ENUM('M','F')
)。SET('A','B','C')
:多选值(用逗号分隔,如用户兴趣标签)。 注意:扩展性差,慎用(可用关联表替代)。
四、JSON 类型
JSON
:存储结构化 JSON 数据(MySQL 5.7+ 支持),高频用于 NoSQL 式灵活存储 。 优势 :支持 JSON 路径查询(如WHERE data->'$.user.name' = 'John'
)
日常开发中最常用的类型
场景 | 推荐类型 | 原因说明 |
---|---|---|
主键 ID | INT UNSIGNED | 自增、范围大且高效 |
金额/精确计算 | DECIMAL(10,2) | 避免浮点误差 |
用户名字/地址 | VARCHAR(255) | 灵活节省空间 |
大段文本 | TEXT | 支持长内容 |
时间戳字段 | TIMESTAMP | 自动时区转换、更新 |
状态标记(布尔值) | TINYINT(1) | 替代 BOOLEAN(兼容性更好) |
配置/动态数据 | JSON | 灵活存储非结构化数据 |
注意事项 ⚠️
-
避免过度分配长度 :如
VARCHAR(255)
可能浪费资源,按实际需求定义 -
TIMESTAMP 的 2038 年问题 :未来需迁移到
DATETIME
-
ENUM / SET 慎用:修改选项需 DDL 操作,影响并发性能
-
数值类型选择 :优先选最小适用类型(如状态用
TINYINT
而非INT
)
常用SQL语句
DDL(数据定义)
创建数据库
sql
CREATE DATABASE shop
CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;
CREATE DATABASE
→ 创建新数据库CHARACTER SET
→ 指定字符集(推荐utf8mb4
支持 emoji)COLLATE
→ 排序规则(unicode_ci
不区分大小写)
创建表
sql
-- 创建用户表,包含主键、自增、默认值和唯一约束
CREATE TABLE users (
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) NOT NULL UNIQUE,
age TINYINT UNSIGNED DEFAULT 18,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
PRIMARY KEY
→ 主键(唯一标识每行数据)AUTO_INCREMENT
→ 自增(常用于主键)DEFAULT
→ 默认值(插入数据时未指定则填充)UNIQUE
→ 唯一约束(如用户名不可重复)
表约束
主键 :PRIMARYKEY
一张表中,我们为了区分每一条记录的唯一性 ,必须有一个字段是永远不会重复 ,并且不会为空的 ,这个字段我们通常会将它设置为主键:
主键是表中唯一的索引;
并且必须是NOTNULL的,如果没有设置NOTNULL,那么MySQL也会隐式的设置为NOTNULL;
主键也可以是多列索引,PRIMARYKEY(key_Part,..) ,我们一般称之为联合主键;
建议:开发中主键字段应该是和业务无关的,尽量不要使用业务字段来作为主键
唯一:UNIQUE
某些字段在开发中我们希望是唯一 的,不会重复的,比如手机号码、身份证号码等,这个字段我们可以使用UNIQUE来约束:
使用UNIQUE约束的字段在表中必须是不同的;
UNIQUE索引I允许NULL包含的列具有多个值NULL
不能为空:NOTNULL
口某些字段我们要求用户必须插入值,不可以为空,这个时候我们可以使用NOTNULL来约束;
默认值:DEFAULT
口某些字段我们希望在没有设置值时给予一个默认值,这个时候我们可以使用DEFAULT来完成;
自动递增:AUTO_INCREMENT
口某些字段我们希望不设置值时可以进行递增,比如用户的id,这个时候可以使用AUTO_INCREMENT来完成;
修改表结构 (ALTER)
添加列
sql
-- 向 `users` 表添加手机号字段
ALTER TABLE users
ADD COLUMN mobile VARCHAR(15) NOT NULL AFTER username;
ADD COLUMN
→ 添加新列AFTER username
→ 指定列的位置(可选)
修改列类型
sql
-- 将 `email` 字段长度扩展到 150
ALTER TABLE users
MODIFY COLUMN email VARCHAR(150) UNIQUE;
修改类型可能导致数据截断(如 VARCHAR(100)
→ VARCHAR(50)
)
重命名表
sql
-- 将表名 `users` 改为 `members`
RENAME TABLE users TO members;
删除列
sql
-- 删除 `users` 表的 `age` 字段
ALTER TABLE users
DROP COLUMN age;
删除表
sql
-- 删除 `temp_data` 表(谨慎操作!)
DROP TABLE IF EXISTS temp_data;
IF EXISTS
→ 避免表不存在时报错
DML (数据操作)
插入数据 INSERT
sql
-- 插入一条用户数据,指定字段名和值
INSERT INTO users (name, email, created_at)
VALUES ('王五', '[email protected]', NOW());
INSERT INTO users
:向users
表插入数据(name, email, created_at)
:指定要插入的字段VALUES (...)
:对应字段的值NOW()
:函数,插入当前时间 注意 :字段顺序必须与值一一对应,未指定的字段会用默认值或NULL
。
更新数据 UPDATE
sql
-- 将 id=5 的用户年龄改为 25,并记录修改时间UPDATE users
SET age = 25, updated_at = CURRENT_TIMESTAMP
WHERE id = 5;
UPDATE users
:更新users
表SET age = 25
:设置age
字段为 25updated_at = CURRENT_TIMESTAMP
:同时更新修改时间WHERE id = 5
:关键约束 ,只修改id=5
的行(无 WHERE 会更新全表!)
删除数据 DELETE
sql
-- 删除邮箱为 NULL 且注册时间在 2020 年前的无效用户DELETE FROM users
WHERE email IS NULL
AND created_at < '2020-01-01';
DELETE FROM users
:从users
表删除数据WHERE ...
:必须指定条件,否则清空整个表!email IS NULL
:判断邮箱为空的记录AND
:同时满足两个条件
DQL (数据查询)
基础查询
sql
-- 查询年龄大于 18 岁的用户,按注册时间倒序排列,取前 10 条SELECT id, name, age
FROM users
WHERE age > 18
ORDER BY created_at DESC
LIMIT 10 OFFSET 20;
SELECT id, name, age
:选择要查询的字段(避免用*
提升性能)FROM users
:从users
表查询WHERE age > 18
:过滤条件ORDER BY created_at DESC
:按注册时间倒序 (ASC升序[默认] DESC降序)LIMIT 10
:限制返回 10 条 (分页查询)OFFSET 20
:数据偏移
多表连接 JOIN
sql
-- 查询订单详情:关联用户表和订单表SELECT o.order_id, u.name, o.amount
FROM orders o
INNER JOIN users u ON o.user_id = u.id
WHERE o.status = 'paid';
INNER JOIN
:内连接,只返回两表匹配的行orders o
:给orders
表起别名o
ON o.user_id = u.id
:连接条件(订单的用户ID = 用户的ID)WHERE o.status = 'paid'
:筛选已支付的订单
聚合与分组 GROUP BY
sql
-- 统计每个部门的平均工资,且只显示平均工资大于 10000 的部门SELECT department, AVG(salary) AS avg_salary
FROM employees
GROUP BY department
HAVING avg_salary > 10000;
解释:
AVG(salary)
:计算平均工资AS avg_salary
:给计算结果起别名GROUP BY department
:按部门分组HAVING avg_salary > 10000
:对分组后的结果过滤(WHERE 不能用于聚合函数)
聚合函数
什么是聚合函数?
聚合函数对一组值 执行计算并返回单个汇总值 ,常用于统计、分组、汇总数据 核心特点:
- 通常与
GROUP BY
子句配合使用(分组统计) - 忽略
NULL
值(除非特别处理) - 默认对所有行进行计算,可用
DISTINCT
去重后再聚合
常见聚合函数及用法
函数 | 作用 | 示例 | 高频场景 |
---|---|---|---|
COUNT() | 统计行数或非 NULL 值数量 | SELECT COUNT(*) FROM users | 统计总记录数、满足条件的数量 |
SUM() | 数值列求和 | SELECT SUM(salary) FROM employees | 计算总和(如销售额、库存总量) |
AVG() | 数值列平均值 | SELECT AVG(score) FROM exams | 计算平均分、平均价格 |
MAX() | 最大值 | SELECT MAX(price) FROM products | 找最高分、最新日期、最大金额 |
MIN() | 最小值 | SELECT MIN(created_at) FROM orders | 找最低分、最早日期 |
GROUP_CONCAT() | 将分组结果拼接成字符串 | SELECT GROUP_CONCAT(name) FROM ... | 合并多行文本(如用户标签列表) |
- 基础统计(不分组)
sql
SELECT
COUNT(*) AS total_users,AVG(age) AS avg_age,MAX(balance) AS max_balance
FROM users;
作用: 直接对整个表进行统计,不按任何字段分组
COUNT(*)
:统计表中所有行的总数(包括NULL
值)AVG(age)
:计算所有用户的平均年龄(自动跳过age
为NULL
的行)MAX(balance)
:找出表中最大的余额值
输出示例:
total_users | avg_age | max_balance |
---|---|---|
1000 | 28.5 | 99999.99 |
适用场景:
- 快速获取全表核心指标(如用户总数、平均值、极值)
- 适用于数据报表的摘要部分或管理后台的统计面板
- 分组统计(
GROUP BY
)
sql
SELECT
department_id,COUNT(*) AS emp_count,AVG(salary) AS avg_salary
FROM employees
GROUP BY department_id;
作用 : 按 department_id
将员工分组,统计每个部门的员工数量和平均工资
GROUP BY department_id
:将数据按部门分组,每个部门单独计算COUNT(*)
:统计每个部门的员工总数AVG(salary)
:计算每个部门的平均工资
输出示例:
department_id | emp_count | avg_salary |
---|---|---|
1 | 50 | 8000 |
2 | 30 | 12000 |
适用场景:
- 按维度分析数据(如按地区统计销售额、按月份统计订单量)
- 常用于生成分组报表或可视化图表的数据源
- 过滤分组结果(
HAVING
)
sql
SELECT
customer_id,
COUNT(*) AS order_count
FROM orders
GROUP BY customer_id
HAVING order_count > 100;
作用: 按客户分组统计订单数量,并筛选出订单数超过 100 的客户
GROUP BY customer_id
:按客户分组COUNT(*)
:统计每个客户的订单总数HAVING order_count > 100
:过滤出订单数大于 100 的分组
输出示例:
customer_id | order_count |
---|---|
101 | 150 |
202 | 200 |
与 WHERE
的区别:
WHERE
在分组前过滤行(如WHERE order_date > '2023-01-01'
)HAVING
在分组后过滤分组(必须依赖聚合结果或分组字段)
适用场景:
- 筛选出满足条件的分组(如高价值客户、热门商品)
- 常用于识别异常数据或重点分析对象
- 结合
DISTINCT
去重统计
sql
SELECT
COUNT(DISTINCT ip_address) AS unique_visitors
FROM access_logs;
作用: 统计访问日志中不同 IP 地址的数量(去重后计数)
DISTINCT ip_address
:先对ip_address
去重,再统计数量- 如果直接
COUNT(ip_address)
会统计所有 IP(含重复值)
输出示例:
unique_visitors |
---|
35678 |
适用场景:
- 统计唯一值数量(如活跃用户数、独立访客数)
- 避免重复数据干扰统计结果
- 字符串聚合(
GROUP_CONCAT
)
sql
SELECT
order_id,
GROUP_CONCAT(product_name SEPARATOR ', ') AS products
FROM order_items
GROUP BY order_id;
作用: 将同一订单的商品名称合并成一个字符串(用逗号分隔)
GROUP_CONCAT(product_name)
:默认用逗号分隔SEPARATOR ', '
:可自定义分隔符(如换行符\n
)
输出示例:
order_id | products |
---|---|
1001 | 手机, 耳机, 充电宝 |
1002 | 笔记本电脑, 鼠标 |
适用场景:
- 将多行数据合并为单行展示(如订单商品列表、用户标签)
- 简化数据展示格式,便于导出或前端渲染
关键区别与注意事项
-
WHERE
vsHAVING
WHERE
:过滤原始数据行,执行在聚合前
sql-- 统计 2023 年每个客户的订单数SELECT customer_id, COUNT(*) FROM orders WHERE order_date >= '2023-01-01' -- 先过滤行GROUP BY customer_id;
HAVING
:过滤分组后的结果,依赖聚合值
sql-- 筛选出 2023 年订单数超过 50 的客户SELECT customer_id, COUNT(*) FROM orders WHERE order_date >= '2023-01-01'GROUP BY customer_id HAVING COUNT(*) > 50; -- 再过滤分组
-
聚合函数 与
NULL
值- 所有聚合函数(如
SUM
,AVG
)默认忽略NULL
- 若需将
NULL
视为 0 参与计算,需用COALESCE
:
sqlSELECT AVG(COALESCE(salary, 0)) FROM employees; -- NULL 转为 0
- 所有聚合函数(如
-
性能优化
- 对大表分组时,尽量先通过
WHERE
缩小数据范围 - 为
GROUP BY
的列添加索引(如INDEX(department_id)
)
- 对大表分组时,尽量先通过
外键
用通俗的方法解释就是,假如你有两个表格,一个是 "班级表",记录着每个班级的信息,比如班级编号、班级名称;另一个是 "学生表",记录着每个学生的信息,像学生编号、学生姓名、所在班级。在 "学生表" 里,为了表明每个学生属于哪个班级,就需要一个字段来引用 "班级表" 里的班级编号,这个用来引用其他表中主键的字段就叫做外键
外键约束
外键约束就像是一种规则,它规定了外键字段的值必须是被引用表(也就是 "班级表")中主键字段已经存在的值,或者可以为 NULL
。这就好比学校规定每个学生必须属于一个已经存在的班级,不能说自己属于一个根本不存在的班级。外键约束可以保证数据的一致性和完整性
sql
-- 创建班级表
CREATE TABLE classes (
class_id INT PRIMARY KEY,
class_name VARCHAR(50)
);
-- 创建学生表,并添加外键约束
CREATE TABLE students (
student_id INT PRIMARY KEY,
student_name VARCHAR(50),
class_id INT,
-- 添加外键约束,class_id 引用 classes 表的 class_id
FOREIGN KEY (class_id) REFERENCES classes(class_id)
);
-- 向班级表中插入数据
INSERT INTO classes (class_id, class_name) VALUES (1, '一年级一班');
INSERT INTO classes (class_id, class_name) VALUES (2, '一年级二班');
-- 向学生表中插入数据
-- 这个插入操作会成功,因为 class_id 1 存在于 classes 表中
INSERT INTO students (student_id, student_name, class_id) VALUES (1, '张三', 1);
-- 这个插入操作会失败,因为 class_id 3 不存在于 classes 表中
-- INSERT INTO students (student_id, student_name, class_id) VALUES (2, '李四', 3);
更新
sql
-- 创建班级表,添加 ON UPDATE CASCADE 选项
CREATE TABLE classes (
class_id INT PRIMARY KEY,
class_name VARCHAR(50)
);
-- 创建学生表,添加外键约束并设置 ON UPDATE CASCADE
CREATE TABLE students (
student_id INT PRIMARY KEY,
student_name VARCHAR(50),
class_id INT,
FOREIGN KEY (class_id) REFERENCES classes(class_id)
ON UPDATE CASCADE
);
-- 向班级表中插入数据
INSERT INTO classes (class_id, class_name) VALUES (1, '一年级一班');
INSERT INTO classes (class_id, class_name) VALUES (2, '一年级二班');
-- 向学生表中插入数据
INSERT INTO students (student_id, student_name, class_id) VALUES (1, '张三', 1);
INSERT INTO students (student_id, student_name, class_id) VALUES (2, '李四', 2);
-- 更新班级表中 class_id 为 1 的记录,将 class_id 更新为 3
UPDATE classes
SET class_id = 3
WHERE class_id = 1;
-- 查看更新后的学生表,发现学生表中 class_id 为 1 的记录也自动更新为 3
SELECT * FROM students;
- 在创建
students
表时,使用ON UPDATE CASCADE
选项,表示当更新classes
表的class_id
时,students
表中引用该class_id
的记录会自动更新 - 执行
UPDATE
语句更新classes
表中class_id
为 1 的记录,将其更新为 3。由于设置了ON UPDATE CASCADE
,students
表中class_id
为 1 的记录也会自动更新为 3
删除
sql
-- 创建班级表,添加 ON DELETE CASCADE 选项
CREATE TABLE classes (
class_id INT PRIMARY KEY,
class_name VARCHAR(50)
);
-- 创建学生表,添加外键约束并设置 ON DELETE CASCADE
CREATE TABLE students (
student_id INT PRIMARY KEY,
student_name VARCHAR(50),
class_id INT,
FOREIGN KEY (class_id) REFERENCES classes(class_id)
ON DELETE CASCADE
);
-- 向班级表中插入数据
INSERT INTO classes (class_id, class_name) VALUES (1, '一年级一班');
INSERT INTO classes (class_id, class_name) VALUES (2, '一年级二班');
-- 向学生表中插入数据
INSERT INTO students (student_id, student_name, class_id) VALUES (1, '张三', 1);
INSERT INTO students (student_id, student_name, class_id) VALUES (2, '李四', 2);
-- 删除班级表中 class_id 为 1 的记录
DELETE FROM classes WHERE class_id = 1;
-- 查看学生表,发现学生表中 class_id 为 1 的记录也自动删除
SELECT * FROM students;
- 在创建
students
表时,使用ON DELETE CASCADE
选项,表示当删除classes
表的记录时,students
表中引用该记录的class_id
的记录会自动删除。 - 执行
DELETE
语句删除classes
表中class_id
为 1 的记录。由于设置了ON DELETE CASCADE
,students
表中class_id
为 1 的记录也会自动删除
多表查询
SQL join 用于把来自两个或多个表的行结合起来
下图展示了 LEFT JOIN、RIGHT JOIN、INNER JOIN、OUTER JOIN 相关的 7 种用法
LEFT JOIN (左连接)
左连接会返回左表中的所有行,以及右表中满足连接条件的行。若右表中没有匹配的行,那么右表的列值会显示为 NULL
。可以理解为以左表为基础,尽量去匹配右表的数据
sql
SELECT orders.order_id, customers.customer_name
FROM orders
LEFT JOIN customers
ON orders.customer_id = customers.customer_id;
RIGHT JOIN (右连接)
右连接和左连接相反,它会返回右表中的所有行,以及左表中满足连接条件的行。若左表中没有匹配的行,左表的列值会显示为 NULL
。也就是以右表为基础,去匹配左表的数据
sql
SELECT orders.order_id, customers.customer_name
FROM orders
RIGHT JOIN customers
ON orders.customer_id = customers.customer_id;
这个查询会返回 customers
表中的所有客户
即便某些客户没有对应的订单信息(此时 orders.order_id
会显示为 NULL
)
INNER JOIN (内连接)
这是最简单、最容易理解、最常用的JOIN方式
内连接查询返回表A和表B中所有匹配行的结果
假设存在两个表,orders
(订单表)和 customers
(客户表)
orders
表有 order_id
、customer_id
等列
customers
表有 customer_id
、customer_name
等列
sql
SELECT orders.order_id, customers.customer_name
FROM orders
INNER JOIN customers
ON orders.customer_id = customers.customer_id;
仅当 orders
表中的 customer_id
和 customers
表中的 customer_id
相匹配时
对应的行才会被选取出来
OUTER JOIN (外连接)
OUTER JOIN也可以当作是FULL OUTER JOIN(全外连接) 或者FULL JOIN(全连接)
返回左表和右表中的所有行。当某一行在另一个表中没有匹配的行时,对应的列值会显示为 NULL
可以看作是左连接和右连接的并集
我们需要使用UNION来实现
sql
-- 选取左连接的结果
SELECT orders.order_id, customers.customer_name
FROM orders
LEFT JOIN customers
ON orders.customer_id = customers.customer_id
UNION
-- 选取右连接的结果
SELECT orders.order_id, customers.customer_name
FROM orders
RIGHT JOIN customers
ON orders.customer_id = customers.customer_id;
此查询会返回 orders
表和 customers
表中的所有行,无论是否有匹配关系
LEFT Excluding JOIN (左排除连接)
左排除连接用于返回左表中那些在右表中没有匹配记录的行。它本质上是在左连接的基础上,排除掉左右表都有匹配的部分,只保留左表中独有的行
假设我们有两个表:orders
表和 customers
表。orders
表记录订单信息,包含 order_id
和 customer_id
列;customers
表记录客户信息,包含 customer_id
和 customer_name
列。以下是实现左排除连接的 SQL 代码
sql
SELECT orders.*
FROM orders
LEFT JOIN customers
ON orders.customer_id = customers.customer_id
WHERE customers.customer_id IS NULL;
- 首先使用
LEFT JOIN
将orders
表和customers
表进行左连接,这样会得到orders
表的所有行以及customers
表中匹配的行。 - 然后通过
WHERE customers.customer_id IS NULL
过滤掉那些在customers
表中有匹配记录的行,最终只保留orders
表中没有对应客户记录的行。
RIGHT Excluding JOIN(右排除连接)
右排除连接用于返回右表中那些在左表中没有匹配记录的行。它是在右连接的基础上,排除掉左右表都有匹配的部分,只保留右表中独有的行
sql
SELECT customers.*
FROM orders
RIGHT JOIN customers
ON orders.customer_id = customers.customer_id
WHERE orders.order_id IS NULL;
- 先使用
RIGHT JOIN
将orders
表和customers
表进行右连接,得到customers
表的所有行以及orders
表中匹配的行。 - 接着通过
WHERE orders.order_id IS NULL
过滤掉那些在orders
表中有匹配记录的行,最终只保留customers
表中没有对应订单记录的行
OUTER Excluding JOIN(全外排除连接)
OUTER Excluding JOIN 并非标准 SQL 术语,但可理解为在 全外连接(FULL OUTER JOIN) 的基础上,排除掉左右表都匹配的行,仅保留单边存在的记录(即左表独有或右表独有的行)
sql
SELECT *
FROM products
FULL OUTER JOIN brand
ON products.brand_id = brand.id
WHERE products.brand_id IS NULL -- 右表独有(左表无匹配)
OR brand.id IS NULL; -- 左表独有(右表无匹配)
多对多关系表
在数据库设计中,多对多关系是指一个表中的多条记录可以与另一个表中的多条记录相关联。为了处理这种关系,通常需要引入一个中间表(也称为联结表或关联表)
多对多关系表结构原理
假设有两个实体,分别用表 A 和表 B 表示,它们之间存在多对多关系。为了表示这种关系,需要创建一个中间表 C,该表至少包含两个外键,分别引用表 A 和表 B 的主键。通过中间表 C,可以将表 A 和表 B 中的记录关联起来。
表结构设计
- 学生表(students):存储学生的基本信息。
- 课程表(courses):存储课程的基本信息。
- 选课表(student_courses):作为中间表,存储学生和课程的关联信息。
sql
-- 创建学生表
CREATE TABLE students (
student_id INT PRIMARY KEY AUTO_INCREMENT,
student_name VARCHAR(50) NOT NULL,
-- 可以添加其他学生相关的列,如年龄、性别等
age INT,
gender CHAR(1)
);
-- 创建课程表
CREATE TABLE courses (
course_id INT PRIMARY KEY AUTO_INCREMENT,
course_name VARCHAR(100) NOT NULL,
-- 可以添加其他课程相关的列,如学分、授课教师等
credits INT,
teacher VARCHAR(50)
);
-- 创建选课表(中间表)
CREATE TABLE student_courses (
id INT PRIMARY KEY AUTO_INCREMENT,
student_id INT,
course_id INT,
-- 添加外键约束,关联学生表
FOREIGN KEY (student_id) REFERENCES students(student_id),
-- 添加外键约束,关联课程表
FOREIGN KEY (course_id) REFERENCES courses(course_id)
);
-
学生表(students):
student_id
:学生的唯一标识符,作为主键,使用AUTO_INCREMENT
自动生成。student_name
:学生姓名,不能为空。age
和gender
:学生的年龄和性别,可根据实际需求添加更多列。
-
课程表(courses):
course_id
:课程的唯一标识符,作为主键,使用AUTO_INCREMENT
自动生成。course_name
:课程名称,不能为空。credits
和teacher
:课程的学分和授课教师,可根据实际需求添加更多列。
-
选课表(student_courses):
id
:选课记录的唯一标识符,作为主键,使用AUTO_INCREMENT
自动生成。student_id
:引用students
表的student_id
,表示选课的学生。course_id
:引用courses
表的course_id
,表示所选的课程。- 两个外键约束确保了数据的一致性和完整性,即选课记录中的学生和课程必须存在于相应的表中。
多对多表关系数据查询
sql
-- 1. 查询每个学生所选的课程
SELECT
s.student_name,
c.course_name
FROM
students s
JOIN
student_courses sc ON s.student_id = sc.student_id
JOIN
courses c ON sc.course_id = c.course_id
ORDER BY
s.student_name;
-- 2. 查询某门课程的所有选课学生
SELECT
c.course_name,
s.student_name
FROM
courses c
JOIN
student_courses sc ON c.course_id = sc.course_id
JOIN
students s ON sc.student_id = s.student_id
WHERE
c.course_name = '数学';
-- 3. 查询每个学生所选课程的数量
SELECT
s.student_name,
COUNT(sc.course_id) AS course_count
FROM
students s
LEFT JOIN
student_courses sc ON s.student_id = sc.student_id
GROUP BY
s.student_id
ORDER BY
s.student_name;
-- 4. 查询没有选课的学生
SELECT
s.student_name
FROM
students s
LEFT JOIN
student_courses sc ON s.student_id = sc.student_id
WHERE
sc.student_id IS NULL;
-- 5. 查询选择了所有课程的学生
SELECT
s.student_name
FROM
students s
JOIN
student_courses sc ON s.student_id = sc.student_id
GROUP BY
s.student_id
HAVING
COUNT(DISTINCT sc.course_id) = (SELECT COUNT(*) FROM courses);
数据库查询结果转化为对象
我们在操作数据库的时候,一般都是多表进行联合查询,而不是独立的单个表
但是进行联合查询的时候它返回的数据是平铺的数据,这就不符合我们日常需求
所以需要转换成对象或是数组的形式返回给到前端
JSON_OBJECT
函数
sql
SELECT
products.id AS id,
products.title AS title,
products.price AS price,
JSON_OBJECT(
'id', brans.id,
'name', brans.name,
'sort', brans.sort
) AS brand_info
FROM
products
LEFT JOIN
brans
ON
products.brand_id = brans.id
WHERE
products.price > 5000;
返回结果示例
json
[
{
"id": 1,
"title": "高端笔记本电脑",
"price": 12000,
"brand_info": {
"id": 101,
"name": "Dell",
"sort": 5
}
},
{
"id": 2,
"title": "4K 显示器",
"price": 6000,
"brand_info": null -- 若无匹配品牌,LEFT JOIN 会返回 NULL
}
]
数据库查询数据转化为数组
多对多查询里面的数据转化成数组需要使用 JSON_OBJECT
和 JSON_ARRAYAGG
联合使用
sql
SELECT
s.id AS student_id,
s.name AS student_name,
JSON_ARRAYAGG(
JSON_OBJECT(
'course_id', c.id,
'title', c.title,
'credit', c.credit
)
) AS enrolled_courses
FROM
students s
LEFT JOIN
student_course sc ON s.id = sc.student_id
LEFT JOIN
courses c ON sc.course_id = c.id
GROUP BY
s.id, s.name; -- 按学生分组,聚合课程数据
查询结果示例
json
[
{
"student_id": 1,
"student_name": "张三",
"enrolled_courses": [
{
"course_id": 101,
"title": "数据库原理",
"credit": 3
},
{
"course_id": 102,
"title": "算法设计",
"credit": 4
}
]
},
{
"student_id": 2,
"student_name": "李四",
"enrolled_courses": null -- 未选修任何课程
}
]
-
多对多关系的 JOIN:
- 通过
student_course
中间表连接学生和课程。 - 使用
LEFT JOIN
确保未选课的学生也能被查询到。
- 通过
-
JSON ****聚合函数:
JSON_OBJECT
:将单条课程记录转换为 JSON 对象。JSON_ARRAYAGG
:将所有课程对象聚合成一个 JSON 数组。
-
分组 (
GROUP BY
) :- 必须按学生字段分组(如
s.id
),否则会返回重复的学生记录。
- 必须按学生字段分组(如
-
处理 NULL 值:
- 若学生未选课,
enrolled_courses
会返回NULL
,可通过COALESCE
设置默认空数组:
sqlCOALESCE( JSON_ARRAYAGG(...), JSON_ARRAY() -- 返回空数组 []) AS enrolled_courses
- 若学生未选课,
本文为个人学习记录,内容会根据学习进度不定期补充和更新,难免存在一定的疏漏或错误。若发现任何问题,恳请指正与建议