文章汇总
MySQL基础:数据库与数据表的概念与操作
概括总结对MySQL数据库与数据表的增删改查操作与相关概念
## 数据库的增删改查操作 ```sql -- 显示所有已存在的数据库(用于查看当前数据库环境中的数据库列表) SHOW DATABASES; -- 获取当前正在使用的数据库名称(用于验证当前会话的数据库上下文) SELECT DATABASE(); -- 切换到名为demo的数据库 USE demo; -- 创建名为demo的数据库(如果已存在则不执行任何操作) CREATE DATABASE IF NOT EXISTS demo [CHARACTER SET utf8mb4] -- 可选字符集声明(默认为utf8mb4) [COLLATE utf8mb4_general_ci];-- 可选排序规则声明(默认为utf8mb4_general_ci) -- 删除名为demo的数据库(如果不存在则报错) DROP DATABASE demo; -- 安全删除数据库(如果不存在则忽略错误) DROP DATABASE IF EXISTS demo; -- 修改demo数据库的字符集和排序规则配置 ALTER DATABASE demo CHARACTER SET utf8mb4 -- 使用utf8mb4字符集(支持更多Unicode字符,包括Emoji) COLLATE utf8mb4_0900_ai_ci; -- 使用最新的排序规则(ai=True表示大小写不敏感) ``` --- ## SQL数据类型 ### 常见的SQL数据类型 - 数字类型:整型、浮点型、定点型等。 - 字符串类型:字符型、文本型、枚举型、集合型等。 - 日期时间类型:日期型、日期时间型、时间戳型等。 --- ### 数据类型的属性解释 - `NULL`:数据列可包含NULL值。 - `NOT NULL`:数据列不允许包含NULL值。 - `DEFAULT`:默认值。 - `PRIMARY KEY`:KEY 主键。 - `AUTO_INCREMENT`:自动递增,适用于整数类型。 - `UNSIGNED`:是指数值类型只能为正数。 - `CHARACTER SET name`:指定一个字符集。 - `COMMENT`:对表或者字段说明。 --- ### 整数类型 - 专门用来保存整数 - 区分有符号和无符号,默认就是有符号的 - 可以在数据类型后加上 unsigned 表示是无符号的 - 在设置整型时,可以设置将来显示的位宽,不足的补足,超出不管。 - 默认填充的是空格。 - tinyint(2):设置位宽是 2,如果查询的是1,显示的就是' 1'。 | 类型 | 字节 | 有符号取值范围 | 无符号取值范围 | 说明 | 典型应用场景 | | ------------- | ---- | ------------------------------------------------------ | ------------------------------ | -------- | ------------------------ | | **tinyint** | 1 | -128 ~ 127 | 0 ~ 255 | 超小整数 | 状态码、布尔标志 | | **smallint** | 2 | -32,768 ~ 32,767 | 0 ~ 65,535 | 小整数 | 计数器、评分等级 | | **mediumint** | 3 | -3,888,608 ~ 3,888,607 | 0 ~ 16,777,215 | 中等整数 | 分区标识、中等规模计数器 | | **int** | 4 | -2,147,483,648 ~ 2,147,483,647 | 0 ~ 4,294,967,295 | 标准整数 | 主键自增、常规数值存储 | | **bigint** | 8 | -9,223,372,036,854,775,808 ~ 9,223,372,036,854,775,807 | 0 ~ 18,446,744,073,709,551,615 | 大整数 | 用户ID、时间戳、金融计算 | --- ### 浮点类型 - 专门保存小数,会丢失精度 - 不要用来保存不希望丢失精度的数据 | 类型 | 字节 | 精度(有效数字位数) | 说明 | | ---------- | ---- | -------------------- | ------------------------------------------------------------ | | **float** | 4 | 6 | 单精度浮点数(±1.17549435×10⁻³⁸ ~ ±3.4028235×10³⁸) | | **double** | 8 | 16 | 双精度浮点数(±2.225073858497757×10⁻³⁰⁸ ~ ±1.7976931348623157×10³⁰⁸) | --- ### 定点类型 - 专门保存小数,不会丢失精度 - 可以用来保存不希望丢失精度的数据 | 类型 | 说明 | | ----------- | ------------------------------------------------------------ | | **DECIMAL** | `DECIMAL(size, d)`● `size`:数字总位数(整数部分 + 小数部分),● `d`:小数部分位数● **约束条件**: - `size ≥ d + 1` - `d ≥ 0` - `size`最大支持38位 | --- ### 字符串类型 | 数据类型 | 说明 | | --------------------- | ------------------------------------------------------------ | | **char(size)** | 字符型,固定长度字符串,可包含字母、数字及特殊字符。size范围:0~255 | | **varchar(size)** | 字符型,可变长度字符串,可包含字母、数字及特殊字符。size范围:0~65535 | | **tinytext** | 文本类型,长度为0~255的字符串文本 | | **text** | 文本类型,长度为0~65535的字符串文本 | | **mediumtext** | 文本类型,长度为0~16,777,215的字符串文本 | | **longtext** | 文本类型,长度为0~4,294,967,295的字符串文本 | | **enum(v1, v2, ...)** | 枚举类型,插入数据必须匹配预定义值列表中的其中一个值,示例:`ENUM('男', '女')` | | **set(v1, v2, ...)** | 集合类型,插入数据可匹配预定义值列表中的一个或多个值,示例:`SET('A', 'B', 'C')` | --- ### 日期时间类型 - 专门用来保存日期和时间 - datetime 和 timestamp 可以自动初始化和更新为当前日期时间 | 类型 | 格式 | 取值范围 | 说明 | | ------------- | ------------------------- | --------------------------------------------------- | ------------------------------------------------------ | | **year** | `YYYY` | `1901~2155` 或 `0000` | 年份类型,支持四位数字(MySQL 5.7+ 默认范围1901-2155) | | **date** | `YYYY-MM-DD` | `1000-01-01 ~ 9999-12-31` | 日期类型,包含年月日(范围比MySQL默认的1900-0000更广) | | **time** | `hh:mm:ss` 或 `hhh:mm:ss` | `-838:59:59 ~ 838:59:59` | 时间类型,支持负数时间表示凌晨(三位小时格式更易读) | | **datetime** | `YYYY-MM-DD HH:mm:ss` | `1000-01-01 00:00:00 ~ 9999-12-31 23:59:59` | 日期时间组合类型,精度到秒 | | **timestamp** | `YYYY-MM-DD HH:mm:ss` | `1970-01-01 00:00:01 UTC ~ 2038-01-19 03:14:07 UTC` | 时间戳类型,基于Unix时间戳(注意2038年溢出问题) | --- ## 数据表的增删改查操作 ```sql -- 获取当前连接的数据库名称(用于确认当前操作的数据库环境) SELECT DATABASE(); -- 切换到名为 `demo` 的数据库(若数据库不存在会报错) USE demo; -- 查看当前数据库中的所有表(需要 SELECT 权限) SHOW TABLES; /* * 创建表 * 注意: * 1. 最后一列后不能有逗号 * 2. 推荐添加主键约束和默认值 */ CREATE TABLE student ( id INT UNSIGNED, -- 无符号整数类型(0-4294967295) name VARCHAR(20), -- 变长字符串(最多20字符),建议添加 NOT NULL 约束 age TINYINT UNSIGNED, -- 无符号小整数(0-255),适合年龄字段 gender ENUM('男', '女', '保密'), -- 枚举类型(仅允许指定值) createdAt TIMESTAMP -- 时间戳类型(记录创建时间) ); /* * 创建表(如果不存在则跳过,避免重复创建报错) * 适用于需要初始化脚本的场景 */ CREATE TABLE IF NOT EXISTS student ( id INT UNSIGNED, name VARCHAR(20), age TINYINT UNSIGNED, gender ENUM('男', '女', '保密'), createdAt TIMESTAMP ); -- 查看数据表的创建语句,包括表的结构和属性 SHOW CREATE TABLE student; -- 查看表结构 DESCRIBE student; -- 删除表(执行后不可逆!) DROP TABLE student; -- 安全删除表(如果不存在也不报错) DROP TABLE IF EXISTS student; -- 修改表名(新表名不能已存在) ALTER TABLE student RENAME TO stu; /* * 添加字段 */ ALTER TABLE student ADD updatedAt TIMESTAMP; -- 删除字段(需确保字段存在) ALTER TABLE student DROP updatedAt; -- 修改字段类型 ALTER TABLE student MODIFY createdAt DATETIME; -- 同时修改字段名和类型 ALTER TABLE student CHANGE createdAt createAt TIMESTAMP; ``` --- ## SQL约束 ### **1. 主键约束 (PRIMARY KEY)** - **作用**:唯一标识表中每一行,确保数据完整性。 - **特点**: - 必须唯一且非空 (`NOT NULL + UNIQUE`)。 - 一个表只能有一个主键,但可包含多列(复合主键)。 - **示例**: ```sql CREATE TABLE users ( id INT AUTO_INCREMENT PRIMARY KEY, username VARCHAR(50) UNIQUE NOT NULL ); ``` --- ### **2. 外键约束 (FOREIGN KEY)** - **作用**:建立表间关系,强制引用完整性。 - **特点**: - 仅InnoDB引擎支持。 - 外键列值必须匹配被引用表的主键/唯一索引。 - 可定义级联操作(如 `ON DELETE CASCADE`)。 - **示例**: ```sql CREATE TABLE orders ( order_id INT PRIMARY KEY, user_id INT, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE ); ``` --- ### **3. 唯一性约束 (UNIQUE)** - **作用**:确保列或列组合的值唯一。 - **特点**: - 允许NULL值(除非同时加 `NOT NULL`)。 - 多个唯一约束可共存。 - **示例**: ```sql CREATE TABLE emails ( email VARCHAR(100) UNIQUE, phone VARCHAR(20) UNIQUE ); ``` --- ### **4. 非空约束 (NOT NULL)** - **作用**:强制列必须填写值,禁止NULL。 - **特点**: - 常与 `DEFAULT` 结合使用(如 `NOT NULL DEFAULT 'value'`)。 - 唯一约束允许NULL时,`NOT NULL` 会覆盖此行为。 - **示例**: ```sql CREATE TABLE products ( name VARCHAR(50) NOT NULL, price DECIMAL(10,2) NOT NULL DEFAULT 0.00 ); ``` --- ### **5. 默认值约束 (DEFAULT)** - **作用**:当未指定列值时自动填充默认值。 - **特点**: - 数据类型需与列匹配。 - 适用于`INSERT`操作,不影响`UPDATE`(除非显式设置)。 - **示例**: ```sql CREATE TABLE logs ( created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); ``` --- ### **6. 检查约束 (CHECK)** - **作用**:限制列的取值范围或格式。 - **特点**: - MySQL语法支持,但实际由存储引擎处理(如InnoDB部分支持)。 - 跨字段逻辑可能失效,需结合触发器或应用层校验。 - **示例**: ```sql CREATE TABLE students ( age INT CHECK (age BETWEEN 18 AND 100) ); ``` --- ### **7. 自增约束 (AUTO_INCREMENT)** - **作用**:为主键列自动生成递增整数。 - **特点**: - 仅适用于整数类型列。 - 默认从1开始,可自定义起始值。 - **示例**: ```sql CREATE TABLE posts ( id INT AUTO_INCREMENT PRIMARY KEY, title VARCHAR(200) ); ``` --- ### **约束对比** | 约束类型 | 唯一性 | 非空 | 允许NULL | 复合支持 | 引擎依赖 | | -------------- | ------ | ---- | -------- | -------- | -------- | | PRIMARY KEY | ✔️ | ✔️ | ❌ | ✔️ | 所有 | | FOREIGN KEY | - | - | - | ❌ | InnoDB | | UNIQUE | ✔️ | ❌ | ✔️ | ✔️ | 所有 | | NOT NULL | - | ✔️ | ❌ | ❌ | 所有 | | DEFAULT | - | - | ✔️ | ❌ | 所有 | | CHECK | - | - | ✔️ | ✔️ | 部分引擎 | | AUTO_INCREMENT | - | ❌ | ❌ | ❌ | 所有 | --- ## 外键约束 ### 什么是外键? **外键(Foreign Key)** 是一个字段(或一组字段),它的值必须匹配另一张表中的 **主键(Primary Key)** 的值。 👉 目的:确保两张表之间的数据关联关系正确,防止「无效引用」。 - 外键通常定义在从表(子表)中,用于引用主表(父表)的主键或唯一键。 - 在一对多关系中,外键始终在“多”的一方(从表)创建。 --- ### 基础外键约束 ```sql CREATE TABLE orders ( order_id INT PRIMARY KEY, user_id INT, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE ON UPDATE CASCADE ); ``` --- ### 复合外键约束 ```sql CREATE TABLE order_details ( order_id INT, product_id INT, quantity INT, PRIMARY KEY (order_id, product_id), FOREIGN KEY (order_id) REFERENCES orders(order_id) ON DELETE CASCADE, FOREIGN KEY (product_id) REFERENCES products(product_id) ON DELETE RESTRICT ); ``` --- ### 外键的常见规则 1. **匹配主键** 外键字段的值必须完全匹配主表主键的值(包括类型和长度)。 2. **级联操作(可选)** 可以通过 `ON DELETE` 和 `ON UPDATE` 控制关联行为。 3. **外键操作类型** - 严格操作:`restrict` - 主表中不存在对应的数据,从表不允许添加 - 从表引用着数据,主表对应的数据不允许删除 - 从表引用着数据,主表对应的主键不允许修改 - 置空操作:`set null` - 修改或删除主表 id 时,所有跟它关联的从表字段都会被设置为null。 - 级联操作:`cascade` - 修改或删除主表 id 时,所有跟它关联的从表字段都会做同样的操作。 --- ### 何时用外键? - 当两张表有明确的 **一对多关系** 时(如班级和学生)。 - 需要严格确保数据关联时(如订单和商品)。 - 不推荐过度使用:外键会增加写操作的开销,需权衡灵活性与一致性。

MySQL基础:数据操作查询的方式
概括总结对MySQL中数据的增删改查与条件、排序、分页、聚合、分组、多表的查询操作
## 表中数据的增删改查操作 ### **1. 增(Create)** - **关键字**:`INSERT INTO ... VALUES ...` - `INSERT INTO`:指定插入的目标表 - `VALUES`:定义要插入的具体值 - **示例**: ```sql INSERT INTO users (name, age) VALUES ('张三', 25); ``` --- ### **2. 删(Delete)** - **关键字**:`DELETE FROM ... WHERE ...` - `DELETE FROM`:指定要删除数据的表 - `WHERE`:条件筛选(⚠️ **不加条件会清空全表!**) - **示例**: ```sql DELETE FROM users WHERE id = 100; ``` --- ### **3. 改(Update)** - **关键字**:`UPDATE ... SET ... WHERE ...` - `UPDATE`:指定要修改的表 - `SET`:设置新值 - `WHERE`:条件筛选(⚠️ **不加条件会更新全表!**) - **示例**: ```sql UPDATE users SET age = 26 WHERE name = '张三'; ``` --- ### **4. 查(Read)** - **关键字**:`SELECT ... FROM ... WHERE ...` - `SELECT`:指定要查询的列(`*` 表示所有列) - `FROM`:指定查询的表 - `WHERE`:条件筛选 - `ORDER BY`:排序(`ASC` 升序,`DESC` 降序) - `LIMIT`:限制返回的行数 - **示例**: ```sql SELECT name, age FROM users WHERE age > 20 ORDER BY id DESC LIMIT 10; ``` --- ### 清空表(高危操作!) ```sql TRUNCATE TABLE users; -- 不可回滚,谨慎使用! ``` --- ## 基础查询和Where条件查询 ### **一、基本查询操作(SELECT)** 语法: ```sql SELECT 列1, 列2, ... FROM 表名 [WHERE 条件] [其他子句...]; ``` #### **常见用法** 1. **查询所有列**: ```sql SELECT * FROM employees; ``` 2. **查询指定列**: ```sql SELECT name, salary FROM employees; ``` 3. **去重查询(DISTINCT)**: ```sql SELECT DISTINCT department FROM employees; ``` 4. **计算列(表达式或函数)**: ```sql SELECT name, salary * 1.1 AS new_salary FROM employees; ``` --- ### **二、WHERE 子句** `WHERE` 子句用于筛选满足条件的记录,支持多种运算符和逻辑组合。 #### **1. 比较运算符** - `=`:等于 ```sql SELECT * FROM employees WHERE salary = 5000; ``` - `<>` 或 `!=`:不等于 - `>`、`<`、`>=`、`<=`:数值或日期比较 ```sql SELECT * FROM employees WHERE hire_date > '2020-01-01'; ``` - `BETWEEN ... AND ...`:范围查询 ```sql SELECT * FROM employees WHERE salary BETWEEN 4000 AND 6000; ``` #### **2. 逻辑运算符** - `AND`:同时满足多个条件 ```sql SELECT * FROM employees WHERE department = 'IT' AND salary > 5000; ``` - `OR`:满足任意一个条件 ```sql SELECT * FROM employees WHERE department = 'HR' OR department = 'Sales'; ``` - `NOT`:取反条件 ```sql SELECT * FROM employees WHERE NOT department = 'IT'; ``` #### **3. 模糊查询(LIKE)** - `%`:匹配任意字符(包括空字符) ```sql SELECT * FROM employees WHERE name LIKE 'J%'; -- 以 J 开头(如 John, Jane) SELECT * FROM products WHERE name LIKE '%apple%'; -- 包含 "apple"(如 Pineapple, ApplePie) ``` - `_`:匹配单个字符 ```sql SELECT * FROM employees WHERE name LIKE '_a%'; -- 第二个字符是 a(如 Sam, David) SELECT * FROM users WHERE phone LIKE '138-____-____'; -- 固定格式(如 138-1234-5678) ``` - **转义特殊字符**(如 `%` 或 `_`): ```sql SELECT * FROM discounts WHERE remark LIKE '%10!%%' ESCAPE '!'; -- 匹配包含 "10%" 的文本 ``` - **不区分大小写**: ```sql SELECT * FROM users WHERE username LIKE '%admin%' COLLATE utf8_general_ci; -- 匹配 Admin, ADMIN ``` #### **4. 集合查询(IN)** 筛选字段值在指定集合中的记录: ```sql SELECT * FROM employees WHERE department IN ('IT', 'HR'); ``` #### **5. 处理 NULL 值** - `IS NULL`:判断为空 ```sql SELECT * FROM employees WHERE manager_id IS NULL; ``` - `IS NOT NULL`:判断非空 ```sql SELECT * FROM employees WHERE email IS NOT NULL; ``` --- ### **三、WHERE 子句的注意事项** 1. **执行顺序**:`WHERE` 在数据筛选阶段生效,早于 `GROUP BY`、`ORDER BY` 等子句。 2. **性能优化**: • 对索引列使用 `WHERE` 条件可提高效率。 • 避免在条件中对列做函数操作(如 `WHERE YEAR(date) = 2023`)。 3. **模糊查询性能**: • `LIKE '%keyword%'` 会导致全表扫描,建议对高频搜索字段使用全文索引。 • 前导通配符(如 `LIKE '%abc'`)无法利用索引。 --- ## 排序和分页查询 ### **一、排序(ORDER BY)** 1. **基本语法** ```sql SELECT 列名 FROM 表名 ORDER BY 列1 [ASC|DESC], 列2 [ASC|DESC], ...; ``` • `ASC`:升序(默认),`DESC`:降序。 • 支持多列排序,优先级按列顺序依次降低。 2. **示例** ```sql SELECT name, salary FROM employees ORDER BY salary DESC, hire_date ASC; -- 先按工资降序,再按入职时间升序 ``` 3. **注意事项** - **索引优化**:排序字段若没有索引,大数据量时可能导致性能问题(需磁盘文件排序)。 - **稳定性**:若排序字段有重复值,结果顺序可能不稳定,可添加唯一字段(如主键)作为次要排序条件。 --- ### **二、分页查询(LIMIT & OFFSET)** 1. **基本语法** ```sql SELECT 列名 FROM 表名 LIMIT 每页条数 OFFSET 跳过行数; ``` - 简写形式:`LIMIT 跳过行数, 每页条数`(如`LIMIT 20, 10`表示跳过20行,取10行)。 2. **示例** ```sql SELECT * FROM products ORDER BY price DESC LIMIT 10 OFFSET 20; -- 获取第3页数据(每页10条) ``` 3. **性能优化** - **深分页问题**:`OFFSET`值过大时(如`LIMIT 100000, 10`),MySQL需扫描大量数据后跳过,效率低下。 - **优化方案**: - **基于游标的分页**:记录上一页最后一条数据的唯一标识(如自增ID),下页查询时使用`WHERE id > last_id LIMIT 10`。 - **子查询优化**: ```sql SELECT * FROM table WHERE id >= (SELECT id FROM table ORDER BY id LIMIT 100000, 1) ORDER BY id LIMIT 10; ``` --- ### **三、排序 + 分页的常见场景** 1. **典型查询** ```sql SELECT id, title, created_at FROM articles ORDER BY created_at DESC LIMIT 10 OFFSET 0; -- 首页按时间倒序排列 ``` 2. **需注意的问题** - **结果一致性**:若数据在分页间变化(如新增/删除),可能导致重复或遗漏。 - **覆盖索引**:为排序和过滤字段创建联合索引(如`(created_at, id)`),避免回表查询。 --- ### **四、高级用法(MySQL 8.0+)** 1. **窗口函数分页** 使用`ROW_NUMBER()`实现灵活分页(适用于复杂排序逻辑): ```sql SELECT * FROM ( SELECT *, ROW_NUMBER() OVER (ORDER BY salary DESC) AS row_num FROM employees ) AS tmp WHERE row_num BETWEEN 21 AND 30; ``` --- ### **总结** - **排序**:用`ORDER BY`控制结果顺序,注意索引优化。 - **分页**:用`LIMIT`和`OFFSET`,但深分页需优化。 - **组合使用**:通常先排序再分页,确保结果顺序稳定。 - **性能关键**:尽量通过索引减少全表扫描和文件排序,避免`OFFSET`过大时的性能瓶颈。 --- ## 聚合函数 ### **一、常用聚合函数** | 函数 | 作用 | 示例(假设表 `sales`) | | -------------------- | ---------------------- | ------------------------------------------ | | **`COUNT()`** | 统计行数(可过滤NULL) | `SELECT COUNT(*) FROM sales;` | | **`SUM()`** | 数值列求和 | `SELECT SUM(amount) FROM sales;` | | **`AVG()`** | 数值列平均值 | `SELECT AVG(price) FROM sales;` | | **`MAX()`** | 返回列最大值 | `SELECT MAX(sale_date) FROM sales;` | | **`MIN()`** | 返回列最小值 | `SELECT MIN(quantity) FROM sales;` | | **`GROUP_CONCAT()`** | 合并分组字符串 | `SELECT GROUP_CONCAT(product) FROM sales;` | | **`STD()`** | 标准差 | `SELECT STD(revenue) FROM sales;` | --- ### **二、核心使用场景** 1. **与 `GROUP BY` 结合** ```sql SELECT product_type, COUNT(*) AS total_sales, AVG(price) -- 使用 AS 关键字 给统计列字段定义别名 FROM sales GROUP BY product_type; -- 按商品类型分组统计 ``` 2. **过滤聚合结果(`HAVING`)** ```sql SELECT user_id, SUM(amount) AS total_spent FROM orders GROUP BY user_id HAVING total_spent > 1000; -- 筛选消费超过1000的用户 ``` 3. **去重统计(`DISTINCT`)** ```sql SELECT COUNT(DISTINCT user_id) FROM orders; -- 统计唯一用户数 ``` --- ### **三、注意事项** 1. **`NULL` 值的处理** - 除 `COUNT(*)` 外,其他聚合函数默认忽略 `NULL` 值。 - 示例:`AVG(score)` 仅计算非 NULL 值的平均数。 2. **`GROUP BY` 的隐式排序** - MySQL 8.0 前,`GROUP BY` 默认按分组字段排序;8.0 后取消该特性。若需排序,显式使用 `ORDER BY`。 3. **`WHERE` 与 `HAVING` 的区别** - `WHERE`:在分组前过滤行(不能使用聚合函数)。 - `HAVING`:在分组后过滤聚合结果(可使用聚合函数)。 4. **聚合函数嵌套限制** - 聚合函数不能直接嵌套(如 `SUM(AVG(price))`),需通过子查询实现。 --- ### **四、性能优化** 1. **索引对聚合的影响** - `MIN()`/`MAX()` 在索引列上效率极高,几乎无需扫描数据。 - `COUNT(列)` 比 `COUNT(*)` 慢(需检查 NULL)。 2. **减少全表扫描** - 尽量通过 `WHERE` 缩小聚合范围: ```sql SELECT AVG(score) FROM users WHERE age > 18; ``` 3. **避免 `GROUP BY` 的临时表** - 使用 `EXPLAIN` 检查执行计划,确保分组字段有索引。 --- ### **五、扩展用法** 1. **组合聚合函数** ```sql SELECT COUNT(*) AS total_orders, SUM(amount) AS total_revenue, AVG(amount) AS avg_order_value FROM orders; ``` 2. **与 `WITH ROLLUP` 生成小计** ```sql SELECT product_type, SUM(quantity) FROM sales GROUP BY product_type WITH ROLLUP; -- 添加汇总行 ``` 3. **窗口函数中的聚合**(MySQL 8.0+) ```sql SELECT order_id, amount, SUM(amount) OVER (PARTITION BY user_id) AS user_total FROM orders; ``` --- ### **总结** - **核心用途**:统计、汇总、分组分析数据。 - **性能关键**:合理使用索引,减少全表扫描。 - **易错点**:`NULL` 值处理、`HAVING` 与 `WHERE` 混淆、隐式排序依赖。 --- ## 分组和HAVING条件查询 ### **一、分组(GROUP BY)** #### 1. **作用** 将数据按指定列的值分组,对每组进行聚合统计(如计数、求和、平均值等)。 #### 2. **基本语法** ```sql SELECT 分组列, 聚合函数(列) FROM 表名 GROUP BY 分组列1, 分组列2...; ``` #### 3. **示例** 统计每个部门的员工数量和平均工资: ```sql SELECT department_id, COUNT(*) AS employee_count, -- 配合聚合函数使用 AVG(salary) AS avg_salary FROM employees GROUP BY department_id; ``` #### 4. **关键特性** - **多列分组**:可指定多个分组列(如 `GROUP BY department_id, job_title`)。 - **隐式去重**:分组后每组仅保留唯一值组合。 - **NULL 值处理**:所有 `NULL` 值会被归为同一组。 --- ### **二、HAVING 条件查询** #### 1. **作用** 过滤分组后的结果(类似 `WHERE`,但作用于分组后的数据)。 #### 2. **基本语法** ```sql SELECT 分组列, 聚合函数(列) FROM 表名 GROUP BY 分组列 HAVING 条件; -- 条件可包含聚合函数 ``` #### 3. **示例** 筛选出员工数量超过 5 人且平均工资高于 8000 的部门: ```sql SELECT department_id, COUNT(*) AS employee_count, AVG(salary) AS avg_salary FROM employees GROUP BY department_id HAVING employee_count > 5 AND avg_salary > 8000; ``` --- ### **三、WHERE 与 HAVING 的核心区别** | 特性 | **WHERE** | **HAVING** | | -------------------- | -------------------- | -------------------------- | | **执行阶段** | 分组前过滤行 | 分组后过滤组 | | **是否可用聚合函数** | 否(直接操作原始列) | 是(操作分组后的聚合结果) | | **性能影响** | 减少分组处理的数据量 | 仅过滤最终结果 | --- ### **四、使用场景与技巧** #### 1. **典型场景** - 统计业务指标(如用户订单数、商品销售额)。 - 数据分层分析(如按年龄段、地区分组统计)。 #### 2. **优化技巧** - **索引优化**:为分组字段创建索引(如 `department_id`)可加速分组操作。 - **减少分组列**:分组列越多,计算开销越大。 - **先 WHERE 再 HAVING**:先用 `WHERE` 过滤无关数据,减少分组计算量。 #### 3. **结合 ORDER BY** 分组后排序结果: ```sql SELECT department_id, AVG(salary) AS avg_salary FROM employees GROUP BY department_id HAVING avg_salary > 5000 ORDER BY avg_salary DESC; ``` --- ### **五、常见错误与注意事项** 1. **SELECT 列未分组或聚合** ```sql -- 错误示例:name 未在 GROUP BY 或聚合函数中 SELECT name, AVG(salary) FROM employees GROUP BY department_id; ``` - **解决**:仅选择分组列或聚合函数结果。 2. **混淆 WHERE 与 HAVING** ```sql -- 错误示例:在 WHERE 中使用聚合函数 SELECT department_id FROM employees WHERE AVG(salary) > 5000; ``` - **解决**:聚合条件必须放在 `HAVING` 中。 3. **NULL 值的分组统计** - 若分组列包含 `NULL`,所有 `NULL` 值会被归为同一组。 --- ### **六、扩展用法** #### 1. **WITH ROLLUP 小计汇总** 生成分组小计和总计行: ```sql SELECT department_id, COUNT(*) FROM employees GROUP BY department_id WITH ROLLUP; -- 结果包含每个部门的人数及总人数 ``` #### 2. **多级分组筛选** 按多列分组并筛选: ```sql SELECT country, city, COUNT(*) AS user_count FROM users GROUP BY country, city HAVING user_count > 100; ``` --- ### **总结** - **GROUP BY**:按列分组并进行聚合计算,核心用于数据汇总。 - **HAVING**:过滤分组后的结果,条件可包含聚合函数。 - **最佳实践**:优先用 `WHERE` 减少数据量,合理使用索引优化分组性能。 --- ## 多表查询 ### **一、多表查询类型及语法** #### 1. **内连接(INNER JOIN)** - **作用**:返回两表中匹配的行(交集)。 - **语法**: ```sql SELECT 列名 FROM 表1 INNER JOIN 表2 ON 表1.列 = 表2.列; ``` - **示例**: ```sql SELECT orders.order_id, customers.name FROM orders INNER JOIN customers ON orders.customer_id = customers.id; ``` #### 2. **左外连接(LEFT JOIN)** - **作用**:返回左表全部行 + 右表匹配行(不匹配的右表字段为 `NULL`)。 - **特点**: - 左边的表是不看条件的,无论条件是否满足,都会返回左表中的所有数据。 - 只有右边的表会看条件,对于右表,只有满足条件的,才会返回。 - **语法**: ```sql SELECT 列名 FROM 表1 LEFT JOIN 表2 ON 表1.列 = 表2.列; ``` - **示例**(查询所有员工及其部门,包括未分配部门的员工): ```sql SELECT employees.name, departments.dept_name FROM employees LEFT JOIN departments ON employees.dept_id = departments.id; ``` #### 3. **右外连接(RIGHT JOIN)** - **作用**:返回右表全部行 + 左表匹配行(不匹配的左表字段为 `NULL`)。 - **特点**: - 右边的表是不看条件的,无论条件是否满足,都会返回右表中的所有数据。 - 只有左边的表会看条件,对于左表,只有满足条件的,才会返回。 - **语法**:类似 `LEFT JOIN`,方向相反。 #### 4. **全外连接(FULL OUTER JOIN)** - **作用**:返回两表所有行(不匹配的字段为 `NULL`)。 - **注意**:MySQL 不直接支持,需用 `LEFT JOIN + RIGHT JOIN + UNION` 模拟。 #### 5. **交叉连接(CROSS JOIN)** - **作用**:返回两表的笛卡尔积(所有可能的行组合)。 - **语法**: ```sql SELECT 列名 FROM 表1 CROSS JOIN 表2; ``` - **慎用**:数据量大时性能极差。 #### 6. **UNION** - **UNION 的作用** 将 **多个 SELECT 语句的结果集** 合并为一个结果集,并自动去重(除非使用 `UNION ALL`)。 - **基本语法** ```sql SELECT 列1, 列2 FROM 表1 UNION [ALL] SELECT 列1, 列2 FROM 表2 [UNION [ALL] SELECT 列1, 列2 FROM 表3 ...]; ``` - **`UNION`**:合并结果并去重。 - **`UNION ALL`**:合并结果但保留重复行(性能更高)。 - **列要求**:所有 `SELECT` 的列数、顺序和数据类型必须一致(列名可以不同)。 --- ### **二、多表查询场景** #### 1. **关联数据查询** 如订单表关联客户表,获取订单对应的客户信息。 #### 2. **数据补全** 使用 `LEFT JOIN` 确保主表数据完整(如统计所有商品,包括未销售的)。 #### 3. **多条件连接** ```sql SELECT * FROM table1 JOIN table2 ON table1.id = table2.id AND table1.status = 'active'; ``` #### 4. **自连接** 同一表内关联(如员工表查员工及其经理): ```sql SELECT e.name, m.name AS manager FROM employees e LEFT JOIN employees m ON e.manager_id = m.id; ``` --- ### **三、多表查询的联合使用** #### 1. **多表连接(3张表以上)** ```sql SELECT orders.id, customers.name, products.product_name FROM orders JOIN customers ON orders.customer_id = customers.id JOIN products ON orders.product_id = products.id; ``` #### 2. **结合聚合函数** 统计每个客户的订单总金额: ```sql SELECT customers.name, SUM(orders.amount) AS total FROM customers LEFT JOIN orders ON customers.id = orders.customer_id GROUP BY customers.id; ``` #### 3. **子查询作为临时表** ```sql SELECT a.name, b.avg_salary FROM employees a JOIN (SELECT dept_id, AVG(salary) AS avg_salary FROM employees GROUP BY dept_id) b ON a.dept_id = b.dept_id; ``` --- ### **四、性能优化与注意事项** 1. **索引优化** - 为连接条件列(如 `customer_id`)和筛选条件列创建索引。 2. **避免笛卡尔积** - 确保 `JOIN` 时明确指定 `ON` 或 `WHERE` 条件。 3. **优先使用 `INNER JOIN`** - 比 `OUTER JOIN` 效率更高,尽量减少 `NULL` 值处理。 4. **减少连接表的数量** - 多表连接时,剔除无关表或字段。 5. **分阶段查询** - 对复杂查询拆分为多个步骤,用临时表存储中间结果。 --- ### **五、常见错误** 1. **未指定连接条件** ```sql -- 错误!产生笛卡尔积(性能灾难) SELECT * FROM orders, customers; ``` 2. **混淆 `ON` 和 `WHERE`** - `ON` 用于定义连接条件,`WHERE` 用于过滤结果。 3. **字段歧义** - 多表存在同名字段时需指定表名前缀: ```sql SELECT orders.id, customers.id -- 明确指定表名 FROM orders JOIN customers ... ``` --- ### **总结** - **核心原则**:明确连接类型,写清连接条件,避免笛卡尔积。 - **优化关键**:索引、减少连接表数量、优先 `INNER JOIN`。 - **实际应用**:结合业务场景选择 `JOIN` 或子查询。 --- ## 子查询 ### **一、子查询的定义** 子查询是 **嵌套在其他 SQL 语句(如 `SELECT`、`WHERE`、`FROM` 等)中的查询**,用于辅助主查询完成复杂的数据筛选或计算。 --- ### **二、子查询的分类** #### 1. **按返回结果类型分类** | 类型 | 说明 | 示例(假设表 `employees`) | | -------------- | ------------------------ | ------------------------------------------------------------ | | **标量子查询** | 返回单个值(一行一列) | `SELECT name FROM employees WHERE salary = (SELECT MAX(salary) FROM employees);` | | **列子查询** | 返回一列数据(多行一列) | `SELECT name FROM employees WHERE id IN (SELECT manager_id FROM departments);` | | **行子查询** | 返回一行数据(一行多列) | `SELECT * FROM employees WHERE (salary, age) = (SELECT MAX(salary), MIN(age) FROM employees);` | | **表子查询** | 返回多行多列(临时表) | `SELECT * FROM (SELECT name, salary FROM employees) AS tmp WHERE salary > 5000;` | #### 2. **按执行时机分类** - **非关联子查询**:子查询可独立执行,不依赖主查询。 ```sql SELECT name FROM employees WHERE salary > (SELECT AVG(salary) FROM employees); ``` - **关联子查询**:子查询依赖主查询的字段值,需与外层查询交互。 ```sql SELECT e1.name FROM employees e1 WHERE salary > (SELECT AVG(salary) FROM employees e2 WHERE e2.dept_id = e1.dept_id); ``` --- ### **三、子查询的使用场景** #### 1. **在 `WHERE` 中过滤数据** ```sql -- 查询工资高于平均工资的员工 SELECT name, salary FROM employees WHERE salary > (SELECT AVG(salary) FROM employees); ``` #### 2. **在 `FROM` 中作为临时表(派生表)** ```sql -- 统计每个部门的平均工资 SELECT dept_id, avg_salary FROM (SELECT dept_id, AVG(salary) AS avg_salary FROM employees GROUP BY dept_id) AS dept_stats WHERE avg_salary > 8000; ``` #### 3. **在 `SELECT` 中作为计算字段** ```sql -- 查询员工姓名及其部门平均工资 SELECT name, salary, (SELECT AVG(salary) FROM employees e2 WHERE e2.dept_id = e1.dept_id) AS dept_avg FROM employees e1; ``` #### 4. **与 `IN`、`EXISTS` 配合使用** ```sql -- 查询有订单的客户 SELECT name FROM customers WHERE id IN (SELECT customer_id FROM orders); -- 使用 EXISTS(推荐高效写法) SELECT name FROM customers c WHERE EXISTS (SELECT 1 FROM orders o WHERE o.customer_id = c.id); ``` --- ### **四、子查询的注意事项** 1. **性能问题** - 关联子查询可能效率较低(需多次执行),尽量优化为 `JOIN` 或非关联子查询。 - 使用 `EXISTS` 替代 `IN` 可提升性能(尤其对大数据集)。 2. **子查询结果限制** - 标量子查询必须返回单值,否则会报错(如 `WHERE salary = (SELECT ...)` 中子查询返回多行)。 3. **避免过度嵌套** - 多层嵌套子查询会降低可读性和维护性,可拆分为临时表或公共表达式(CTE,MySQL 8.0+)。 4. **NULL 值处理** - 子查询中包含 `NULL` 时,`IN` 和 `NOT IN` 可能返回意外结果(如 `NOT IN (1, NULL)` 始终为 `FALSE`)。 --- ### **五、子查询 vs 连接查询(JOIN)** | **场景** | **子查询** | **JOIN** | | ------------ | ---------------------------- | ---------------------------- | | **结果需求** | 需要中间值(如聚合结果) | 需要直接关联字段的完整行数据 | | **可读性** | 适合简单逻辑(如单条件过滤) | 适合多表复杂关联 | | **性能** | 关联子查询可能较慢 | 通常更高效(尤其有索引时) | --- ### **六、高级用法(MySQL 8.0+)** #### **公共表达式(CTE)** 使用 `WITH` 子句简化复杂子查询: ```sql WITH dept_avg AS ( SELECT dept_id, AVG(salary) AS avg_salary FROM employees GROUP BY dept_id ) SELECT * FROM dept_avg WHERE avg_salary > 10000; ``` --- ### **总结** - **核心作用**:辅助主查询完成复杂逻辑,如过滤、计算中间值。 - **优先选择**:非关联子查询 > 关联子查询,能用 `JOIN` 时优先用 `JOIN`。 - **性能关键**:避免多层嵌套,优化为 `EXISTS` 或 `JOIN`。 - **适用场景**:动态条件过滤、数据分阶段处理、结果集复用。

MySQL基础:多表查询案例
概括总结对MySQL中多表查询的三种情况与规则
### **一、一对一关系** #### **场景** 用户表(`users`)与用户详情表(`user_details`),每个用户对应一条详情记录。 #### **表结构与外键** ```sql -- 用户表(主表) CREATE TABLE users ( user_id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL ); -- 用户详情表(从表) CREATE TABLE user_details ( user_id INT PRIMARY KEY, -- 主键 + 外键,确保一对一 email VARCHAR(100), phone VARCHAR(20), FOREIGN KEY (user_id) REFERENCES users(user_id) ON DELETE CASCADE ); ``` #### **查询示例** ```sql -- 查询用户及其详情(内连接) SELECT u.username, ud.email, ud.phone FROM users u INNER JOIN user_details ud ON u.user_id = ud.user_id; ``` --- ### **二、一对多关系** #### **场景** 部门表(`departments`)与员工表(`employees`),一个部门有多个员工。 #### **表结构与外键** ```sql -- 部门表(主表) CREATE TABLE departments ( dept_id INT PRIMARY KEY AUTO_INCREMENT, dept_name VARCHAR(50) NOT NULL ); -- 员工表(从表) CREATE TABLE employees ( emp_id INT PRIMARY KEY AUTO_INCREMENT, emp_name VARCHAR(50) NOT NULL, dept_id INT, -- 外键指向部门表 FOREIGN KEY (dept_id) REFERENCES departments(dept_id) ON DELETE SET NULL ); ``` #### **查询示例** ```sql -- 查询所有部门及其员工(左连接,包括无员工的部门) SELECT d.dept_name, e.emp_name FROM departments d LEFT JOIN employees e ON d.dept_id = e.dept_id; ``` --- ### **三、多对多关系** #### **场景** 学生表(`students`)与课程表(`courses`),一个学生可选修多门课程,一门课程可被多个学生选修。 #### **表结构与外键** ```sql -- 学生表(主表) CREATE TABLE students ( student_id INT PRIMARY KEY AUTO_INCREMENT, student_name VARCHAR(50) NOT NULL ); -- 课程表(主表) CREATE TABLE courses ( course_id INT PRIMARY KEY AUTO_INCREMENT, course_name VARCHAR(50) NOT NULL ); -- 中间表(选课关系表) CREATE TABLE student_courses ( student_id INT, course_id INT, PRIMARY KEY (student_id, course_id), -- 联合主键 FOREIGN KEY (student_id) REFERENCES students(student_id) ON DELETE CASCADE, FOREIGN KEY (course_id) REFERENCES courses(course_id) ON DELETE CASCADE ); ``` #### **查询示例** ```sql -- 查询学生及其选修的课程(三表连接) SELECT s.student_name, c.course_name FROM students s INNER JOIN student_courses sc ON s.student_id = sc.student_id INNER JOIN courses c ON sc.course_id = c.course_id; ``` --- ### **四、外键创建的核心规则** 1. **语法** ```sql FOREIGN KEY (当前表列名) REFERENCES 目标表(目标表列名) [ON DELETE {CASCADE | SET NULL | NO ACTION}] [ON UPDATE {CASCADE | NO ACTION}] ``` 2. **注意事项** - **数据类型**:外键列与目标列的数据类型必须一致。 - **索引**:外键列会自动创建索引(MySQL 隐式处理)。 - **级联操作**: - `ON DELETE CASCADE`:主表删除记录时,从表关联记录自动删除。 - `ON DELETE SET NULL`:主表删除记录时,从表外键字段设为 `NULL`。 --- ### **五、总结** | **关系类型** | **外键位置** | **典型场景** | | ------------ | ------------------------ | -------------------------- | | 一对一 | 从表主键同时作为外键 | 主表与扩展表(如用户详情) | | 一对多 | 从表添加外键指向主表主键 | 部门与员工、分类与商品 | | 多对多 | 中间表创建双外键 | 学生选课、用户角色分配 | - **查询逻辑**: - 一对一/一对多用 `INNER JOIN` 或 `LEFT JOIN`。 - 多对多通过中间表多次 `INNER JOIN`。 - **外键约束**:确保数据一致性,避免脏数据。

MySQL基础:事务
MySQL的事务的概念与操作
### MySQL 事务 #### 1. 事务的概念 事务(Transaction)是数据库操作的最小逻辑单元,由一组 SQL 语句组成。事务具有 **ACID** 特性: - **原子性(Atomicity)**:事务要么全部执行成功,要么全部失败回滚。 - **一致性(Consistency)**:事务执行前后,数据库状态保持一致(如约束、触发器等)。 - **隔离性(Isolation)**:多个事务并发执行时,互不干扰。 - **持久性(Durability)**:事务提交后,修改永久保存到数据库。 --- #### 2. 事务控制语句 - `START TRANSACTION` 或 `BEGIN`:显式开启事务。 - `COMMIT`:提交事务,永久保存修改。 - `ROLLBACK`:回滚事务,撤销所有未提交的操作。 - `SAVEPOINT`:设置保存点,允许部分回滚。 --- #### 3. 事务示例 ##### 示例 1:转账操作(原子性) ```sql START TRANSACTION; -- 账户 A 扣款 100 UPDATE account SET balance = balance - 100 WHERE user_id = 'A'; -- 账户 B 收款 100 UPDATE account SET balance = balance + 100 WHERE user_id = 'B'; -- 检查是否出错 IF (无错误) THEN COMMIT; -- 提交事务 ELSE ROLLBACK; -- 回滚事务 END IF; ``` ##### 示例 2:订单与库存一致性 ```sql START TRANSACTION; -- 插入订单记录 INSERT INTO orders (order_id, product_id, quantity) VALUES (1001, 1, 2); -- 减少库存 UPDATE products SET stock = stock - 2 WHERE product_id = 1; -- 若库存不足或其他错误 IF (库存 < 0) THEN ROLLBACK; -- 回滚,取消订单和库存修改 ELSE COMMIT; -- 提交事务 END IF; ``` ##### 示例 3:保存点(Partial Rollback) ```sql START TRANSACTION; INSERT INTO logs (message) VALUES ('Step 1'); SAVEPOINT step1; INSERT INTO logs (message) VALUES ('Step 2'); SAVEPOINT step2; -- 回滚到 step1,撤销 Step 2 的操作 ROLLBACK TO step1; COMMIT; -- 最终只有 Step 1 被提交 ``` --- #### 4. 事务隔离级别 MySQL 支持 4 种隔离级别(默认是 `REPEATABLE READ`): 1. **READ UNCOMMITTED**:可能读到未提交的数据(脏读)。 2. **READ COMMITTED**:只能读到已提交的数据。 3. **REPEATABLE READ**(默认):保证同一事务多次读取结果一致。 4. **SERIALIZABLE**:完全串行化,避免并发问题。 设置隔离级别: ```sql SET TRANSACTION ISOLATION LEVEL READ COMMITTED; ``` --- #### 5. 注意事项 - **隐式提交**:部分语句(如 `ALTER TABLE`)会自动提交当前事务。 - **避免长事务**:长时间未提交的事务可能导致锁竞争和性能问题。 - **死锁处理**:MySQL 会自动检测死锁并回滚其中一个事务。 --- 通过事务,可以确保复杂操作的可靠性和数据一致性,尤其在金融、电商等高并发场景中至关重要。

MySQL:索引
MySQL的索引的概念与操作
### MySQL索引 #### 1. **索引的作用** - **加速查询**:通过减少全表扫描,快速定位数据,类似于书籍目录。 - **约束数据**:如唯一索引保证列值的唯一性,主键索引标识唯一记录。 #### 2. **索引类型** - **主键索引(PRIMARY KEY)**:唯一且非空,每个表只能有一个,InnoDB中为聚簇索引(数据存储在B+树叶子节点)。 - **唯一索引(UNIQUE)**:列值唯一,允许空值,用于避免重复数据。 - **普通索引(INDEX)**:无唯一性限制,单纯加速查询。 - **全文索引(FULLTEXT)**:针对文本内容分词搜索,适用于`MATCH AGAINST`操作,InnoDB(5.6+)和MyISAM支持。 - **空间索引(SPATIAL)**:用于地理数据类型(如GEOMETRY),仅MyISAM支持。 - **复合索引**:多列组合的索引,遵循最左前缀原则。 #### 3. **数据结构** - **B+树索引**(默认): - 支持范围查询、排序、模糊匹配(如`LIKE 'prefix%'`)。 - 叶子节点存储数据或主键(聚簇索引 vs 非聚簇索引)。 - **哈希索引**: - 仅支持精确查询(=, IN),速度快但不支持排序/范围查询。 - 显式支持于Memory引擎,InnoDB有自适应哈希索引(内部自动管理)。 - **其他**:R树(空间索引)、倒排索引(全文搜索)。 #### 4. **优点与缺点** - **优点**: - 大幅提升查询效率,减少磁盘I/O。 - 唯一性约束保证数据完整性。 - 覆盖索引可避免回表,提升性能。 - **缺点**: - 占用额外存储空间。 - 增删改操作需维护索引,降低写性能。 - 不合理的索引设计可能拖慢查询。 #### 5. **使用注意事项** - **适用场景**: - WHERE、JOIN、ORDER BY、GROUP BY中的高频列。 - 高选择性列(如用户ID)优于低选择性列(如性别)。 - **索引优化**: - **最左前缀原则**:复合索引`(a,b,c)`需从最左列开始使用,如`a=1 AND b=2`生效,但`b=2`不生效。 - **覆盖索引**:查询字段均包含在索引中,避免回表。 - **前缀索引**:对长字符串前N个字符建索引,节省空间(如`INDEX(email(10))`)。 - **避免索引失效**:如对索引列使用函数、类型转换、`LIKE '%后缀'`、OR连接非索引列等。 - **维护技巧**: - 定期分析表(`ANALYZE TABLE`)更新统计信息。 - 使用`EXPLAIN`查看执行计划,优化查询语句。 - 删除冗余或使用率低的索引。 #### 6. **示例与操作** - **创建索引**: ```sql CREATE INDEX idx_name ON table(column); ALTER TABLE table ADD UNIQUE (column); ``` - **查看索引**: ```sql SHOW INDEX FROM table; ``` - **删除索引**: ```sql DROP INDEX idx_name ON table; ``` #### 7. **总结** 合理设计索引是数据库性能优化的关键。需权衡查询加速与写操作成本,结合业务场景选择索引类型和列,并通过执行计划分析持续调优。避免过度索引,优先考虑高频、高选择性查询条件,并利用复合索引和覆盖索引提升效率。

MyBatis用法教程
在SpringBoot中集成MyBatis,并且进行增删改查操作
### **一、Spring Boot 集成 MyBatis** #### **1. 添加依赖** ```xml <!-- pom.xml --> <dependencies> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>3.0.3</version> </dependency> <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <scope>runtime</scope> </dependency> </dependencies> ``` #### **2. 配置文件** ```yaml # application.yml spring: datasource: url: jdbc:mysql://localhost:3306/test?useSSL=false&serverTimezone=UTC username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver mybatis: mapper-locations: classpath:mapper/*.xml # 指定 XML 映射文件路径 configuration: map-underscore-to-camel-case: true # 开启驼峰命名映射 ``` #### **3. 实体类** ```java @Data public class User { private Long id; private String name; private Integer age; private String email; } ``` --- ### **二、增删改查示例** #### **1. Mapper 接口定义** ```java @Mapper public interface UserMapper { // 插入用户(返回自增主键) @Options(useGeneratedKeys = true, keyProperty = "id") @Insert("INSERT INTO user(name, age, email) VALUES(#{name}, #{age}, #{email})") int insert(User user); // 根据 ID 删除用户 @Delete("DELETE FROM user WHERE id = #{id}") int deleteById(Long id); // 更新用户(动态 SQL 示例) @UpdateProvider(type = UserSqlBuilder.class, method = "buildUpdateSql") int update(User user); // 根据 ID 查询用户 @Select("SELECT * FROM user WHERE id = #{id}") User selectById(Long id); // 分页查询用户(结合 PageHelper) @Select("SELECT * FROM user") List<User> selectAll(); } ``` #### **2. XML 映射文件示例** ```xml <!-- resources/mapper/UserMapper.xml --> <mapper namespace="com.example.mapper.UserMapper"> <!-- 动态更新 SQL --> <update id="update" parameterType="User"> UPDATE user <set> <if test="name != null">name = #{name},</if> <if test="age != null">age = #{age},</if> <if test="email != null">email = #{email},</if> </set> WHERE id = #{id} </update> </mapper> ``` #### **3. Service 层调用** ```java @Service @RequiredArgsConstructor public class UserService { private final UserMapper userMapper; public void addUser(User user) { userMapper.insert(user); } public void deleteUser(Long id) { userMapper.deleteById(id); } public void updateUser(User user) { userMapper.update(user); } public User getUserById(Long id) { return userMapper.selectById(id); } public List<User> getAllUsers() { return userMapper.selectAll(); } } ``` --- ### **三、动态 SQL 实战** #### **1. 使用 `<if>` 和 `<where>`** ```xml <!-- 动态条件查询 --> <select id="selectByCondition" resultType="User"> SELECT * FROM user <where> <if test="name != null">AND name LIKE CONCAT('%', #{name}, '%')</if> <if test="age != null">AND age = #{age}</if> </where> </select> ``` #### **2. 批量插入** ```xml <insert id="batchInsert"> INSERT INTO user(name, age, email) VALUES <foreach collection="list" item="user" separator=","> (#{user.name}, #{user.age}, #{user.email}) </foreach> </insert> ``` #### **3. 注解方式动态 SQL** ```java // 使用 @SelectProvider 动态生成 SQL public class UserSqlBuilder { public String buildUpdateSql(User user) { return new SQL() .UPDATE("user") .SET("name = #{name}") .SET("age = #{age}") .WHERE("id = #{id}") .toString(); } } ``` --- ### **四、高级功能整合** #### **1. 分页插件(PageHelper)** ```yaml # application.yml pagehelper: helper-dialect: mysql reasonable: true support-methods-arguments: true ``` ```java // Service 层分页查询 public PageInfo<User> getUsersByPage(int pageNum, int pageSize) { PageHelper.startPage(pageNum, pageSize); List<User> users = userMapper.selectAll(); return new PageInfo<>(users); } ``` #### **2. 事务管理** ```java @Service @Transactional // 声明式事务 public class UserService { public void transferMoney(Long fromId, Long toId, BigDecimal amount) { userMapper.deductBalance(fromId, amount); userMapper.addBalance(toId, amount); } } ``` #### **3. 多数据源配置** ```yaml # application.yml spring: datasource: primary: url: jdbc:mysql://localhost:3306/db1 username: root password: 123456 secondary: url: jdbc:mysql://localhost:3306/db2 username: root password: 123456 ``` ```java @Configuration @MapperScan(basePackages = "com.example.mapper.primary", sqlSessionFactoryRef = "primarySqlSessionFactory") public class PrimaryDataSourceConfig { // 配置 primary 数据源和 SqlSessionFactory } @Configuration @MapperScan(basePackages = "com.example.mapper.secondary", sqlSessionFactoryRef = "secondarySqlSessionFactory") public class SecondaryDataSourceConfig { // 配置 secondary 数据源和 SqlSessionFactory } ``` --- ### **五、常见问题与解决** #### **1. Mapper 接口未被扫描** • 确保主类添加 `@MapperScan("com.example.mapper")`。 #### **2. 参数绑定错误** • 多参数时使用 `@Param` 注解: ```java @Select("SELECT * FROM user WHERE name = #{name} AND age = #{age}") User findByNameAndAge(@Param("name") String name, @Param("age") Integer age); ``` #### **3. 事务失效** • 确保 `@Transactional` 注解在 `public` 方法上,且未被同类方法内部调用。 --- ### **六、完整代码结构示例** ``` src/main/java ├── com.example │ ├── Application.java # Spring Boot 主类 │ ├── entity │ │ └── User.java # 实体类 │ ├── mapper │ │ ├── UserMapper.java # Mapper 接口 │ │ └── primary # 多数据源示例 │ ├── service │ │ └── UserService.java # Service 层 │ └── config # 配置类(多数据源等) resources ├── application.yml # 配置文件 └── mapper └── UserMapper.xml # XML 映射文件 ```

解决Element表格展开行异步获取数据页面不更新
el-table在展开行时如果从接口异步获取数据,会造成数据渲染不同步从而不触发页面更新
在Vue2版本的el-table的@expand-change="expandChange"事件里,如果使用了接口异步获取数据,会出现第一次点开展开按钮没有数据,第二次展开才渲染出数据的情况。 ### 问题原因 **这是因为在接口里给列表的新增属性赋值时后添加的属性没有被Vue的对象代理给检测到** ```js expandChange(row, expandedRows) { this.fetchData() .then(res => { row.newList = res // 直接给row对象里的新属性赋值,这个属性是不会有响应式的 }) } ``` ### 解决方法 在获取列表时,给列表中的每个对象提前定义好属性。 ```js // 使用map新返回一个数组并赋值给tableData this.tableData = apiData.map(item => { return { ...item, newList: [], // 需要新增的属性 } }) // 或者使用forEach搭配this.$set()使用 this.tableData = data; this.tableData.forEach(item => { // item.newList= []; // 不能直接给新属性赋值,一样是无效的必须使用this.$set() this.$set(item, 'newList', []); // 必须使用this.$set()才能使属性具有响应式 }); ``` 之后在@expand-change事件里就可以直接使用row.newList进行赋值,页面便会触发响应式更新 ```js expandChange(row, expandedRows) { this.fetchData() .then(res => { row.newList= res // row里的newList已经被提前声明,具有响应式后就可以直接赋值触发页面更新了 }) } ```

「笔记」Java:数组
数组01:数组概述
关于数组我们可以把它看作是一个类型的所有数据的一个集合,并用一个数组下标来区分或指定每一个数,例如一个足球队通常会有几十个人,但是我们来认识他们的时候首先会把他们看作是某某对的成员,然后再利用他们的号码来区分每一个队员,这时候,球队就是一个数组,而号码就是数组的下标,当我们指明是几号队员的时候就找到了这个队员。 同样在编程中,如果我们有一组相同数据类型的数据,例如有10个数字,这时候如果我们要用变量来存放它们的话,就要分别使用10个变量,而且要记住这10个变量的名字,这会十分的麻烦,这时候我们就可以用一个数组变量来存放他们,例如在VB中我们就可以使用dim a(9) as integer(注意:数组的下标是从0开始的,所以10个数的话,下标就是9,a(0)=1)。 使用数组会让程序变的简单,而且避免了定义多个变量的麻烦。 **数组的定义:** - 数组是相同类型数据的有序集合。 - 数组描述的是相同类型的若干个数据,按照一定的先后次序排列组合而成。 - 其中,每一个数据称作一个数组元素,每个数组元素可以通过一个下标来访问它们。 **数组的四个基本特点:** 1. 其长度是确定的。数组一旦被创建,它的大小就是不可以改变的。 2. 其元素必须是相同类型,不允许出现混合类型。 3. 数组中的元素可以是任何数据类型,包括基本类型和引用类型。 4. 数组变量属引用类型,数组也可以看成是对象,数组中的每个元素相当于该对象的成员变量。数组本身就是对象,Java中对象是在堆中的,因此数组无论保存原始类型还是其他对象类型,**数组对象本身是在堆中的**。

「笔记」Java:数组
数组02:数组声明创建
### 1. 声明数组 首先必须声明数组变量,才能在程序中使用数组。下面是声明数组变量的语法: ```java dataType[] arrayRefVar; // 首选的方法 或 dataType arrayRefVar[]; // 效果相同,但不是首选方法 ``` 建议使用 **dataType[] arrayRefVar** 的声明风格声明数组变量。 dataType arrayRefVar[] 风格是来自C/C++ 语言 ,在Java中采用是为了让 C/C++ 程序员能够快速理解java语言。 ```java double[] myList; // 首选的方法 或 double myList[]; // 效果相同,但不是首选方法 ``` ------------ ### 2. 创建数组 Java语言使用new操作符来创建数组,语法如下: ```java arrayRefVar = new dataType[arraySize]; ``` 上面的语法语句做了两件事: 1. 使用 dataType[arraySize] 创建了一个数组。 2. 把新创建的数组的引用赋值给变量 arrayRefVar。 数组变量的声明,和创建数组可以用一条语句完成,如下所示: ```java dataType[] arrayRefVar = new dataType[arraySize]; ``` 数组的元素是通过索引访问的。数组索引从 0 开始,所以索引值从 0 到 arrayRefVar.length-1。 获取数组长度: ```java arrays.length ``` 【演示创建一个数组,并赋值,进行访问】 ```java public static void main(String[] args) { //1.声明一个数组 int[] myList = null; //2.创建一个数组 myList = new int[10]; //3.像数组中存值 myList[0] = 1; myList[1] = 2; myList[2] = 3; myList[3] = 4; myList[4] = 5; myList[5] = 6; myList[6] = 7; myList[7] = 8; myList[8] = 9; myList[9] = 10; // 计算所有元素的总和 double total = 0; for (int i = 0; i < myList.length; i++) { total += myList[i]; } System.out.println("总和为: " + total); } ``` ------------ ### 3. 内存分析  1. 声明的时候并没有实例化任何对象,只有在实例化数组对象时,JVM才分配空间,这时才与长度有关。因此,声明数组时不能指定其长度(数组中元素的个数),例如: int a[5]; //非法 2. 声明一个数组的时候并没有数组被真正的创建。 3. 构造一个数组,必须指定长度。 ```java // 1.声明一个数组 int[] myList = null; ```  ```java // 2.创建一个数组 myList = new int[10]; ```  ```java // 3.向数组中存值 myList[0] = 1; myList[1] = 2; myList[2] = 3; myList[3] = 4; myList[4] = 5; myList[5] = 6; myList[6] = 7; myList[7] = 8; myList[8] = 9; myList[9] = 10; ```  ------------ ### 4. 三种初始化 **静态初始化** 除了用new关键字来产生数组以外,还可以直接在定义数组的同时就为数组元素分配空间并赋值。 ```java int[] a = {1,2,3}; Man[] mans = {new Man(1,1),new Man(2,2)}; ``` **动态初始化** 数组定义、为数组元素分配空间、赋值的操作、分开进行。 ```java int[] a = new int[2]; a[0]=1; a[1]=2; ``` **数组的默认初始化** 数组是引用类型,它的元素相当于类的实例变量,因此数组一经分配空间,其中的每个元素也被按照实 例变量同样的方式被隐式初始化。 ```java public static void main(String[] args) { int[] a=new int[2]; boolean[] b = new boolean[2]; String[] s = new String[2]; System.out.println(a[0]+":"+a[1]); //0,0 System.out.println(b[0]+":"+b[1]); //false,false System.out.println(s[0]+":"+s[1]); //null, null } ``` ------------ ### 5. 数组边界 下标的合法区间:[0, length-1],如果越界就会报错; ```java public static void main(String[] args) { int[] a = new int[2]; System.out.println(a[2]); // 越界了,应为0-1 } ``` **ArrayIndexOutOfBoundsException : 数组下标越界异常!** ------------ ### 6. 小结 数组是相同数据类型(数据类型可以为任意类型)的有序集合。 数组也是对象。数组元素相当于对象的成员变量(详情请见内存图)。 数组长度的确定的,不可变的。如果越界,则报:ArrayIndexOutofBounds。

「笔记」Java:数组
数组03:数组使用
数组的元素类型和数组的大小都是确定的,所以当处理数组元素时候,我们通常使用基本循环或者 ForEach 循环。 【该实例完整地展示了如何创建、初始化和操纵数组】 ```java public class TestArray { public static void main(String[] args) { double[] myList = {1.9, 2.9, 3.4, 3.5}; // 打印所有数组元素 for (int i = 0; i < myList.length; i++) { System.out.println(myList[i] + " "); } // 计算所有元素的总和 double total = 0; for (int i = 0; i < myList.length; i++) { total += myList[i]; } System.out.println("Total is " + total); // 查找最大元素 double max = myList[0]; for (int i = 1; i < myList.length; i++) { if (myList[i] > max) { max = myList[i]; } } System.out.println("Max is " + max); } } ``` ### 1. For-Each 循环 JDK 1.5 引进了一种新的循环类型,被称为 For-Each 循环或者加强型循环,它能在不使用下标的情况下遍历数组。 语法格式如下: ```java for(type element: array){ System.out.println(element); } ``` 【示例】 ```java public static void main(String[] args) { double[] myList = {1.9, 2.9, 3.4, 3.5}; // 打印所有数组元素 for (double element: myList) { System.out.println(element); } } ``` ------------ ### 2. 数组作方法入参 数组可以作为参数传递给方法。 例如,下面的例子就是一个打印 int 数组中元素的方法: ```java public static void printArray(int[] array) { for (int i = 0; i < array.length; i++) { System.out.print(array[i] + " "); } } ``` ------------ ### 3. 数组作返回值 ```java public static int[] reverse(int[] list) { int[] result = new int[list.length]; for (int i = 0, j = result.length - 1; i < list.length; i++, j--) { result[j] = list[i]; } return result; } ``` 以上实例中 result 数组作为函数的返回值。

「笔记」Java:数组
数组04:多维数组
多维数组可以看成是数组的数组,比如二维数组就是一个特殊的一维数组,其每一个元素都是一个一维数组。 **多维数组的动态初始化(以二维数组为例)** 直接为每一维分配空间,格式如下: ```java type[][] typeName = new type[typeLength1][typeLength2]; ``` type 可以为基本数据类型和复合数据类型,arraylenght1 和 arraylenght2 必须为正整数,arraylenght1 为行数,arraylenght2 为列数。 比如定义一个二维数组: ```java int a[][] = new int[2][5]; ``` 解析:二维数组 a 可以看成一个两行三列的数组。 **多维数组的引用(以二维数组为例)** 对二维数组中的每个元素,引用方式为 **arrayName[index1] [index2]**,例如: num[1] [0]; 其实二维甚至多维数组十分好理解,我们把两个或者多个值当做定位就好。 原来的数组就是一条线,我们知道一个位置就好 二维就是一个面,两点确定一个位置 三维呢,就需要三个点来确定 依次理解即可! **获取数组长度:** a.length获取的二维数组第一维数组的长度,a[0].length才是获取第二维第一个数组长度。

「笔记」Java:数组
数组05:Arrays 类
数组的工具类java.util.Arrays 由于数组对象本身并没有什么方法可以供我们调用,但API中提供了一个工具类Arrays供我们使用,从而可以对数据对象进行一些基本的操作。 **文档简介:** 此类包含用来操作数组(比如排序和搜索)的各种方法。此类还包含一个允许将数组作为列表来查看的静态工厂。除非特别注明,否则如果指定数组引用为nul1,则此类中的方法都会抛出NullPointerException。 Arrays类中的方法都是static修饰的静态方法,在使用的时候可以直接使用类名进行调用,而"不用"使用对象来调用(注意:是"不用" 而不是 "不能") java.util.Arrays 类能方便地操作数组. 使用之前需要导包! 具有以下常用功能: - 给数组赋值:通过 fill 方法。 - 对数组排序:通过 sort 方法,按升序。 - 比较数组:通过 equals 方法比较数组中元素值是否相等。 - 查找数组元素:通过 binarySearch 方法能对排序好的数组进行二分查找法操作。 具体说明请查看下表:  ### 1. 打印数组 ```java public static void main(String[] args) { int[] a = {1,2}; System.out.println(a); //[I@1b6d3586 System.out.println(Arrays.toString(a)); //[1, 2] } ``` ------------ ### 2. 数组排序 对指定的 int 型数组按数字升序进行排序 ```java public static void main(String[] args) { int[] a = {1,2,323,23,543,12,59}; System.out.println(Arrays.toString(a)); Arrays.sort(a); System.out.println(Arrays.toString(a)); } ``` ------------ ### 3. 二分法查找 在数组中查找指定元素并返回其下标 注意:使用二分搜索法来搜索指定的数组,以获得指定的值。必须在进行此调用之前对数组进行排序(通过sort方法等)。如果没有对数组进行排序,则结果是不确定的。 如果数组包含多个带有指定值的元素,则无法保证找到的是哪一个。 ```java public static void main(String[] args) { int[] a = {1,2,323,23,543,12,59}; Arrays.sort(a); // 使用二分法查找,必须先对数组进行排序 System.out.println("该元素的索引:"+Arrays.binarySearch(a, 12)); } ``` ------------ ### 4. 元素填充 ```java public static void main(String[] args) { int[] a = {1,2,323,23,543,12,59}; Arrays.sort(a); // 使用二分法查找,必须先对数组进行排序 Arrays.fill(a, 2, 4, 100); // 将2到4索引的元素替换为100 System.out.println(Arrays.toString(a)); } ``` ------------ ### 5. 数组转换为List集合 ```java int[] a = {3,5,1,9,7}; List<int[]> list = Arrays.asList(a); ```

「笔记」Java:数组
数组06:常见排序算法
### 1. 冒泡排序 冒泡排序(Bubble Sort),是一种计算机科学领域的较简单的排序算法。 它重复地走访过要排序的元素列,依次比较两个相邻的元素,如果他们的顺序(如从大到小、首字母从A到Z)错误就把他们交换过来。走访元素的工作是重复地进行直到没有相邻元素需要交换,也就是说该元素列已经排序完成。 这个算法的名字由来是因为越大的元素会经由交换慢慢“浮”到数列的顶端(升序或降序排列),就如同碳酸饮料中二氧化碳的气泡最终会上浮到顶端一样,故名“冒泡排序”。 冒泡排序算法的原理如下: 1. 比较相邻的元素。如果第一个比第二个大,就交换他们两个。 2. 对每一对相邻元素做同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。 3. 针对所有的元素重复以上的步骤,除了最后一个。 4. 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。 ```java class Bubble { public int[] sort(int[] array) { int temp = 0; // 外层循环,它决定一共走几趟 // -1为了防止溢出 for (int i = 0; i < array.length - 1; i++) { int flag = 0; // 通过符号位可以减少无谓的比较,如果已经有序了,就退出循环 // 内层循环,它决定每趟走一次 for (int j = 0; j < array.length - i - 1; j++) { // 如果后一个大于前一个,则换位 if (array[j + 1] > array[j]) { temp = array[j]; array[j] = array[j + 1]; array[j + 1] = temp; flag = 1; } } if (flag == 0) { break; } } return array; } public static void main(String[] args) { Bubble bubble = new Bubble(); int[] array = {2, 5, 1, 6, 4, 9, 8, 5, 3, 1, 2, 0}; int[] sort = bubble.sort(array); for (int num : sort) { System.out.print(num + "\t"); } } } ``` ------------ ### 2. 选择排序 选择排序(Selection sort)是一种简单直观的排序算法。它的工作原理是每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到排序序列的末尾。以此类推,直到全部待排序的数据元素排完。 选择排序是不稳定的排序方法。 ```java class SelectSort{ public int[] sort(int arr[]) { int temp = 0; for (int i = 0; i < arr.length - 1; i++) { // 认为目前的数就是最小的, 记录最小数的下标 int minIndex = i; for (int j = i + 1; j < arr.length; j++) { if (arr[minIndex] > arr[j]) { // 修改最小值的下标 minIndex = j; } } // 当退出for就找到这次的最小值,就需要交换位置了 if (i != minIndex) { //交换当前值和找到的最小值的位置 temp = arr[i]; arr[i] = arr[minIndex]; arr[minIndex] = temp; } } return arr; } public static void main(String[] args) { SelectSort selectSort = new SelectSort(); int[] array = {2, 5, 1, 6, 4, 9, 8, 5, 3, 1, 2, 0}; int[] sort = selectSort.sort(array); for (int num : sort) { System.out.print(num + "\t"); } } } ```

「笔记」Java:数组
数组07:稀疏数组
### 1. 线性结构 - 线性结构是最常用的数据结构,其特点是数据元素之间存在一对一的线性关系。 - 线性结构有两种不同存储结构,即顺序存储结构和链式存储结构。 - 顺序存储的线性表称为顺序表,顺序表中的存储元素是连续的,即在内存中是连续的,例如数组。 - 链式存储的线性表称为链表,链表中的存储元素不一定是连续的,元素节点中存放数据元素以及相邻元素的地址信息,但好处是可以充分利用碎片地址。 - 线性结构常见的有:数组、队列、链表和栈,后面我们会详细讲解。 ------------ ### 2. 非线性结构 非线性结构不是一对一的关系, 非线性结构包括:二维数组,多维数组,广义表,树结构,图结构 ------------ ### 3. 稀疏数组 先看一个需求,在编写五子棋程序时,有存盘退出和续上盘的功能。  因为该二维数组的很多值是默认值 0, 因此记录了很多没有意义的数据,因此,在这里,我们需要引入一个新的概念——稀疏数组 当一个数组中大部分元素为0,或者为同一个值的数组时,可以使用稀疏数组来保存该数组,稀疏数组的处理方法是: - 记录数组一共有几行几列,有多少个不同的值 - 把具有不同值的元素的行列及值记录在一个小规模的数组中,从而缩小程序的规模  即第一行第一列记录记录原始数组行数,第一行第二列记录原始数组列数,第一行第三列总共有多少个值,第二行记录第一个有效数据,第三行记录第二个有效数据 代码实现 ```java public class SparseArr { public static void main(String[] args) { //创建一个二维数组 11*11 //0表示没有棋子,1表示黑棋,2表示蓝棋 int[][] chessArr = new int[11][11]; chessArr[1][2] = 1; chessArr[2][3] = 2; //输出原始的二维数组 System.out.println("原始的二维数组:"); for (int i = 0; i < chessArr.length; i++) { for (int j = 0; j < chessArr[i].length; j++) { System.out.print(chessArr[i][j]+"\t"); } System.out.println(); } //将二维数组转换为稀疏数组 //1.先遍历二维数组得到非零数据的个数 int sum = 0; for (int i = 0; i < 11; i++) { for (int j = 0; j < 11; j++) { if (chessArr[i][j] != 0){ sum++; } } } //2.创建对应的系数数组 int[][] sparseArr = new int[sum+1][3]; //给系数数组赋值 sparseArr[0][0] = 11; sparseArr[0][1] = 11; sparseArr[0][2] = sum; //遍历二维数组将非零的值存放到稀疏数组 int count = 0; for (int i = 0; i < chessArr.length; i++) { for (int j = 0; j < chessArr[i].length; j++) { if (chessArr[i][j] != 0){ count++; sparseArr[count][0] = i; sparseArr[count][1] = j; sparseArr[count][2] = chessArr[i][j]; } } } //输出稀疏数组 System.out.println(); System.out.println("稀疏数组:"); for (int i = 0; i < sparseArr.length; i++) { System.out.println(sparseArr[i][0]+"\t"+sparseArr[i][1]+"\t"+sparseArr[i][2]); } //将稀疏数组恢复成二维数组 //1.先读取稀疏数组的第一行,根据第一行创建二维数组 int[][] chessArr2 = new int[sparseArr[0][0]][sparseArr[0][1]]; //2.读取稀疏数组后几行赋值给二维数组 //注意这里是从第二行开始 for (int i = 1; i < sparseArr.length; i++) { chessArr2[sparseArr[i][0]][sparseArr[i][1]] = sparseArr[i][2]; } System.out.println(); System.out.println("恢复后的二维数组:"); for (int[] row : chessArr2) { for (int data : row) { System.out.print(data+"\t"); } System.out.println(); } } } ``` ------------

「笔记」Java:方法
方法06:递归
A方法调用B方法,我们很容易理解! 递归就是:A方法调用A方法!就是自己调用自己,因此我们在设计递归算法时,一定要指明什么时候自己不调用自己。否则,就是个死循环! ### **递归算法重点** 递归是一种常见的解决问题的方法,即把问题逐渐简单化。递归的基本思想就是“自己调用自己”,一个使用递归技术的方法将会直接或者间接的调用自己。 利用递归可以用简单的程序来解决一些复杂的问题。它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。递归的能力在于用有限的语句来定义对象的无限集合。 递归结构包括两个部分: 1. 递归头。解释:什么时候不调用自身方法。如果没有头,将陷入死循环。 2. 递归体。解释:什么时候需要调用自身方法。 【演示:利用代码计算5的乘阶!】 ```java // 5*4*3*2*1 public static void main(String[] args) { System.out.println(f(5)); } public static int f(int n) { if (1 == n) { return 1; } else { return n*f(n-1); } } ```  此题中,按照递归的三个条件来分析: 1. 边界条件:阶乘,乘到最后一个数,即1的时候,返回1,程序执行到底; 2. 递归前进段:当前的参数不等于1的时候,继续调用自身; 3. 递归返回段:从最大的数开始乘,如果当前参数是5,那么就是5 4,即5 (5-1),即n \* (n-1) 递归其实是方便了程序员难为了机器,递归可以通过数学公式很方便的转换为程序。其优点就是易理解,容易编程。但递归是用栈机制实现的,每深入一层,都要占去一块栈数据区域,对嵌套层数深的一些算法,递归会力不从心,空间上会以内存崩溃而告终,而且递归也带来了大量的函数调用,这也有许多额外的时间开销。所以在深度大时,它的时空性就不好了。(会占用大量的内存空间) 而迭代虽然效率高,运行时间只因循环次数增加而增加,没什么额外开销,空间上也没有什么增加,但缺点就是不容易理解,编写复杂问题时困难。 **能不用递归就不用递归,递归都可以用迭代来代替。**

Spring的@Component、@Controller、@Service、@Repository、@Mapper
Spring框架中@Component和@Controller、@Service、@Repository、@Mapper的区别和使用方法
在Spring框架中,`@Component`、`@Controller`、`@Service`、`@Repository` 是核心注解,用于依赖注入和组件扫描,而 `@Mapper` 通常是MyBatis的注解。它们的区别主要体现在**语义**、**用途**和**框架层级**上。以下是详细对比: --- ### 1. **@Component** - **作用**:通用注解,标记一个类为Spring管理的组件(Bean),会被自动扫描到IoC容器中。 - **使用场景**:适用于所有不属于其他特定层(如Controller/Service/Repository)的通用组件。 - **特点**: - 是其他注解(`@Controller`、`@Service`、`@Repository`)的基类。 - 没有明确的语义,仅表示类需要被Spring管理。 --- ### 2. **@Controller** - **作用**:标记一个类为**Web层控制器**,处理HTTP请求(如MVC中的Controller)。 - **使用场景**:定义REST API或传统Spring MVC的请求处理器。 - **特点**: - 通常与 `@RequestMapping`、`@GetMapping` 等注解配合使用。 - 在Spring Boot中,`@RestController` 是 `@Controller` + `@ResponseBody` 的组合。 --- ### 3. **@Service** - **作用**:标记一个类为**业务逻辑层组件**,封装业务逻辑。 - **使用场景**:实现核心业务逻辑的类(如订单服务、用户服务)。 - **特点**: - 强调类的职责是“业务逻辑”,而非数据访问或表现层。 - 无特殊技术功能,但能提高代码可读性。 --- ### 4. **@Repository** - **作用**:标记一个类为**数据访问层组件**,通常用于数据库操作(DAO层)。 - **使用场景**:直接与数据库交互的类(如JPA Repository、MyBatis Mapper)。 - **特点**: - **自动转换数据访问异常**:将底层数据库异常(如JDBC的SQLException)转换为Spring的统一`DataAccessException`。 - 在JPA中,`@Repository` 接口通常继承自 `JpaRepository`。 --- ### 5. **@Mapper(MyBatis注解)** - **作用**:**MyBatis框架**中标记接口为数据库映射器(Mapper),MyBatis会为这些接口生成代理实现类。 - **使用场景**:定义MyBatis的Mapper接口,用于执行SQL操作。 - **特点**: - **非Spring原生注解**,属于MyBatis框架。 - 需要配合 `@MapperScan` 注解或在配置中指定扫描路径。 - 与 `@Repository` 的区别:`@Mapper` 由MyBatis管理,而 `@Repository` 由Spring管理(可结合使用)。 --- ### 对比表格 | 注解 | 框架归属 | 层级/用途 | 特殊功能 | | ------------- | -------- | ---------------------------- | ------------------------------- | | `@Component` | Spring | 通用组件 | 无 | | `@Controller` | Spring | Web层(处理HTTP请求) | 路由映射(如`@RequestMapping`) | | `@Service` | Spring | 业务逻辑层 | 无(语义化标记业务逻辑) | | `@Repository` | Spring | 数据访问层(DAO) | 异常转换(JDBC → Spring异常) | | `@Mapper` | MyBatis | 数据访问层(MyBatis Mapper) | MyBatis接口代理实现 | --- ### 关键区别总结 1. **语义化分层**: - `@Controller`(Web层)、`@Service`(业务层)、`@Repository`(DAO层)是Spring对`@Component`的细化,提供清晰的代码分层。 - `@Component` 适用于无法归类到上述层的通用组件。 2. **异常处理**: - 只有 `@Repository` 会自动转换数据访问异常。 3. **框架归属**: - `@Mapper` 是MyBatis的注解,需要与Spring集成(如通过`@MapperScan`),而其他注解都是Spring原生。 4. **技术实现**: - `@Controller` 和 `@RestController` 用于构建Web接口。 - `@Repository` 可结合JPA/Hibernate,而 `@Mapper` 通常用于MyBatis的SQL映射。 --- ### 示例代码 ```java // Web层 @Controller public class UserController { @Autowired private UserService userService; } // 业务层 @Service public class UserService { @Autowired private UserRepository userRepository; } // 数据访问层(Spring管理) @Repository public class UserRepositoryImpl implements UserRepository { // 使用JPA或JDBC操作数据库 } // MyBatis Mapper(MyBatis管理) @Mapper public interface UserMapper { @Select("SELECT * FROM user WHERE id = #{id}") User findById(int id); } ``` 在开发中建议根据代码的职责选择对应的注解,以提高可读性和维护性。

JSON解析与Dart Model的使用
在Flutter中处理JSON数据并转换为Dart Model
### 将json string转成Dart Model类 通过上述方式可以将json字符串转换成Map,但Map中存放那些字段在使用的时候很不方便。 为了提高字段获取的效率,目前常用的方案是将json string转成Dart Model类,不仅可以提高字段获取的效率,而且方便字段的后期维护和扩展。 > 小知识:什么是Dart Model类? 实体类可以理解为一种数据的载体,主要是作为数据管理和业务逻辑处理层面上存在的类别。比如:我们将从数据库中或服务端接口中读取到的用户信息转成一个User类来存储;然后在需要的时候获取User类的属性并将其展示在界面上等。 ## 如何将json string转成Dart Model 1. 定义Dart Model 2. 将json string转成Map(可借助jsonDecode完成) 3. 将Map转成Dart Model 在定义Dart Model前我们先来了解下Dart Model的格式要求: ### Dart Model格式要求 1. 字段不能为私有(既字段前不能有下划线); 2. 普通构造函数; 3. 声明为`XXX.fromJson`的命名构造函数; 4. 声明为`Map<String, dynamic> toJson`成员函数; 其中:命名构造函数为必选。 > 代码示例: ```dart class Data { int? code; String? method; String? requestPrams; Data({this.code, this.method, this.requestPrams}); Data.fromJson(Map<String, dynamic> json) { code = json['code']; method = json['method']; requestPrams = json['requestPrams']; } Map<String, dynamic> toJson() { final Map<String, dynamic> data = <String, dynamic>{}; data['code'] = code; data['method'] = method; data['requestPrams'] = requestPrams; return data; } } ``` ## Dart Model的使用 定义好Dart Model之后接下来我们来看下如何使用它: 1. 使用`dart:convert` 中的`jsonDecode`将json string转成Map 2. 通过`DataModel.fromJson`将Map转成Dart Model > 代码示例: ```dart //json转Model void _json2Model() { var jsonString = '{"code":0,"data":{"code":0,"method":"GET","requestPrams":"11"},"msg":"SUCCESS."}'; Map<String, dynamic> map = jsonDecode(jsonString); //将json转成Map; DataModel model = DataModel.fromJson(map); //将Map转成Dart Model setState(() { resultShow = 'code: ${model.code};requestPrams:${model.data?.requestPrams}'; //使用Map中的数据 }); } ```

在Flutter中进行网络请求
Flutter官方推荐在Flutter中用Http进行网络请求
> Flutter官方推荐我们在Flutter中用Http进行网络请求。 ## 什么是Http库? Http库是Flutter社区开发的一个可组合的、跨平台的用于Flutter的网络请求插件。 ## 如何使用Http库 ### 第一步:添加依赖 在`pubspec.yaml`中引入[http](https://pub.dev/packages/http)插件; ```dart dependencies: http: <latest_version> ``` ### 第二步:导入http 在dart文件中导入: ```dart import 'package:http/http.dart' as http; ``` ## 使用http库做get请求 调用`http.get`发送请求: > 代码示例: ```dart ///发送Get请求 _doGet() async { var uri = Uri.parse('https://api.geekailab.com/uapi/test/test?requestPrams=11'); var response = await http.get(uri); //http请求成功 if (response.statusCode == 200) { setState(() { resultShow = response.body; }); } else { setState(() { resultShow = "请求失败:code: ${response.statusCode},body:${response.body}"; }); } } ``` `http.get()`返回一个包含`http.Response`的`Future`: * [Future](https://api.flutter.dev/flutter/dart-async/Future-class.html):是与异步操作一起工作的核心Dart类,它用于表示未来某个时间可能会出现的可用值或错误; * `http.Response`:类包含一个成功的HTTP请求接收到的数据; * `response.statusCode`:通过statusCode可以获取http请求状态码,200代表请求成功; * `response.body`:通过body获取返回的数据; ## 如何用http库做post请求? * 调用`http.post`发送请求; 使用post传递的数据常用的内容类型主要有两种: * `x-www-form-urlencoded` * `application/json` ### 发送`x-www-form-urlencoded`(后面简称form类型)类型的数据: form类型是post请求中较为常见的一种内容类型,也是http库默认的内容类型。 注意:body必须为`Map<String, String>`类型。 > 代码示例: ```dart _doPost() async { var uri = Uri.parse('https://api.geekailab.com/uapi/test/test'); var params = {"requestPrams": "doPost"};//数据格式必须为Map<String, String> var response = await http.post(uri, body: params); //默认为x-www-form-urlencoded 格式,所以可以不用设置content-type //http请求成功 if (response.statusCode == 200) { setState(() { resultShow = response.body; }); } else { setState(() { resultShow = "请求失败:code: ${response.statusCode},body:${response.body}"; }); } } ``` `http.post()`返回一个包含`http.Response`的`Future`: * [Future](https://api.flutter.dev/flutter/dart-async/Future-class.html):是与异步操作一起工作的核心Dart类。它用于表示未来某个时间可能会出现的可用值或错误; * `http.Response`:类包含一个成功的HTTP请求接收到的数据; ### 发送`application/json`(后面简称json类型)类型的数据 要发送json类型的数据,主要有以下步骤: 1. 将数据转换为json String,可以利用jsonEncode()来转; 2. 将json数据赋值给body参数; 3. 在header中设置`content-type`为`application/json`; ```dart ///发送Json类型的Post请求 _doPostJson() async { var uri = Uri.parse('https://api.geekailab.com/uapi/test/testJson'); var params = {"requestPrams": "doPost"}; var response = await http.post(uri, body: jsonEncode(params), //将数据转成string headers: { //设置content-type为application/json "content-type": "application/json" }); //http请求成功 if (response.statusCode == 200) { setState(() { resultShow = response.body; }); } else { setState(() { resultShow = "请求失败:code: ${response.statusCode},body:${response.body}"; }); } } ```

StatelessWidget与StatefulWidget开发指南
Flutter的widget很多,但主要可以分为两类,一种是无状态的widget(StatelessWidget),一种是有状态的widget(StatefulWidget)。
## 什么是StatelessWidget? Flutter中的`StatelessWidget`是一个不需要状态更改的widget - 它没有要管理的内部状态。 当您描述的用户界面部分不依赖于对象本身中的配置信息以及widget的BuildContext 时,无状态widget非常有用。 `AboutDialog`, `CircleAvatar`和 `Text` 都是`StatelessWidget`的子类。 ```dart void main() => runApp(const MyStatelessWidget(text: "StatelessWidget Example")); class MyStatelessWidget extends StatelessWidget { final String text; const MyStatelessWidget({Key? key, required this.text}) : super(key: key); @override Widget build(BuildContext context) { return Center( child: Text( text, textDirection: TextDirection.ltr, ), ); } } ``` 在前面的示例中,您使用了MyStatelessWidget类的构造函数 传递标记为final的text。这个类继承了StatelessWidget-它包含不可变数据 无状态widget的build方法通常只会在以下三种情况调用: * 将widget插入树中时 * 当widget的父级更改其配置时 * 当它依赖的[InheritedWidget](https://api.flutter.dev/flutter/widgets/InheritedWidget-class.html)发生变化时 --- ## 什么是StatefulWidget? [StatefulWidget](https://api.flutter.dev/flutter/widgets/StatefulWidget-class.html) 是可变状态的widget。 使用`setState`方法管理StatefulWidget的状态的改变。调用`setState`告诉Flutter框架,某个状态发生了变化,Flutter会重新运行build方法,以便应用程序可以应用最新状态。 状态是在构建widget时可以同步读取的信息可能会在widget的生命周期中发生变化。确保在状态改变时及时通知状态 变化是widget实现者的责任。当widget可以动态更改时,需要使用StatefulWidget。 例如, 通过键入表单或移动滑块来更改widget的状态. 或者, 它可以随时间变化 - 或者数据推送更新UI `Checkbox`, `Radio`, `Slider`, `InkWell`, `Form`, 和 `TextField` 都是有状态的widget,也是StatefulWidget的子类。 下面的示例声明了一个StatefulWidget,它需要一个`createState()`方法。此方法创建管理widget状态的状态对象\_MyStatefulWidgetState。 ```dart class MyStatefulWidget extends StatefulWidget { MyStatefulWidget({Key key, this.title}) : super(key: key); final String title; @override _MyStatefulWidgetState createState() => _MyStatefulWidgetState(); } ``` 以下状态类\_MyStatefulWidgetState实现widget的`build()`方法。当状态改变时,例如,当用户切换按钮时,使用新的切换值调用`setState`。这会导致框架在UI中重建此widget。 ```dart import 'package:flutter/material.dart'; void main() => runApp(const MyStatefulWidget(title: "StatelessWidget与StatefulWidget开发指南")); class MyStatefulWidget extends StatefulWidget { const MyStatefulWidget({Key? key, required this.title}) : super(key: key); final String title; @override MyStatefulWidgetState createState() => MyStatefulWidgetState(); } class MyStatefulWidgetState extends State<MyStatefulWidget> { bool isOpen = false; @override Widget build(BuildContext context) { return MaterialApp( theme: ThemeData( useMaterial3: false, ), home: Scaffold( appBar: AppBar( title: Text(widget.title), ), body: Column( children: [ Text("开关状态:${isOpen ? '开启' : '关闭'}"), Switch( value: isOpen, onChanged: (change) { setState(() { isOpen = change; }); }) ], ), ), ); } } ``` --- ## StatefulWidget和StatelessWidget有哪些最佳实践? 在开发widget时,需要考虑以下几点。 > 1. 确定widget应该使用StatefulWidget还是StatelessWidget **在Flutter中,widget是有状态的还是无状态的 - 取决于是否 他们依赖于状态的变化** * 如果用户交互或数据改变导致widget改变,那么它就是有状态的。 * 如果一个widget是最终的或不可变的,那么它就是无状态。 > 2. 确定哪个对象管理widget的状态(对于StatefulWidget) 在Flutter中,管理状态有三种主要方式: * 每个widget管理自己的状态 * 父widget管理widget的状态 * 混合搭配管理的方法 如何决定使用哪种方式时,可以参考以下原则: * 如果所讨论的状态是用户数据,例如复选框的已选中或未选中状态,或滑块的位置,则状态最好由父widget管理; * 如果widget的状态取决于动作,例如动画,那么最好是由widget自身来管理状态; * 如有还是不确定谁管理状态,请让父widget管理子widget的状态;

Flutter 页面生命周期
Flutter 页面 Widget 的生命周期的功能与介绍
## 一、Flutter 页面生命周期 **Flutter** 页面生命周期就是 Flutter 页面组件 Widget 的生命周期 ; 页面的组件就是 StatefulWidget 组件 和 StatelessWidget 组件 ; ### 1、StatelessWidget 组件生命周期函数 **StatelessWidget 组件生命周期函数 :** 只有两个 , 分别是 createElement() , build() 两个方法 ; ### 2、StatefulWidget 组件生命周期函数 **StatefulWidget 组件生命周期分为三组 :** **① 初始化期 :** createState , initState ; **② 更新期 :** didChangeDependencies , build , didUpdateWidget ; **③ 销毁期 :** deactivate , dispose ; --- ## 二、StatefulWidget 组件生命周期 ### 1、createState **createState 函数 :** * **所处时期 :** 初始化期的生命周期函数 * **调用时机 :** 创建 StatefulWidget 之后调用的第一个方法 ; * **抽象方法 :** 该方法是抽象方法 , 必须覆盖重写该方法 ; ### 2、initState **initState 函数 :** * **所处时期 :** 初始化期的生命周期函数 * **调用时机 :** 该方法是创建 Widget 组件时除构造方法之外的第一个方法 , * **对应方法 :** 对应 **Android** 中的 onCreate 方法 ; 对应 **iOS** 中的 viewDidLoad 方法 ; * **常用用法 :** 在该方法中执行一些初始化操作 ; ### 3、didChangeDependencies **didChangeDependencies 函数 :** * **所处时期 :** 更新期的生命周期函数 ; * **调用时机 :** ① 创建 Widget 组件时 , 调用完 initState 方法后 , 调用该方法 ; ② InheritedWidget 相关 ( 暂不涉及 ) ; ### 4、build **build 函数 :** * **所处时期 :** 更新期的生命周期函数 ; * **调用时机 :** ① 调用完 didChangeDependencies 方法后调用该方法 ; ② 调用 setState 方法之后 , 该方法也会被调用 ; * **方法作用 :** 页面每次渲染时都会调用该方法 ; ### 5、didUpdateWidget **didUpdateWidget 函数 :** * **所处时期 :** 更新期的生命周期函数 ; * **调用时机 :** 该生命周期方法不经常调用 , 只有在父容器件重绘时才调用该方法 ; * **方法机制 :** 传入的 oldWidget 参数是旧的组件信息 , 将当前的 Widget 与旧的 Widget 进行对比 , 如果不相等 , 便执行额外操作 ; ### 6、deactivate **deactivate 函数 :** * **所处时期 :** 销毁期的生命周期函数 ; * **调用时机 :** 该生命周期方法不经常调用 , 只有在组件被移除时才调用 ; ### 7、dispose **dispose 函数 :** * **所处时期 :** 销毁期的生命周期函数 ; * **调用时机 :** 组件被销毁时调用 , 要在该方法中进行资源的释放与销毁操作 ;
