%% # 纲要 > 主干纲要、Hint/线索/路标 # Q&A #### 已明确 #### 待明确 > 当下仍存有的疑惑 **❓<font color="#c0504d"> 有什么问题?</font>** # Buffer ## 闪念 > sudden idea 1. 从哪些方面去组织笔记? 2. SQL 语法可能很细,笔记只**体现纲要 & 重点**,但要给出详细语法说明的参考信息,例如书籍的附录或者官方文档等。 ## 候选资料 > Read it later %% # 关于 SQL ><font color="#c00000">❓什么是 SQL?</font> **SQL(Structured Query Language,结构化查询语言)** 是用于**管理和操作==关系型数据库==的标准化语言**。 # MySQL 大小写敏感性 | | SQL 命令(关键字) | 数据库名 & 表名 | 表别名 | 列名(字段名) | 变量名 | | --------- | ----------- | --------- | --- | ------- | --- | | Linux 下 | ❌ | ✔️ | ✔️ | ❌ | ✔️ | | Windows 下 | ❌ | ❌ | ❌ | ❌ | ✔️ | - ❌ 表示**大小写不敏感**;✔️表示**严格==区分大小写==** > [!NOTE] **Linux 下大小写敏感性** > > ![[_attachment/02-开发笔记/08-数据库/SQL 语句.assets/IMG-SQL 语句-7C094ED1251AB48A86111E5BCC6B36AA.png|468]] > > Linux 下,"**数据库名和表名**" 区分大小写的原因是 "Linux 的文件系统中对文件名本身是大小写敏感的"。 > <br> # MySQL 系统信息相关命令 ```sql STATUS; -- 查看mysql当前相关信息(版本号, 当前数据库, 连接ID, 连接方式, 端口等等) SELECT version(); -- 查看mysql版本号 ``` <br><br> # SQL 语句基本说明 - **子句**:SQL 语句由子句构成。一个子句通常**由一个关键字和所提供的数据**组成。 - **结束标识符**:sql 命令以 `;` 或 `\g` 或 `\G` 标识结束; - `DELIMITER <char>` 命令可指定 "**结束标识符**",常用于定义 "存储程序" 的场景(执行语句以 `;` 结尾,因此需要更改为结束标识符) - **放弃命令**:sql 命令以`\c` 结尾时,表示 **==放弃本次操作==**。 - **字符串表示**:**字符串需要使用单引号或双引号**,数据库名、表名、列名不用,**除非包含特殊字符**。 - **注释格式**:三种 - 单行注释:使用 `#` 或 `--` 起始(`#` 是 MySQL 特有扩展) - 多行注释:`/* ... */` # 表格字段约束(Contraint) 约束:对**列中存储的数据**进行**限制或者追加条件规则**的功能。 #### MySQL 支持的约束 - `DEFAULT <value>` :指定默认值 - `NULL`:允许 NULL 值(**默认设置**) - `NOT NULL`:要求该字段**非空**,必须有值 - `UNIQUE` 或 `UNIQUE KEY`:声明**该字段的==有效值不可重复==**(允许值为 `NULL` 且重复)——**插入数据时 MySQL 会进行校验** - `CHECK`: - `PRIMARY KEY`:作为**主键**(一张表**最多只能有一个主键**,**主键值不能重复**)——**插入数据时 MySQL 会进行校验 - `CONSTRAINT [外键名称] FOREIGN KEY (列1, ...) REFERENCES 父表名(父列1, 父列2)`:声明**字段为==外键==**——**插入数据时 MySQL 会进行校验** - `AUTO_INCREMENT`:表示**若在插入数据时若未指定该字段值或显式设置为 `NULL` 或`0` 时,则==自增==取值** - `COMMENT <'strng'>`:**列的备注说明** - `ZEROFILE`:对于 **`UNSIGNED`整数类型列**,当**实际位数小于==数据类型指定的最大位数==时,当补上前缀 0**。 > [!NOTE] `DISTINCT` 作用于 `SELECT` 中的 "所有列",而非单个列。 > [!note] 关于主键 > > - 主键和具有`UNIQUE` 属性的列会==自动建立索引== > - 主键天然具有 `NOT NULL` 属性 > [!NOTE] 关于 `AUTO_INCREMENT` > > 1. 每张表中**只允许有一个 `AUTO_INCREMENT`列**。 > 2. `AUTO_INCREMENT` 列**必须建立索引**。 > 3. `AUTO_INCREMENT` 列**不能再通过 `DEFAULT` 指定默认值**。 > 4. 一般拥有 `AUTO_INCREMENT` 属性的列都是作为主键的属性,来自动生成唯一标识一条记录的主键值。 > 5. 对 `AUTO_INCREMENT` 列通过 `INSERT` 插入**有效的指定值**(未使用过的)后,**后续的自动增量**将 **==基于这一值==** 自增。 > > > <br><br><br> # 数据定义语句 ## 数据库管理 #### 增删查改数据库 ```sql # 查看所有数据库 SHOW DATABASES; # 创建数据库 CREATE DATABASES [IF NOT EXISTS] 数据库名; # 使用/切换当前访问的数据库 USE 数据库名; # 删除数据库 DROP DATABASE [IF EXISTS] 数据库名; ``` ## 表格管理 #### 创建表格 ```sql # 创建表格 CREATE TABLE [IF NOT EXITS] 表名 ( 列名1 数据类型 [列的属性], 列名2 数据类型 [列的属性], ... ) [table_option] [partition_option]; # 创建表格示例 CREATE TABLE IF NOT EXISTS first_table ( number INT PRIMARY KEY, # 学号作为主键 name VARCHAR(5), sex ENUM('男', '女'), id_number CHAR(18), department VARCHAR(30), major VARCHAR(30), enrollment_time DATE ) COMMENT '学生基本信息表'; # 表的备注说明 # 创建表格示例 CREATE TABLE IF NOT EXISTS student_score ( number INT, subject VARCHAR(30), score TINYINT, PRIMARY KEY (number, subject) # 设置两个字段值作为主键 CONSTRAINT FOREIGN KEY(number) REFERENCES student_info(number) # 声明外键: 该表中的number字段作为外键, 关联自`student_info`表中的number列。 ); ``` #### 删除表格 ```sql # 删除表格 DROP TABLE [IF EXISTS] 表1, 表2, ... 表n; # 清空表格 (实际过程是删除表格后再创建一张同名表, 而不是逐行删除表中数据) TRUNCATE TABLE; ``` #### 查看表格信息 ![[_attachment/02-开发笔记/08-数据库/SQL 语句.assets/IMG-SQL 语句-00731A59E441F52B1AB2C0BBC7D45563.png|554]] ```sql # 查看当前数据库中的所有表格 SHOW TABLES; # 查看指定数据库中的所有表格 SHOW TABLES FROM 数据库名; # 查看表结构: 各个字段及类型(下列语句等价) # 可为`表名`指示其所属数据库——`[数据库名.]表名` DESC 表名; DESCRIBE 表名; EXPLAIN 表名; SHOW COLUMNS FROM 表名; SHOW FIElDS FROM 表名; # 查看完整的表格创建语句 SHOW CREATE TABLE 表名; # 建议使用\G结尾, 更易于查看: `SHOW CREATE TABLE 表名\G` ``` #### 修改表格信息 > [!NOTE] `ALTER TABLE 表名` 之后可以跟多个对于该表的操作,以逗号 `,` 分隔,例如: > > ```sql > ALTER TABLE first_table DROP COLUMN third_column, DROP COLUMN fourth_column, DROP COLUMN fifth_column; > ``` > > **修改表名** & **转移表所属数据库**: ```sql # 修改表名, 也可转移表所属的数据库 (下列两语句等价) # 方式一: ALTER TABLE 旧表名 RENAME TO 新表名; # 方式二: RENAME TABLE 旧表名1 TO 新表名1, 旧表名2 TO 新表名2, ...; # 支持同时重命名多个表. # 转移表所属的数据库 ALTER TABLE 旧表名 RENAME TO 新数据库.新表名; ``` **更改表格所用的存储引擎**: ```sql # 更改已有表的存储引擎 ALTER TABLE 表名 ENGINE = InnoDB; ``` #### 字段修改 ###### **新增列** `ADD COLUMN` ```sql -- 新增列 ALTER TABLE 表名 ADD COLUMN 列名 数据类型 [列属性]; -- 新增列(添加为第一列) ALTER TABLE 表名 ADD COLUMN 列名 数据类型 [列属性] FIRST; -- 新增列(添加到指定列之后) ALTER TABLE 表名 ADD COLUMN 列名 数据类型 [列属性] AFTER 指定列名; -- 示例: ALTER TABLE vendors ADD vend_phone CHAR(20); ``` ###### **删除列** (`DROP COLUMN`) ```sql -- 删除列 ALTER TABLE 表名 DROP COLUMN 列名; -- 示例: 删除列 ALTER TABLE Vendors DROP COLUMN vend_phone; ``` ###### **修改列信息** (`MODIFY` 或 `CHANGE`) ```sql # 修改字段信息 # 方式一: ALTER TABLE 表名 MODIFY 列名 新数据类型 [新属性]; # 方式二: ALTER TABLE 表名 CHANGE 旧列名 新列名 新数据类型 [新属性]; ``` > [!caution] 修改字段类型时,要求修改后的数据类型必须兼容原类型现有数据!否则 MySQL 会报错。 ###### 增加外键 ```sql -- 向Vendors表中添加外键, 以`Vendors.order_num`作为外键, 键值引用`orders.order_num`. ALTER TABLE Vendors ADD CONSTRAINT fk_orderitems_orders FOREIGN KEY (order_num) REFERENCES orders (order_num); ``` <br><br> ## 视图管理 #### 创建视图 - `CREATE VIEW 视图名[(列名1, 列名2, ...)] AS 查询语句` ```sql -- 创建视图: 为指定查询语句创建一个视图 -- 示例一: 为男生成绩创建一个视图 CREATE VIEW male_student_view AS SELECT s1.number, s1.name, s2.score, s2.subject FROM student_info s1 LEFT JOIN student_score s2 ON s1.number = s2.number WHERE s1.sex = '男'; -- 示例二: 可指定视图的自定义列名 CREATE VIEW student_info_view(no, n, m) AS SELECT number, name, major FROM student_info; ``` #### 删除视图 ```sql -- 删除视图 DROP VIEW 视图名; ``` #### 查看视图 ```sql -- 查看所有表&视图(视图作为虚拟表, 会和真实表一并显示) SHOW TABLES; -- 查看视图的定义 SHOW CREATE VIEW 视图名; ``` #### 修改已有视图(修改其对应的查询语句) - `ALTER VIEW` 或 `CREATE OR REPLACE VIEW ... AS 查询语句` ```sql -- 修改既有视图对应的查询语句 ALTER VIEW view_name AS SELECT ... FROM ... ... -- 修改既有视图对应的查询语句 CREATE OR REPLACE VIEW view_name AS SELECT ... FROM ... ... ``` #### 更新视图(操作数据) **满足特定条件的视图**是可更新的,即**可执行 `INSERT`, `DELETE`, `UPDATE` 语句**,实质上会转换为对 "**底层真实表**" 中数据的**增、删、改**操作。 > [!caution] 可更新的视图 > > 仅基于单表、无复杂聚合的视图是可更新的。 生成视图时使用了下列语句的视图均不可更新: > > - 聚集函数(`AVG`、`MAX`、`SUM`、`COUNT` 等) > - `DISTINCT` > - `GROUP BY` 与 `HAVING` > - `UNION` 或 `UNION ALL` > - 某些子查询 > - 某些连接查询 > - .....等等 > <br><br><br> # 数据操纵语句 #### 插入数据 > [!NOTE] 对于指定了默认值的列,可在 `VALUES` 子句中的括号内使用 `DEFAULT` 关键字,代表该列使用默认值。 > - `INSERT INTO ... VALUES ...`:**存在重复值时报错** ```sql -- 向表中插入数据 (指定要插入数据的列并给出对应值, 未显式指定的列取空值NULL) INSERT INTO 表名(列1, 列2, ...) VALUES (列1值, 列2值, ...), (...), ...; # 向表中插入数据(将查询结果作为值插入) INSERT INTO 表名(列1, 列2, ...) SELECT 列11, 列22, ... -- 检索出的列数量要对齐被插入表中指定的列 FROM ... ... ``` - `INSERT IGNORE INTO ... VALUES ...`:**存在重复值时==忽略==,跳过** ```sql -- 向表中插入数据, `IGNORE`表示示若重复数据值已存在则忽略 INSERT IGNORE INTO 表名(列1, 列2, ...) VALUES (列1值, 列2值)... ``` - `INSERT INTO ... VALUES ... ON DUPLICATE KEY UPDATE ...`:**==更新==重复值**(主键或 `UNIQUE` 列重复时,更新其他指定列的值) ```sql -- 向表中插入数据, 若对于主键或unique列存在中复制, 则更新既有记录中的某些指定列的值 -- 示例一: 对于要插入的数据`(1, '哇哈哈')`,若表中已存在`first_column`的列值为`1`的记录(因为`first_column`列具有`UNIQUE`约束),则将该记录的`second_column`列更新为`'雪碧'`. INSERT INTO first_table (first_column, second_column) VALUES(1, '娃哈哈') ON DUPLICATE KEY UPDATE second_column = '雪碧'. -- 插入时, 若主键重复, 则更新已有记录的`second_column` 为新值. INSERT INTO first_table (first_column, second_column) VALUES(2, '红牛'), (3, '橙汁') ON DUPILICATE KEY UPDATE second_column = VALUES(second_colum); ``` #### 删除数据 - `DELETE FROM 表名 [WHERE 条件]`: **删除符合指定条件的数据**,若**未指定 `WHERE` 子句则表明删除所有数据** ```sql -- 删除所有数据 DELETE FROM first_table -- 删除符合指定条件的数据 DELETE FROM firt_table WHERE first_column > 4; -- 删除指定列值最大的那条记录 DELETE FROM first_table ORDER BY first_column DESC -- 对`first_column`列降序排列, 取最大的一项值. LIMIT 1; ``` #### 更新数据 - `UPDATE [IGNORE] 表名 SET 列1=值1, 列2=值2, ... [WHERE 布尔表达式]` - `IGNORE` : 在更新多行数据时,**忽略出错的行**,而继续执行剩余更新 ```sql -- 更新数据: 将表中为`first_column`为NULL的记录进行更新 UPDATE first_table SET first_column = 5, second_column = '东鹏特饮' WHERE first_column IS NULL; -- 更新数据: 将表中为`first_column`值最大的记录进行更新 UPDATE first_table SET second_column = '椰汁' ORDER BY first_column DESC LIMIT 1; ``` <br><br><br> # 数据查询语句 ### 语句关键字概览 - `SELECT`:指定**检索/查询的列** - `DISTINCT`:**去重** - `AS 别名`:**列别名** - `FROM`:**指定表格** - `WHERE`:指定**过滤条件** - **比较条件**:`=`、`<>` 或 `!=`、`<` , `>` , `<=` , `>=` - **范围区间**: - `BETWEEN a AND b`:目标值位于区间 `[a, b]` - `NOT BETWEEN a AND b`: 目标值位于区间 `(, a)` 或 `(b, )` - **匹配列表项**: - `IN (v1, v2, ...)`:若匹配指定列表中**任一项**则成功 - `NOT INT(v1, v2, ...)`:要求**不匹配指定列表中所有项** - **匹配 `NULL` 值**: - `IS NULL`:为空 - `IS NOT NULL`:非空 - **逻辑操作符**:`AND` 、`OR`,用于 "**组合多个过滤条件**" - **通配符**: - 语句: - `a LIKE b`: - `a NOT LIKE b`: - 通配符: - `%` :代表任意**任意字符出现任意次数**(可为空) - `_` :代表任意**一个字符** - **正则表达式匹配**: - `REGEXP <正则表达式>` - `GROUP BY <列名>`:**分组** - `[HAVING <条件>]`:**作用于分组结果的过滤条件** - `ORDER BY <列名> ASC|DESC`:**排序**(默认`ASC`,从上到下升序排列) - `LIMIT <[起始行,] 限制条数>` :**限制查询结果条数** ##### 执行顺序 > [!NOTE] 执行顺序——先过滤(`WHERE`),再分组(`GROUP BY`),最后再排序(`ORDER BY`) ```sql SELECT [DISTINCT] 列名1 [AS 别名], ... [FROM 表名] [[INNER]|[LEFT|RIGHT [OUTER]]|[CROSS] JOIN 表名] [ON 连接条件] [WHERE 过滤条件] [GROUP BY 列名] [HAVING 分组过滤条件] [ORDER BY 列名] [LIMIT <起始行, 限制条数>] ; ``` ##### 示例 ```sql SELECT * FROM student_score WHERE (score > 95 OR score < 55) AND subject = '论萨达姆的战争准备'; ``` <br> ## 过滤 #### 空值检查 > [!caution] 值为`NULL` 的记录不参与匹配 > > `NULL` 表示 "空值",**==不参与匹配比较==**,因此在 `WHERE` 子句中: > - `<>` 或 `!=` 筛选出的结果集中**不包含值为 `NULL` 的行**。 > - **`LIKE` 与通配符** 也不能匹配到 `NULL`。 > > 如果要包含值为 `NULL` 的行,必须**在过滤条件中显式指明 `or col IS NULL`**,例如: `WHERE col != XXX or col IS NULL` 。 > #### 通配符匹配 **`LIKE` 关键字**作为谓词,**搭配通配符 `%` 或者 `_`** 进行模糊匹配。 #### 正则表达式匹配 > 详细说明参见 [^1](P55) `REGEXP [BINARY] <正则表达式>` 进行**正则表达式匹配**。 - `[BINARY]` 指示 "**正则表达式匹配时==严格区分==大小写**"(默认不区分) ```sql SELECT prod_name FROM products WHERE prod_name REGEXP '.000'; -- 正则表达式匹配, 例如1000和2000都能匹配上. ``` > [!NOTE] 测试正则表达式 > > ![[_attachment/02-开发笔记/08-数据库/SQL 语句.assets/IMG-SQL 语句-16E3D5BCE4B90598609FE67B32A795B5.png|592]] > > <br> ## 排序 SQL 中,`ORDER BY` 子句用于将查询结果**按指定列的顺序排列**(默认==`ASC`==,从上到下**升序**)。 > [!NOTE] `ASC` 与 `DESC` 只作用于 "**其前面的列**" > > 多列排序时,如果**要在每个列上均降序排列**,需**对每个列指定 `DESC` 关键字**。 > [!caution] NULL 值不能被应用比较运算符,因此不参与排序,其总是排列在排序结果集的开头或末尾,汇总显示。 ```sql SELECT column1, column2 FROM table_name ORDER BY column1 [ASC|DESC], column2 [ASC|DESC], ...; -- 支持按多个列排序 ``` 示例:`ORDER BY` 搭配 `LIMIT`,可找出一个列中的最大 or 最小值 ```sql -- 找出分数最高的学生 SELECT number, score FROM student_score ORDER BY score DESC LIMIT 1; ``` <br><br> # 子查询 子查询是嵌套在另一条 SQL 查询中的查询,其**可位于 `SELECT`、`FROM`、`WHERE`、`HAVING` 子句**中。 根据子查询的结果,可分为: | | 说明 | | ----------------------------------- | ----------------------------------------------------------- | | **标量子查询**(Scalar Subquery) | 子查询返回**单个值**,常用于 `SELECT` 或 `WHERE` 子句中。 | | **列子查询**(Column Subquery) | 子查询返回**一列多行**(**一组值**),常用于`WHERE` 子句搭配 `IN`、`ANY`、`ALL` 操作符 | | **行子查询**(Row Subquery) | 子查询返回**一行多列**,常**用于与其他行的比较,必须与主查询中的多列做匹配**。 | | **表子查询**(Table Subquery) | 子查询返回**一张完整的表**(多行多列),通常**用于 `FROM` 语句中**,作为临时表 | | **EXISTS 子查询** | 子查询用于**检测某个条件是否成立**,子查询**结果集非空**时,`EXISTS`表达式为 `TRUE` | | **NOT EXISTS 子查询** | 子查询用于**检测某个条件是否不成立**,子查询**结果集为空**时,`EXISTS`表达式为 `TRUE` | | **不相干子查询**(Non-Correlated Subquery) | 子查询与外层查询**没有关联关系**,**子查询独立运行并产出结果**,外层查询再利用子查询的结果 | | **相关子查询**(Correlated Subquery) | 子查询中需要引用**主查询中的值**,**主查询每处理一行记录,子查询都会被执行一次** | ### 标量子查询 ```sql -- 标量子查询: 子查询返回一个值 -- 示例一: 查找姓名为'YYJ'的学生成绩 -- 内层子查询: 从`student_info`表中根据姓名找到学号, 返回学号. -- 外层查询: 根据学号, 在`student_score`表中找到其所有成绩分数. SELECT * FROM student_score WHERE number = ( SELECT number FROM student_info WHERE name = 'YYJ' ); -- 示例二: 查找student_score表中, '母猪的产后护理'这门课的课程成绩大于平均分的记录 -- 内层子查询: 返回这门课的平均成绩 -- 外层查询: 找出score大于平均成绩的记录 SELECT * FROM student_score WHERE subject = '母猪的产后护理' and score > ( SELECT AVG(SCORE) as avg_score FROM student_score WHERE subject = '母猪的产后护理' ); ``` > [!NOTE] 标量子查询可位于 `SELECT` 子句中,充当一列值 示例: ```sql -- 相关子查询: 子查询返回一个统计值(顾客的订单总数), 作为SELECT输出结果集的一列. SELECT cust_name, cust_state, ( SELECT COUNT(*) FROM orders WHERE orders.cust_id = customers.cust_id ) AS order_cnt FROM customers ORDER BY cust_name; ``` ### 列子查询 ```sql -- 列子查询: 查询结果返回一列多行的一组值. -- 示例一: 查询专业为'计算机科学与工程'的所有学生的成绩 -- 内层子查询: 从`student_info`表中找出所有指定专业学生的学号 -- 外层查询: 根据学生学号, 在`student_score`表中找到其所有成绩分数. SELECT * FROM student_score WHERE number in ( SELECT number FROM student_info WHERE major = '计算机科学与工程' ); -- 示例二: 查询工资大于"指定部门最低工资"的员工姓名. -- 内层子查询: 返回指定部门的员工工资列表 -- 外层子查询: 返回工资大于 "子查询列表中任一值" 的员工姓名 SELECT name FROM employees WHERE salary > ANY ( SELECT DISTINCT salary FROM employees WHERE department_id = 10; ); -- 示例三: 多层嵌套子查询 SELECT cust_name, cust_contact FROM customers WHERE cust_id IN ( SELECT cust_id FROM orders WHERE order_num IN ( SELECT order_num FROM orderitems WHERE prod_id = 'TNT2' ) ); ``` ### 行子查询 ```sql -- 行子查询: 查询结果返回一行多列 -- 内层子查询: 从`employees` 表中找出指定部门中的最高薪资 -- 外层查询: 根据返回的薪资和部门ID, 定位到这个人, 返回其姓名和薪资 SELECT name, salary FROM employees WHERE (salary, department_id) = ( SELECT MAX(salary), department_id FROM employees WHERE department_id = 10 ); ``` ### 表子查询 ```sql -- 表子查询: 子查询返回一张完整的表(多行多列) SELECT * FROM ( SELECT name, salary FROM employees WHERE department_id = 10 ) AS temp_table; ``` ### EXISTS 子查询、NOT EXISTS 子查询 ```sql -- EXISTS 子查询: 子查询用于检测某个条件是否成立. -- 内层子查询: 为外层查询中的每个部门id, 检查其是否同时存在于`employees`表中. -- 外层查询: 返回同时存在于`employees` 与 `department` 两表中的部门的名字. SELECT name FROM department d WHERE EXISTS ( SELECT 1 FROM employees e WHERE e.department_id = d.department_id -- 这里是"相关子查询", 子查询语句中引用了主查询中的值, 主查询每检查一行记录, 子查询都会执行一次. ); ``` ### 相关子查询 ```sql -- 相关子查询: 子查询中需要引用"主查询中的值", 主查询每处理一行记录,子查询都会被执行一次. -- 示例一: 从`student_info`表中, 找出在`student_score`表中存在成绩记录的学生的信息. SELECT number, name, id_number, major FROM student_info si WHERE EXISTS ( SELECT * FROM student_score ss WHERE ss.number = si.number -- 引用主查询中的学生id, 在student_score中查询 ); ``` ### 不相关子查询 ```sql -- 不相关子查询: 子查询独立执行 SELECT * FROM student_score WHERE number = ( SELECT number FROM student_info WHERE name = '杜琦玉' ); ``` <br><br><br> # 连接(联结)查询 SQL 中,连接(JOIN)用于**将多个表中的数据根据某种条件组合/拼接在一起**。 SQL 支持的连接包括以下类型: - **==内连接==**(Inner Join):只返回两个表中**满足连接条件的==交集==**,即**同时存在于两张表中的数据**。如果某行在一张表中没有匹配,则不会出现在结果中。 - **==外连接==**(Outer Join):分为三种: - **左外连接**(Left Join):**以左侧表为"==驱动表==",返回==左表==中的所有记录**,对于在右表中无匹配项的,则右表的列将填充为 `NULL` 值。 - **右外连接**(Right Join):**以右侧表为"==驱动表==",返回==右表==中的所有记录**,对于在左表中无匹配项的,则左表的列将填充为 `NULL` 值。 - **全外连接**(Full Join):**返回两个表中==所有的记录==**,包括在连接条件中**没有匹配**的记录。对于没有匹配的记录,**未匹配的表中的字段会填充 `NULL` 值**。 - **==交叉连接==**(Cross Join):返回**两个表的==笛卡尔积==**(全连接),即**每一条左表的记录都与右表的所有记录组合**,用于**生成所有组合**的情况。 - **自连接**(Self Join):**将==同一张表==进行连接**,通常用于查找表中数据之间的关系 - **自然连接**(Natural Join):自动**根据两个表中==具有相同名字的列==进行连接**,而不需要显式指定连接条件(进行连接的列)。 > [!NOTE] MySQL 中,`JOIN` 关键字等价于 `INNER JOIN` ### 内连接 ```sql -- 内连接使用示例: 查询"已被分配了所属部门"的员工姓名及其所在部门名 -- 对于未分配部门的员工(e.department_id为NULL), 或者部门id无效的员工(e.department_id不在d表中), 将被过滤. SELECT e.name, d.department_name FROM employees e [INNER] JOIN departments d ON e.department_id = d.department_id; -- 内连接的另一种等价写法, `FROM`中给出多个表, 使用`WHERE`子句进行连接过滤 SELECT e.name, d.department_name FROM employees e, department_name d WHERE e.department_id = d.department_id ``` > [!NOTE] 内连接可省略 `JOIN ... ON ...` 关键字,而改用 `WHERE` 子句实现。 示例二 ```sql -- 查询每个顾客的订单总数: -- 将顾客信息表与订单表, 按顾客ID拼接, 再按顾客id分组, 对分组结果应用聚合函数 SELECT c.cust_name, c_cust_id, COUNT(o.order_num) AS num_ord FROM customers AS c INNER JOIN orders AS o ON c.cust_id = o.cust_id GROUP BY c.cust_id ``` ### 外连接 左外连接: ```sql -- 左外连接示例: 返回 `employees` 表中的所有员工,若某个员工没有所属的部门,则员工的 `department_name` 将显示为 `NULL` SELECT e.name, d.department_name FROM employees e LEFT JOIN departments d ON e.department_id = d.department_id ``` 右外连接: ```sql -- 右外连接示例: 返回 `departments` 表中的所有部门,若某个部门没有员工, 则部门的 `employees.name` 将显示为 `NULL`。 SELECT e.name, d.department_name FROM employees e RIGHT JOIN departments d ON e.department_id = d.department_id ``` 全外连接: ```sql -- 返回所有员工和所有部门,若某个员工没有部门, 或某个部门没有员工, 则对应字段将显示为 `NULL` SELECT e.name d.deparment_name FROM employees e FULL [OUTER] JOIN ``` ### 交叉连接 > [!NOTE] **内连接 `[INNER] JOIN` 在未通过 `ON` 指定连接条件时,即等价于 `CROSS JOIN`** ```sql -- 交叉连接的几种等价写法 SELECT * FROM t1 CROSS JOIN t2; SELECT * FROM t1 [INNER] JOIN t2; SELECT * FROM t1, t2; ``` ### 自连接 示例一: ```sql -- 自连接示例一: 返回每个员工的直接上级. SELECT e1.name as Employee, e2.name as Manager FROM employee e1 JOIN employee e2 ON e1.manager_id = e2.employee_id; -- 自连接示例二: 找出`DTNTR`的商品供应商所生产的所有产品 SELECT p1.prod_id, p1.prod_name FROM products AS p1 INNER JOIN products AS p2 ON p1.vend_id = p2.vend_id WHERE p2.prod_id = 'DTNTR'; -- 使用where语句的等价写法: SELECT p1.prod_id, p1.prod_name FROM products AS p1, products AS p2 WHERE p1.vend_id = p2.vend_id AND p2.prod_id = 'DTNTR'; ``` ### 连接与子查询的等价使用 有的查询需求既可以使用连接查询解决,也可以使用子查询解决: ```sql -- 查找'计算机科学与工程'专业学生的考试成绩 -- 使用子查询 (注: 返回结果中, 不包含"缺考"的同学, 即`student_score`中无成绩的同学) SELECT * FROM student_score WHERE number IN ( SELECT number FROM student_info WHERE major = '计算机科学与工程' ) -- 使用连接: SELECT ss.*, FROM student_score ss RIGHT JOIN student_info si # 使用右外连接, 使结果中包含缺考的同学, 其分数将显示为NULL. ON ss.number = si.number WHERE si.major = '计算机科学与工程'; ``` <br><br><br> # 分组查询 分组查询(`GROUP BY`) 用于将查询结果**按一个或多个列的值**进行分组。 - **`SELECT` 语句中的每个列,除聚合函数语句外,都必须==同时在 `GROUP BY` 子句中给出==**。 - 如果分组列具有 `NULL` 值,则 **所有`NULL` 值将作为一个==独立分组==** 返回。 ### 聚合函数 聚合函数与分组查询结合使用时,将**分别==应用于每个分组内==** 进行统计。 对于 "**多列分组**" 的情况,聚合函数作用于 "**最后一列**" 的分组结果。 示例: ```sql -- 统计每个部门的平均薪资 SELECT departmend_id, AVG(salary) FROM employees GROUP BY department_id; -- 统计各个部门中, 每个岗位的薪酬总支出 SELECT department_id, job_title, SUM(salary) FROM employees GROUP BY department_id, job_title; ``` ### 过滤分组结果——HAVING 子句 **`HAVING`** 是用于**对==分组结果==进行过滤的子句**,类似于 `WHERE`,但 `HAVING` **作用于分组之后**。 示例: ```sql -- 找出至少具有2笔订单的所有顾客 SELECT cust_id, COUNT(*) AS order_cnt FROM orders GROUP BY cust_id HAVING COUNT(*) >= 2; ``` ###### WHERE 与 HAVING > [!NOTE] `WHERE` 与 `HAVING` 的区别 > > - `WHERE` 子句是对 "**==原始记录条目==**" 进行过滤,作用于分组前; > - `HAVING` 子句是对 "**==分组结果==**" 进行过滤,作用于分组后的记录 <br>(用法:`GROUP BY ... HAVING ...` ) 示例一: ```sql -- 过滤掉低于 60 分的记录后, 再统计平均分 SELECT subject, AVG(score) FROM student_score WHERE score > 60; -- GROUP BY subject; -- 查询平均分大于 70 分的课程 SELECT subject, AVG(score) FROM student_score GROUP BY subject HAVING AVG(score) > 70; -- 过滤掉平均分<=70 的分组记录 ``` 示例二: ```sql -- 列出具有2个及以上的, 价格在10元及以上商品的供应商 SELECT vend_id, COUNT(*) AS num_prods FROM products WHERE prod_price >= 10 -- 筛选出价格在10元及以上的商品 GROUP BY vend_id -- 按供应商分组 HAVING COUNT(*) >= 2; -- 筛选出, 具有2个及以上的10元以上商品的供应商. ``` <br><br><br> # 组合查询 - `UNION [ALL]`:将两个或多个 `SELECT` 查询的结果集进行组合,并返回所有查询的结果——**两个结果集的==并集==**。 - `UNION` 会**去除重复记录**,`UNION ALL` 会**包含所有重复记录**。 - `INTERSECT`:返回两个 `SELECT` 语句中都存在的共同记录,即**两个结果集的==交集==**。 - `INTERSECT` **仅在某些 SQL 实现中(如 PostgreSQL、Oracle)被支持**,但 **MySQL 不直接支持** - `EXCEPT` 或 `MINUS`:**返回第一个 `SELECT` 查询中有但第二个 `SELECT` 查询中没有的记录**,即**两个结果集的==差集==**。 - `EXCEPT` 通常在 PostgreSQL 中使用,`MINUS` 常见于 Oracle。 > [!NOTE] "组合查询" 使用注意 > > - 组合查询要求**每个 `SELECT` 语句必须==具有相同的列、表达式或聚集函数==(顺序不一定相同),且==各个列的数据类型必须兼容==**。 > - **最后的结果集展示的 "列名" 是==第一个查询中给定的列名==**。 > - 组合查询中 **`ORDER BY` 子句只能放置在==最后一条 `SELECT` 语句之后==**,表示 **==对整个结果集排序==**(不支持仅对部分结果排序) <br><br><br> # 表达式 表达式可以用在两个地方: - 放在 **`SELECT` 之后的==查询列表==中** - 放在 **`WHERE` 之后作为==搜索条件==** ### CASE 表达式 ![[_attachment/02-开发笔记/08-数据库/SQL 练习.assets/IMG-SQL 练习-C54B6C25DEECD86908D24520AE433D7F.png|603]] ![[_attachment/02-开发笔记/08-数据库/SQL 语句.assets/IMG-SQL 语句-1973EBA6CBAE1F82403D27531FEA7CB7.png|516]] <br><br><br> # MySQL 函数 ## 聚合函数 **聚合函数/聚集函数**(Aggregation function)用于获取 "**统计**" 结果。 MySQL 提供了一系列聚合函数[^1](P76),最主要的 5 个: ![[_attachment/02-开发笔记/08-数据库/SQL 语句.assets/IMG-SQL 语句-8294530DC0F948CE179F46CB13D4E07F.png|602]] > [!NOTE] 聚合函数对 `NULL` 值的处理 > > - `AVG`, `MAX` , `MIN`, `SUM` 都会**忽略值为 `NULL` 行**; > - `COUNT(*)`: 统计 **"行"数目**,**==计入 `NULL`==** > - `COUNT(列名)`:统计指**定列中具有 "有效值" 的行数目**,**忽略该列值为 `NULL` 的行** > > > > [!NOTE] 聚合函数中可应用 `DISTINCT` 关键字,表示**统计时不计入重复行** > > `DISTINCT` 实际上只对 `AVG` 和 `COUNT` 有意义。 > > ```mysql > -- 统计平均值时, 去掉`DISTINCT`列 > SELECT AVG(DISTINCT prod_price) AS avg_price > FROM products > WHERE vend_id = 1003; > ``` > > ## 数值处理函数 ![[_attachment/02-开发笔记/08-数据库/SQL 语句.assets/IMG-SQL 语句-88E24CE8CCA98559045A7775ED9D26EB.png|647]] ## 文本处理函数 ![[_attachment/02-开发笔记/08-数据库/SQL 语句.assets/IMG-SQL 语句-793F234DD88069E7F93948F87BC17F73.png|736]] ## 日期和时间处理函数 ### 函数说明 ![[_attachment/02-开发笔记/08-数据库/SQL 语句.assets/IMG-SQL 语句-09F7A5841FD434D1F444905E667ECA59.png|618]] 使用示例: ```sql -- 查询指定日期的订单记录 SELECT cust_id, order_num FROM orders WHERE Date(order_date) = '2005-09-01'; -- 查询指定月份内的所有订单(方式一) SELECT cust_id, order_num FROM orders WHERE Date(order_date) BETWEEN '2005-09-01' AND '2005-09-30'; -- 查询指定月份内的所有订单(方式二) SELECT cust_id, order_num FROM orders WHERE Year(order_date) = 2005 AND Month(order_date) = 9; ``` ### 支持的时间单位 ```sql # 令指定时间`2019-08-16 17:10:43` 增加2分钟 SELECT DATE_ADD('2019-08-16 17:10:43', INTERVAL 2 MINUTE); ``` ### 日期时间格式符 ![[_attachment/02-开发笔记/08-数据库/SQL 语句.assets/IMG-SQL 语句-862255DC03B83998D5FFECA3A271EE80.png|417]] ```sql -- 显示: 'Sep 19 2024 09:07 PM' SELECT DATE_FORMAT(NOW(), '%b %d %Y %h:%i %p'); ``` ### 系统函数 **系统函数**:返回 DBMS 正使用的特殊信息(例如返回用户登录信息,检查版本细节) ```sql SELECT version(); -- `version()`即为系统函数 ``` <br><br><br> # MySQL 变量 MySQL 中可以定义变量,支持多种变量类型,可用于不同上下文: - **用户定义变量**(User-Defined Variables):使用 `@` 作为前缀,变量值**可用于 SQL 语句中**。 - **局部变量**(Local Variables):在**存储过程或函数**中定义和使用,在 `BEGIN` 和 `END` 块中**使用 `DECLARE` 关键字定义** - **系统变量**(System Variables):**用于==配置 MySQL 服务器的参数和状态信息==** - **全局系统变量**:影响整个服务器,所有会话都会受到影响。 - **会话系统变量**:仅影响当前会话 #### 用户定义变量 使用 `@` 作为前缀,存储在**会话级别**(同一个数据库连接期间) ```sql -- 定义用户变量 SET @var_name = value; SET @var_name = @other_var; SET @var_name = (SELECT ... ); -- 将查询语句的结果(单个值)赋给变量 (SELECT ...) INTO @var_name; -- 将查询语句的结果(单个值)赋给变量 (SELECT col1, col2 ...) INTO @var1, @var2; -- `INTO`支持将单行多列的查询结果赋给多个变量 -- 查看变量值 SELECT @var_name; ``` > [!caution] MySQL 中没有直接查看 "所有用户定义变量" 的命令,只能通过 `SELECT` 指明需要查看的变量值. #### 局部变量 「**局部变量**」在**存储过程或存储函数**中使用,在 `BEGIN` 和 `END` 块中**使用 ==`DECLARE` 关键字==定义**,只在**存储过程、函数或触发器**的执行期间有效。 ```sql -- "局部变量" 仅在存储过程或存储函数中使用 CREATE PROCEDURE example_procedure() BEGIN DECLARE counter INT DEFAULT 0; -- 声明局部变量 `counter` SET counter = counter + 1; SELECT counter; END; ``` #### 系统变量 「**系统变量**」**用于==配置 MySQL 服务器的参数和状态信息==**。包括两类: - **全局系统变量**:影响整个服务器,所有会话都会受到影响。 - **会话系统变量**:仅影响当前会话 ```sql -- 查看所有系统变量 SHOW VARIABLES; -- 查看指定系统变量(模糊匹配) SHOW VARIABLES LIKE ...; -- 设置会话级系统变量 SET SESSION var_name = value; -- 示例: SET SESSION sql_mode = 'STRICT_TRANS_TABLES'; -- 设置全局系统变量 SET GLOBAL var_name = value; -- 示例: SET GLOBAL max_connections = 1000; ``` <br><br> # FAQ # 参考资料 - [MySQL :: MySQL 5.7 Reference Manual :: 13 SQL Statements](https://dev.mysql.com/doc/refman/5.7/en/sql-statements.html) # Footnotes [^1]: 《**MySQL 必知必会**》