Initial commit
This commit is contained in:
commit
4c7451db25
8
.claude/settings.local.json
Normal file
8
.claude/settings.local.json
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"permissions": {
|
||||||
|
"allow": [
|
||||||
|
"mcp__zen__chat"
|
||||||
|
],
|
||||||
|
"deny": []
|
||||||
|
}
|
||||||
|
}
|
||||||
2807
Java语法完整指南.md
Normal file
2807
Java语法完整指南.md
Normal file
File diff suppressed because it is too large
Load Diff
1997
Linux常用命令详解.md
Normal file
1997
Linux常用命令详解.md
Normal file
File diff suppressed because it is too large
Load Diff
954
MySQL_GROUP_BY语法使用文档.md
Normal file
954
MySQL_GROUP_BY语法使用文档.md
Normal file
@ -0,0 +1,954 @@
|
|||||||
|
# MySQL GROUP BY 语法使用文档
|
||||||
|
|
||||||
|
## 目录
|
||||||
|
1. [GROUP BY 基础语法](#group-by-基础语法)
|
||||||
|
2. [GROUP BY 规则和特性](#group-by-规则和特性)
|
||||||
|
3. [示例数据准备](#示例数据准备)
|
||||||
|
4. [基础分组查询](#基础分组查询)
|
||||||
|
5. [聚合函数详解](#聚合函数详解)
|
||||||
|
6. [HAVING 子句使用](#having-子句使用)
|
||||||
|
7. [多列分组查询](#多列分组查询)
|
||||||
|
8. [复杂分组统计](#复杂分组统计)
|
||||||
|
9. [面试题和实际案例](#面试题和实际案例)
|
||||||
|
10. [性能优化和最佳实践](#性能优化和最佳实践)
|
||||||
|
|
||||||
|
## GROUP BY 基础语法
|
||||||
|
|
||||||
|
GROUP BY 用于将查询结果按指定字段进行分组,通常与聚合函数一起使用。
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SELECT column1, aggregate_function(column2)
|
||||||
|
FROM table_name
|
||||||
|
[WHERE condition]
|
||||||
|
GROUP BY column1, column2, ...
|
||||||
|
[HAVING group_condition]
|
||||||
|
[ORDER BY column]
|
||||||
|
[LIMIT number];
|
||||||
|
```
|
||||||
|
|
||||||
|
**执行顺序:**
|
||||||
|
1. FROM - 确定数据源
|
||||||
|
2. WHERE - 过滤原始数据
|
||||||
|
3. GROUP BY - 分组
|
||||||
|
4. 聚合函数计算
|
||||||
|
5. HAVING - 过滤分组结果
|
||||||
|
6. SELECT - 选择输出列
|
||||||
|
7. ORDER BY - 排序
|
||||||
|
8. LIMIT - 限制结果数量
|
||||||
|
|
||||||
|
## GROUP BY 规则和特性
|
||||||
|
|
||||||
|
1. **SELECT 列限制**:SELECT 子句中只能包含:
|
||||||
|
- GROUP BY 子句中的列
|
||||||
|
- 聚合函数
|
||||||
|
- 常量
|
||||||
|
|
||||||
|
2. **NULL 值处理**:NULL 值被视为一组
|
||||||
|
|
||||||
|
3. **聚合函数**:
|
||||||
|
- COUNT():计数
|
||||||
|
- SUM():求和
|
||||||
|
- AVG():平均值
|
||||||
|
- MAX():最大值
|
||||||
|
- MIN():最小值
|
||||||
|
- GROUP_CONCAT():字符串连接
|
||||||
|
|
||||||
|
4. **HAVING vs WHERE**:
|
||||||
|
- WHERE:过滤原始行
|
||||||
|
- HAVING:过滤分组结果
|
||||||
|
|
||||||
|
## 示例数据准备
|
||||||
|
|
||||||
|
### 创建示例表
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 销售订单表
|
||||||
|
CREATE TABLE sales_orders (
|
||||||
|
order_id INT PRIMARY KEY AUTO_INCREMENT,
|
||||||
|
customer_id INT,
|
||||||
|
customer_name VARCHAR(50),
|
||||||
|
product_id INT,
|
||||||
|
product_name VARCHAR(100),
|
||||||
|
category VARCHAR(50),
|
||||||
|
order_date DATE,
|
||||||
|
quantity INT,
|
||||||
|
unit_price DECIMAL(10,2),
|
||||||
|
sales_rep VARCHAR(50),
|
||||||
|
region VARCHAR(30),
|
||||||
|
discount_rate DECIMAL(3,2) DEFAULT 0.00
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 员工表
|
||||||
|
CREATE TABLE employees (
|
||||||
|
emp_id INT PRIMARY KEY,
|
||||||
|
name VARCHAR(50),
|
||||||
|
department VARCHAR(30),
|
||||||
|
position VARCHAR(50),
|
||||||
|
salary DECIMAL(10,2),
|
||||||
|
hire_date DATE,
|
||||||
|
manager_id INT,
|
||||||
|
age INT,
|
||||||
|
gender ENUM('M', 'F'),
|
||||||
|
city VARCHAR(30)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 学生成绩表
|
||||||
|
CREATE TABLE student_scores (
|
||||||
|
student_id INT,
|
||||||
|
student_name VARCHAR(50),
|
||||||
|
subject VARCHAR(30),
|
||||||
|
score INT,
|
||||||
|
exam_date DATE,
|
||||||
|
teacher VARCHAR(30),
|
||||||
|
class_id INT,
|
||||||
|
semester VARCHAR(20),
|
||||||
|
PRIMARY KEY (student_id, subject, exam_date)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 网站访问日志表
|
||||||
|
CREATE TABLE web_logs (
|
||||||
|
log_id INT PRIMARY KEY AUTO_INCREMENT,
|
||||||
|
user_id INT,
|
||||||
|
page_url VARCHAR(200),
|
||||||
|
visit_date DATE,
|
||||||
|
visit_time TIME,
|
||||||
|
session_duration INT, -- 会话时长(分钟)
|
||||||
|
device_type VARCHAR(20),
|
||||||
|
browser VARCHAR(30),
|
||||||
|
country VARCHAR(30),
|
||||||
|
page_views INT DEFAULT 1
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 库存表
|
||||||
|
CREATE TABLE inventory (
|
||||||
|
product_id INT PRIMARY KEY,
|
||||||
|
product_name VARCHAR(100),
|
||||||
|
category VARCHAR(50),
|
||||||
|
supplier VARCHAR(50),
|
||||||
|
stock_quantity INT,
|
||||||
|
reorder_level INT,
|
||||||
|
unit_cost DECIMAL(8,2),
|
||||||
|
last_restock_date DATE,
|
||||||
|
warehouse_location VARCHAR(30)
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 插入示例数据
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 插入销售订单数据
|
||||||
|
INSERT INTO sales_orders (customer_id, customer_name, product_id, product_name, category, order_date, quantity, unit_price, sales_rep, region, discount_rate) VALUES
|
||||||
|
(1, '阿里巴巴', 101, 'MacBook Pro', '笔记本', '2024-01-15', 10, 12999.00, '张三', '华东', 0.05),
|
||||||
|
(1, '阿里巴巴', 102, 'iPhone 15', '手机', '2024-01-16', 50, 5999.00, '张三', '华东', 0.03),
|
||||||
|
(2, '腾讯', 103, 'iPad Air', '平板', '2024-01-20', 20, 3999.00, '李四', '华南', 0.04),
|
||||||
|
(2, '腾讯', 101, 'MacBook Pro', '笔记本', '2024-01-22', 15, 12999.00, '李四', '华南', 0.06),
|
||||||
|
(3, '百度', 104, 'AirPods Pro', '耳机', '2024-01-25', 100, 1999.00, '王五', '华北', 0.08),
|
||||||
|
(3, '百度', 102, 'iPhone 15', '手机', '2024-01-28', 30, 5999.00, '王五', '华北', 0.05),
|
||||||
|
(4, '字节跳动', 105, 'Apple Watch', '智能手表', '2024-02-01', 25, 2999.00, '赵六', '华北', 0.10),
|
||||||
|
(4, '字节跳动', 103, 'iPad Air', '平板', '2024-02-03', 35, 3999.00, '赵六', '华北', 0.07),
|
||||||
|
(5, '美团', 106, 'MacBook Air', '笔记本', '2024-02-05', 12, 8999.00, '钱七', '华东', 0.04),
|
||||||
|
(5, '美团', 104, 'AirPods Pro', '耳机', '2024-02-08', 80, 1999.00, '钱七', '华东', 0.06),
|
||||||
|
(6, '滴滴', 107, 'iMac', '台式机', '2024-02-10', 8, 15999.00, '孙八', '华南', 0.03),
|
||||||
|
(6, '滴滴', 102, 'iPhone 15', '手机', '2024-02-12', 40, 5999.00, '孙八', '华南', 0.04),
|
||||||
|
(7, '小米', 108, 'iPad Pro', '平板', '2024-02-15', 18, 7999.00, '周九', '华北', 0.05),
|
||||||
|
(7, '小米', 105, 'Apple Watch', '智能手表', '2024-02-18', 30, 2999.00, '周九', '华北', 0.12),
|
||||||
|
(8, '华为', 109, 'Studio Display', '显示器', '2024-02-20', 6, 11999.00, '吴十', '华南', 0.02);
|
||||||
|
|
||||||
|
-- 插入员工数据
|
||||||
|
INSERT INTO employees (emp_id, name, department, position, salary, hire_date, manager_id, age, gender, city) VALUES
|
||||||
|
(1001, '张经理', '技术部', '部门经理', 25000.00, '2020-01-15', NULL, 35, 'M', '北京'),
|
||||||
|
(1002, '李架构师', '技术部', '高级架构师', 22000.00, '2020-05-20', 1001, 32, 'M', '北京'),
|
||||||
|
(1003, '王工程师', '技术部', '高级工程师', 18000.00, '2021-03-10', 1001, 28, 'F', '北京'),
|
||||||
|
(1004, '赵工程师', '技术部', '中级工程师', 15000.00, '2022-01-15', 1001, 26, 'M', '北京'),
|
||||||
|
(1005, '钱实习生', '技术部', '实习工程师', 8000.00, '2023-07-01', 1002, 23, 'F', '北京'),
|
||||||
|
(2001, '孙经理', '产品部', '产品经理', 20000.00, '2021-02-01', NULL, 30, 'F', '上海'),
|
||||||
|
(2002, '周产品', '产品部', '高级产品', 16000.00, '2021-08-15', 2001, 27, 'M', '上海'),
|
||||||
|
(2003, '吴助理', '产品部', '产品助理', 12000.00, '2022-06-20', 2001, 25, 'F', '上海'),
|
||||||
|
(3001, '郑经理', '销售部', '销售经理', 18000.00, '2020-11-10', NULL, 33, 'M', '深圳'),
|
||||||
|
(3002, '刘销售', '销售部', '高级销售', 14000.00, '2021-09-25', 3001, 29, 'F', '深圳'),
|
||||||
|
(3003, '陈销售', '销售部', '销售代表', 10000.00, '2022-12-01', 3001, 24, 'M', '深圳'),
|
||||||
|
(4001, '林经理', '人事部', 'HR经理', 16000.00, '2021-04-12', NULL, 31, 'F', '广州'),
|
||||||
|
(4002, '黄专员', '人事部', 'HR专员', 11000.00, '2022-08-30', 4001, 26, 'F', '广州'),
|
||||||
|
(5001, '何经理', '财务部', '财务经理', 19000.00, '2020-07-08', NULL, 34, 'M', '杭州'),
|
||||||
|
(5002, '魏会计', '财务部', '会计', 13000.00, '2021-11-20', 5001, 28, 'F', '杭州');
|
||||||
|
|
||||||
|
-- 插入学生成绩数据
|
||||||
|
INSERT INTO student_scores (student_id, student_name, subject, score, exam_date, teacher, class_id, semester) VALUES
|
||||||
|
(1, '张小明', '数学', 85, '2024-01-15', '王老师', 1, '2024春'),
|
||||||
|
(1, '张小明', '语文', 78, '2024-01-16', '李老师', 1, '2024春'),
|
||||||
|
(1, '张小明', '英语', 92, '2024-01-17', '赵老师', 1, '2024春'),
|
||||||
|
(2, '李小红', '数学', 92, '2024-01-15', '王老师', 1, '2024春'),
|
||||||
|
(2, '李小红', '语文', 88, '2024-01-16', '李老师', 1, '2024春'),
|
||||||
|
(2, '李小红', '英语', 95, '2024-01-17', '赵老师', 1, '2024春'),
|
||||||
|
(3, '王小刚', '数学', 76, '2024-01-15', '王老师', 2, '2024春'),
|
||||||
|
(3, '王小刚', '语文', 82, '2024-01-16', '李老师', 2, '2024春'),
|
||||||
|
(3, '王小刚', '英语', 79, '2024-01-17', '赵老师', 2, '2024春'),
|
||||||
|
(4, '赵小丽', '数学', 88, '2024-01-15', '王老师', 2, '2024春'),
|
||||||
|
(4, '赵小丽', '语文', 85, '2024-01-16', '李老师', 2, '2024春'),
|
||||||
|
(4, '赵小丽', '英语', 90, '2024-01-17', '赵老师', 2, '2024春'),
|
||||||
|
(5, '钱小伟', '数学', 90, '2024-01-15', '钱老师', 3, '2024春'),
|
||||||
|
(5, '钱小伟', '语文', 87, '2024-01-16', '孙老师', 3, '2024春'),
|
||||||
|
(5, '钱小伟', '英语', 93, '2024-01-17', '周老师', 3, '2024春'),
|
||||||
|
-- 添加期中考试成绩
|
||||||
|
(1, '张小明', '数学', 88, '2024-03-15', '王老师', 1, '2024春'),
|
||||||
|
(1, '张小明', '语文', 82, '2024-03-16', '李老师', 1, '2024春'),
|
||||||
|
(2, '李小红', '数学', 95, '2024-03-15', '王老师', 1, '2024春'),
|
||||||
|
(2, '李小红', '语文', 90, '2024-03-16', '李老师', 1, '2024春'),
|
||||||
|
(3, '王小刚', '数学', 80, '2024-03-15', '王老师', 2, '2024春');
|
||||||
|
|
||||||
|
-- 插入网站访问日志数据
|
||||||
|
INSERT INTO web_logs (user_id, page_url, visit_date, visit_time, session_duration, device_type, browser, country, page_views) VALUES
|
||||||
|
(1001, '/home', '2024-01-15', '09:30:00', 25, 'Desktop', 'Chrome', '中国', 5),
|
||||||
|
(1001, '/products', '2024-01-15', '10:15:00', 15, 'Desktop', 'Chrome', '中国', 8),
|
||||||
|
(1002, '/home', '2024-01-15', '14:20:00', 30, 'Mobile', 'Safari', '美国', 3),
|
||||||
|
(1003, '/about', '2024-01-16', '11:45:00', 20, 'Tablet', 'Chrome', '英国', 4),
|
||||||
|
(1001, '/checkout', '2024-01-16', '16:30:00', 45, 'Desktop', 'Chrome', '中国', 2),
|
||||||
|
(1004, '/home', '2024-01-17', '08:15:00', 35, 'Mobile', 'Firefox', '日本', 6),
|
||||||
|
(1002, '/products', '2024-01-17', '13:20:00', 28, 'Desktop', 'Edge', '美国', 7),
|
||||||
|
(1005, '/contact', '2024-01-18', '10:40:00', 12, 'Mobile', 'Safari', '加拿大', 2),
|
||||||
|
(1003, '/home', '2024-01-18', '15:25:00', 22, 'Desktop', 'Chrome', '英国', 4),
|
||||||
|
(1006, '/products', '2024-01-19', '09:10:00', 40, 'Tablet', 'Safari', '澳大利亚', 9),
|
||||||
|
(1001, '/home', '2024-01-19', '14:35:00', 18, 'Mobile', 'Chrome', '中国', 3),
|
||||||
|
(1007, '/login', '2024-01-20', '11:20:00', 8, 'Desktop', 'Firefox', '德国', 1),
|
||||||
|
(1002, '/dashboard', '2024-01-20', '16:45:00', 55, 'Desktop', 'Chrome', '美国', 12);
|
||||||
|
|
||||||
|
-- 插入库存数据
|
||||||
|
INSERT INTO inventory (product_id, product_name, category, supplier, stock_quantity, reorder_level, unit_cost, last_restock_date, warehouse_location) VALUES
|
||||||
|
(101, 'MacBook Pro', '笔记本', 'Apple', 45, 20, 10000.00, '2024-01-10', '北京仓库'),
|
||||||
|
(102, 'iPhone 15', '手机', 'Apple', 120, 50, 4500.00, '2024-01-12', '上海仓库'),
|
||||||
|
(103, 'iPad Air', '平板', 'Apple', 80, 30, 3200.00, '2024-01-08', '深圳仓库'),
|
||||||
|
(104, 'AirPods Pro', '耳机', 'Apple', 200, 80, 1500.00, '2024-01-15', '北京仓库'),
|
||||||
|
(105, 'Apple Watch', '智能手表', 'Apple', 60, 25, 2400.00, '2024-01-05', '广州仓库'),
|
||||||
|
(106, 'MacBook Air', '笔记本', 'Apple', 35, 15, 7200.00, '2024-01-20', '上海仓库'),
|
||||||
|
(107, 'iMac', '台式机', 'Apple', 15, 10, 12800.00, '2024-01-18', '深圳仓库'),
|
||||||
|
(108, 'iPad Pro', '平板', 'Apple', 25, 15, 6400.00, '2024-01-22', '北京仓库'),
|
||||||
|
(109, 'Studio Display', '显示器', 'Apple', 12, 8, 9600.00, '2024-01-25', '广州仓库'),
|
||||||
|
(110, 'Mac Mini', '台式机', 'Apple', 30, 12, 4800.00, '2024-01-28', '杭州仓库');
|
||||||
|
```
|
||||||
|
|
||||||
|
## 基础分组查询
|
||||||
|
|
||||||
|
### 1. 简单分组统计
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 按产品类别统计销售数量
|
||||||
|
SELECT category, SUM(quantity) AS total_quantity
|
||||||
|
FROM sales_orders
|
||||||
|
GROUP BY category;
|
||||||
|
```
|
||||||
|
|
||||||
|
**结果:**
|
||||||
|
```
|
||||||
|
+----------+----------------+
|
||||||
|
| category | total_quantity |
|
||||||
|
+----------+----------------+
|
||||||
|
| 笔记本 | 37 |
|
||||||
|
| 手机 | 120 |
|
||||||
|
| 平板 | 73 |
|
||||||
|
| 耳机 | 180 |
|
||||||
|
| 智能手表 | 55 |
|
||||||
|
| 台式机 | 8 |
|
||||||
|
| 显示器 | 6 |
|
||||||
|
+----------+----------------+
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 按部门统计员工信息
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 按部门统计员工数量和平均薪资
|
||||||
|
SELECT department,
|
||||||
|
COUNT(*) AS emp_count,
|
||||||
|
ROUND(AVG(salary), 2) AS avg_salary,
|
||||||
|
MIN(salary) AS min_salary,
|
||||||
|
MAX(salary) AS max_salary
|
||||||
|
FROM employees
|
||||||
|
GROUP BY department;
|
||||||
|
```
|
||||||
|
|
||||||
|
**结果:**
|
||||||
|
```
|
||||||
|
+----------+-----------+------------+------------+------------+
|
||||||
|
| department | emp_count | avg_salary | min_salary | max_salary |
|
||||||
|
+----------+-----------+------------+------------+------------+
|
||||||
|
| 技术部 | 5 | 17600.00 | 8000.00 | 25000.00 |
|
||||||
|
| 产品部 | 3 | 16000.00 | 12000.00 | 20000.00 |
|
||||||
|
| 销售部 | 3 | 14000.00 | 10000.00 | 18000.00 |
|
||||||
|
| 人事部 | 2 | 13500.00 | 11000.00 | 16000.00 |
|
||||||
|
| 财务部 | 2 | 16000.00 | 13000.00 | 19000.00 |
|
||||||
|
+----------+-----------+------------+------------+------------+
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 按日期分组统计
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 按月份统计订单数量和销售额
|
||||||
|
SELECT DATE_FORMAT(order_date, '%Y-%m') AS order_month,
|
||||||
|
COUNT(*) AS order_count,
|
||||||
|
SUM(quantity * unit_price * (1 - discount_rate)) AS total_sales
|
||||||
|
FROM sales_orders
|
||||||
|
GROUP BY DATE_FORMAT(order_date, '%Y-%m')
|
||||||
|
ORDER BY order_month;
|
||||||
|
```
|
||||||
|
|
||||||
|
**结果:**
|
||||||
|
```
|
||||||
|
+-------------+-------------+-------------+
|
||||||
|
| order_month | order_count | total_sales |
|
||||||
|
+-------------+-------------+-------------+
|
||||||
|
| 2024-01 | 6 | 912393.00 |
|
||||||
|
| 2024-02 | 9 | 809328.40 |
|
||||||
|
+-------------+-------------+-------------+
|
||||||
|
```
|
||||||
|
|
||||||
|
## 聚合函数详解
|
||||||
|
|
||||||
|
### 1. COUNT() 函数详解
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- COUNT 的不同用法
|
||||||
|
SELECT
|
||||||
|
COUNT(*) AS total_rows, -- 总行数
|
||||||
|
COUNT(manager_id) AS has_manager, -- 非NULL值数量
|
||||||
|
COUNT(DISTINCT department) AS dept_count, -- 去重计数
|
||||||
|
COUNT(CASE WHEN salary > 15000 THEN 1 END) AS high_salary_count -- 条件计数
|
||||||
|
FROM employees;
|
||||||
|
```
|
||||||
|
|
||||||
|
**结果:**
|
||||||
|
```
|
||||||
|
+------------+-------------+------------+-------------------+
|
||||||
|
| total_rows | has_manager | dept_count | high_salary_count |
|
||||||
|
+------------+-------------+------------+-------------------+
|
||||||
|
| 15 | 10 | 5 | 8 |
|
||||||
|
+------------+-------------+------------+-------------------+
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. SUM() 和 AVG() 函数
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 按销售代表统计业绩
|
||||||
|
SELECT sales_rep,
|
||||||
|
COUNT(*) AS order_count,
|
||||||
|
SUM(quantity) AS total_quantity,
|
||||||
|
SUM(quantity * unit_price * (1 - discount_rate)) AS total_sales,
|
||||||
|
ROUND(AVG(quantity * unit_price * (1 - discount_rate)), 2) AS avg_order_value
|
||||||
|
FROM sales_orders
|
||||||
|
GROUP BY sales_rep
|
||||||
|
ORDER BY total_sales DESC;
|
||||||
|
```
|
||||||
|
|
||||||
|
**结果:**
|
||||||
|
```
|
||||||
|
+----------+-------------+----------------+-------------+-----------------+
|
||||||
|
| sales_rep | order_count | total_quantity | total_sales | avg_order_value |
|
||||||
|
+----------+-------------+----------------+-------------+-----------------+
|
||||||
|
| 张三 | 2 | 60 | 423447.00 | 211723.50 |
|
||||||
|
| 李四 | 2 | 35 | 318969.00 | 159484.50 |
|
||||||
|
| 王五 | 2 | 130 | 344562.00 | 172281.00 |
|
||||||
|
| 赵六 | 2 | 60 | 206187.00 | 103093.50 |
|
||||||
|
| 钱七 | 2 | 92 | 258291.60 | 129145.80 |
|
||||||
|
| 孙八 | 2 | 48 | 355464.00 | 177732.00 |
|
||||||
|
| 周九 | 2 | 48 | 222777.60 | 111388.80 |
|
||||||
|
| 吴十 | 1 | 6 | 70622.80 | 70622.80 |
|
||||||
|
+----------+-------------+----------------+-------------+-----------------+
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. MAX() 和 MIN() 函数
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 按班级统计成绩分布
|
||||||
|
SELECT class_id,
|
||||||
|
COUNT(*) AS student_count,
|
||||||
|
MAX(score) AS highest_score,
|
||||||
|
MIN(score) AS lowest_score,
|
||||||
|
ROUND(AVG(score), 2) AS avg_score,
|
||||||
|
MAX(score) - MIN(score) AS score_range
|
||||||
|
FROM student_scores
|
||||||
|
GROUP BY class_id
|
||||||
|
ORDER BY class_id;
|
||||||
|
```
|
||||||
|
|
||||||
|
**结果:**
|
||||||
|
```
|
||||||
|
+----------+---------------+---------------+--------------+-----------+-------------+
|
||||||
|
| class_id | student_count | highest_score | lowest_score | avg_score | score_range |
|
||||||
|
+----------+---------------+---------------+--------------+-----------+-------------+
|
||||||
|
| 1 | 8 | 95 | 78 | 86.75 | 17 |
|
||||||
|
| 2 | 7 | 90 | 76 | 82.71 | 14 |
|
||||||
|
| 3 | 3 | 93 | 87 | 90.00 | 6 |
|
||||||
|
+----------+---------------+---------------+--------------+-----------+-------------+
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. GROUP_CONCAT() 函数
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 按部门列出所有员工姓名
|
||||||
|
SELECT department,
|
||||||
|
COUNT(*) AS emp_count,
|
||||||
|
GROUP_CONCAT(name ORDER BY salary DESC) AS employees,
|
||||||
|
GROUP_CONCAT(DISTINCT city) AS cities
|
||||||
|
FROM employees
|
||||||
|
GROUP BY department;
|
||||||
|
```
|
||||||
|
|
||||||
|
**结果:**
|
||||||
|
```
|
||||||
|
+----------+-----------+------------------------------------------+----------+
|
||||||
|
| department | emp_count | employees | cities |
|
||||||
|
+----------+-----------+------------------------------------------+----------+
|
||||||
|
| 技术部 | 5 | 张经理,李架构师,王工程师,赵工程师,钱实习生 | 北京 |
|
||||||
|
| 产品部 | 3 | 孙经理,周产品,吴助理 | 上海 |
|
||||||
|
| 销售部 | 3 | 郑经理,刘销售,陈销售 | 深圳 |
|
||||||
|
| 人事部 | 2 | 林经理,黄专员 | 广州 |
|
||||||
|
| 财务部 | 2 | 何经理,魏会计 | 杭州 |
|
||||||
|
+----------+-----------+------------------------------------------+----------+
|
||||||
|
```
|
||||||
|
|
||||||
|
## HAVING 子句使用
|
||||||
|
|
||||||
|
### 1. HAVING 基础用法
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 查找员工数量大于2的部门
|
||||||
|
SELECT department,
|
||||||
|
COUNT(*) AS emp_count,
|
||||||
|
ROUND(AVG(salary), 2) AS avg_salary
|
||||||
|
FROM employees
|
||||||
|
GROUP BY department
|
||||||
|
HAVING COUNT(*) > 2
|
||||||
|
ORDER BY emp_count DESC;
|
||||||
|
```
|
||||||
|
|
||||||
|
**结果:**
|
||||||
|
```
|
||||||
|
+----------+-----------+------------+
|
||||||
|
| department | emp_count | avg_salary |
|
||||||
|
+----------+-----------+------------+
|
||||||
|
| 技术部 | 5 | 17600.00 |
|
||||||
|
| 产品部 | 3 | 16000.00 |
|
||||||
|
| 销售部 | 3 | 14000.00 |
|
||||||
|
+----------+-----------+------------+
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. HAVING 与 WHERE 的区别
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 错误用法:WHERE 不能使用聚合函数
|
||||||
|
-- SELECT department, COUNT(*) FROM employees WHERE COUNT(*) > 2 GROUP BY department;
|
||||||
|
|
||||||
|
-- 正确用法:先过滤再分组
|
||||||
|
SELECT department,
|
||||||
|
COUNT(*) AS emp_count,
|
||||||
|
ROUND(AVG(salary), 2) AS avg_salary
|
||||||
|
FROM employees
|
||||||
|
WHERE salary > 12000 -- 先过滤薪资大于12000的员工
|
||||||
|
GROUP BY department
|
||||||
|
HAVING COUNT(*) >= 2 -- 再过滤员工数量>=2的部门
|
||||||
|
ORDER BY avg_salary DESC;
|
||||||
|
```
|
||||||
|
|
||||||
|
**结果:**
|
||||||
|
```
|
||||||
|
+----------+-----------+------------+
|
||||||
|
| department | emp_count | avg_salary |
|
||||||
|
+----------+-----------+------------+
|
||||||
|
| 技术部 | 3 | 21666.67 |
|
||||||
|
| 产品部 | 2 | 18000.00 |
|
||||||
|
| 财务部 | 2 | 16000.00 |
|
||||||
|
+----------+-----------+------------+
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 复杂 HAVING 条件
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 查找高价值客户(订单总额>30万且订单数量>1)
|
||||||
|
SELECT customer_name,
|
||||||
|
COUNT(*) AS order_count,
|
||||||
|
SUM(quantity * unit_price * (1 - discount_rate)) AS total_sales,
|
||||||
|
ROUND(AVG(quantity * unit_price * (1 - discount_rate)), 2) AS avg_order_value
|
||||||
|
FROM sales_orders
|
||||||
|
GROUP BY customer_name
|
||||||
|
HAVING COUNT(*) > 1 AND SUM(quantity * unit_price * (1 - discount_rate)) > 300000
|
||||||
|
ORDER BY total_sales DESC;
|
||||||
|
```
|
||||||
|
|
||||||
|
**结果:**
|
||||||
|
```
|
||||||
|
+--------------+-------------+-------------+-----------------+
|
||||||
|
| customer_name | order_count | total_sales | avg_order_value |
|
||||||
|
+--------------+-------------+-------------+-----------------+
|
||||||
|
| 阿里巴巴 | 2 | 423447.00 | 211723.50 |
|
||||||
|
| 腾讯 | 2 | 318969.00 | 159484.50 |
|
||||||
|
| 百度 | 2 | 344562.00 | 172281.00 |
|
||||||
|
| 滴滴 | 2 | 355464.00 | 177732.00 |
|
||||||
|
+-------------+-------------+-------------+-----------------+
|
||||||
|
```
|
||||||
|
|
||||||
|
## 多列分组查询
|
||||||
|
|
||||||
|
### 1. 双列分组
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 按地区和销售代表分组统计
|
||||||
|
SELECT region, sales_rep,
|
||||||
|
COUNT(*) AS order_count,
|
||||||
|
SUM(quantity) AS total_quantity,
|
||||||
|
SUM(quantity * unit_price * (1 - discount_rate)) AS total_sales
|
||||||
|
FROM sales_orders
|
||||||
|
GROUP BY region, sales_rep
|
||||||
|
ORDER BY region, total_sales DESC;
|
||||||
|
```
|
||||||
|
|
||||||
|
**结果:**
|
||||||
|
```
|
||||||
|
+--------+-----------+-------------+----------------+-------------+
|
||||||
|
| region | sales_rep | order_count | total_quantity | total_sales |
|
||||||
|
+--------+-----------+-------------+----------------+-------------+
|
||||||
|
| 华北 | 王五 | 2 | 130 | 344562.00 |
|
||||||
|
| 华北 | 赵六 | 2 | 60 | 206187.00 |
|
||||||
|
| 华北 | 周九 | 2 | 48 | 222777.60 |
|
||||||
|
| 华东 | 张三 | 2 | 60 | 423447.00 |
|
||||||
|
| 华东 | 钱七 | 2 | 92 | 258291.60 |
|
||||||
|
| 华南 | 李四 | 2 | 35 | 318969.00 |
|
||||||
|
| 华南 | 孙八 | 2 | 48 | 355464.00 |
|
||||||
|
| 华南 | 吴十 | 1 | 6 | 70622.80 |
|
||||||
|
+--------+-----------+-------------+----------------+-------------+
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 三列分组
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 按年龄段、性别、部门分组统计员工
|
||||||
|
SELECT
|
||||||
|
CASE
|
||||||
|
WHEN age < 25 THEN '24岁以下'
|
||||||
|
WHEN age <= 30 THEN '25-30岁'
|
||||||
|
WHEN age <= 35 THEN '31-35岁'
|
||||||
|
ELSE '35岁以上'
|
||||||
|
END AS age_group,
|
||||||
|
gender,
|
||||||
|
department,
|
||||||
|
COUNT(*) AS emp_count,
|
||||||
|
ROUND(AVG(salary), 2) AS avg_salary
|
||||||
|
FROM employees
|
||||||
|
GROUP BY
|
||||||
|
CASE
|
||||||
|
WHEN age < 25 THEN '24岁以下'
|
||||||
|
WHEN age <= 30 THEN '25-30岁'
|
||||||
|
WHEN age <= 35 THEN '31-35岁'
|
||||||
|
ELSE '35岁以上'
|
||||||
|
END,
|
||||||
|
gender,
|
||||||
|
department
|
||||||
|
HAVING COUNT(*) >= 1
|
||||||
|
ORDER BY age_group, gender, department;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 时间维度分组
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 按年、月、产品类别分组统计
|
||||||
|
SELECT
|
||||||
|
YEAR(order_date) AS order_year,
|
||||||
|
MONTH(order_date) AS order_month,
|
||||||
|
category,
|
||||||
|
COUNT(*) AS order_count,
|
||||||
|
SUM(quantity) AS total_quantity,
|
||||||
|
ROUND(SUM(quantity * unit_price * (1 - discount_rate)), 2) AS total_sales
|
||||||
|
FROM sales_orders
|
||||||
|
GROUP BY YEAR(order_date), MONTH(order_date), category
|
||||||
|
ORDER BY order_year, order_month, total_sales DESC;
|
||||||
|
```
|
||||||
|
|
||||||
|
## 复杂分组统计
|
||||||
|
|
||||||
|
### 1. 嵌套分组查询
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 查找每个部门薪资最高的员工信息
|
||||||
|
SELECT e1.*
|
||||||
|
FROM employees e1
|
||||||
|
WHERE e1.salary = (
|
||||||
|
SELECT MAX(e2.salary)
|
||||||
|
FROM employees e2
|
||||||
|
WHERE e2.department = e1.department
|
||||||
|
)
|
||||||
|
ORDER BY e1.department, e1.salary DESC;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 窗口函数与分组
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 计算每个员工在部门内的薪资排名
|
||||||
|
SELECT name, department, salary,
|
||||||
|
ROW_NUMBER() OVER (PARTITION BY department ORDER BY salary DESC) AS dept_rank,
|
||||||
|
ROUND(salary / AVG(salary) OVER (PARTITION BY department) * 100, 2) AS salary_ratio
|
||||||
|
FROM employees
|
||||||
|
ORDER BY department, dept_rank;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 数据透视表效果
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 按设备类型和浏览器统计访问情况
|
||||||
|
SELECT device_type,
|
||||||
|
SUM(CASE WHEN browser = 'Chrome' THEN page_views ELSE 0 END) AS Chrome,
|
||||||
|
SUM(CASE WHEN browser = 'Safari' THEN page_views ELSE 0 END) AS Safari,
|
||||||
|
SUM(CASE WHEN browser = 'Firefox' THEN page_views ELSE 0 END) AS Firefox,
|
||||||
|
SUM(CASE WHEN browser = 'Edge' THEN page_views ELSE 0 END) AS Edge,
|
||||||
|
SUM(page_views) AS Total
|
||||||
|
FROM web_logs
|
||||||
|
GROUP BY device_type
|
||||||
|
ORDER BY Total DESC;
|
||||||
|
```
|
||||||
|
|
||||||
|
**结果:**
|
||||||
|
```
|
||||||
|
+-------------+--------+--------+---------+------+-------+
|
||||||
|
| device_type | Chrome | Safari | Firefox | Edge | Total |
|
||||||
|
+-------------+--------+--------+---------+------+-------+
|
||||||
|
| Desktop | 24 | 0 | 1 | 7 | 32 |
|
||||||
|
| Mobile | 6 | 5 | 6 | 0 | 17 |
|
||||||
|
| Tablet | 4 | 9 | 0 | 0 | 13 |
|
||||||
|
+-------------+--------+--------+---------+------+-------+
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 动态分组条件
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 按库存状态分组统计产品
|
||||||
|
SELECT
|
||||||
|
CASE
|
||||||
|
WHEN stock_quantity <= reorder_level THEN '需要补货'
|
||||||
|
WHEN stock_quantity <= reorder_level * 2 THEN '库存偏低'
|
||||||
|
ELSE '库存充足'
|
||||||
|
END AS stock_status,
|
||||||
|
COUNT(*) AS product_count,
|
||||||
|
SUM(stock_quantity) AS total_stock,
|
||||||
|
ROUND(AVG(unit_cost), 2) AS avg_cost
|
||||||
|
FROM inventory
|
||||||
|
GROUP BY
|
||||||
|
CASE
|
||||||
|
WHEN stock_quantity <= reorder_level THEN '需要补货'
|
||||||
|
WHEN stock_quantity <= reorder_level * 2 THEN '库存偏低'
|
||||||
|
ELSE '库存充足'
|
||||||
|
END
|
||||||
|
ORDER BY
|
||||||
|
CASE
|
||||||
|
WHEN stock_status = '需要补货' THEN 1
|
||||||
|
WHEN stock_status = '库存偏低' THEN 2
|
||||||
|
ELSE 3
|
||||||
|
END;
|
||||||
|
```
|
||||||
|
|
||||||
|
## 面试题和实际案例
|
||||||
|
|
||||||
|
### 面试题1:连续登录用户分析
|
||||||
|
|
||||||
|
**题目**:找出连续3天都有访问记录的用户。
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 解答:使用窗口函数和分组
|
||||||
|
WITH daily_users AS (
|
||||||
|
SELECT DISTINCT user_id, visit_date
|
||||||
|
FROM web_logs
|
||||||
|
),
|
||||||
|
user_sequences AS (
|
||||||
|
SELECT user_id, visit_date,
|
||||||
|
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY visit_date) as rn,
|
||||||
|
DATE_SUB(visit_date, INTERVAL ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY visit_date) DAY) as group_date
|
||||||
|
FROM daily_users
|
||||||
|
),
|
||||||
|
consecutive_groups AS (
|
||||||
|
SELECT user_id, group_date, COUNT(*) as consecutive_days
|
||||||
|
FROM user_sequences
|
||||||
|
GROUP BY user_id, group_date
|
||||||
|
HAVING COUNT(*) >= 3
|
||||||
|
)
|
||||||
|
SELECT DISTINCT cg.user_id
|
||||||
|
FROM consecutive_groups cg;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 面试题2:同比增长率计算
|
||||||
|
|
||||||
|
**题目**:计算每个月的销售额同比增长率。
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 解答:使用自连接和分组
|
||||||
|
WITH monthly_sales AS (
|
||||||
|
SELECT
|
||||||
|
DATE_FORMAT(order_date, '%Y-%m') as month,
|
||||||
|
SUM(quantity * unit_price * (1 - discount_rate)) as total_sales
|
||||||
|
FROM sales_orders
|
||||||
|
GROUP BY DATE_FORMAT(order_date, '%Y-%m')
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
current_month.month,
|
||||||
|
current_month.total_sales as current_sales,
|
||||||
|
last_year.total_sales as last_year_sales,
|
||||||
|
ROUND(
|
||||||
|
(current_month.total_sales - IFNULL(last_year.total_sales, 0)) /
|
||||||
|
IFNULL(last_year.total_sales, current_month.total_sales) * 100, 2
|
||||||
|
) as growth_rate
|
||||||
|
FROM monthly_sales current_month
|
||||||
|
LEFT JOIN monthly_sales last_year
|
||||||
|
ON DATE_FORMAT(DATE_SUB(STR_TO_DATE(CONCAT(current_month.month, '-01'), '%Y-%m-%d'), INTERVAL 1 YEAR), '%Y-%m') = last_year.month
|
||||||
|
ORDER BY current_month.month;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 面试题3:帕累托分析(80/20法则)
|
||||||
|
|
||||||
|
**题目**:找出贡献80%销售额的前20%客户。
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 解答:使用窗口函数计算累计占比
|
||||||
|
WITH customer_sales AS (
|
||||||
|
SELECT customer_name,
|
||||||
|
SUM(quantity * unit_price * (1 - discount_rate)) as total_sales
|
||||||
|
FROM sales_orders
|
||||||
|
GROUP BY customer_name
|
||||||
|
),
|
||||||
|
customer_ranking AS (
|
||||||
|
SELECT customer_name, total_sales,
|
||||||
|
ROW_NUMBER() OVER (ORDER BY total_sales DESC) as rank_num,
|
||||||
|
COUNT(*) OVER () as total_customers,
|
||||||
|
SUM(total_sales) OVER () as grand_total,
|
||||||
|
SUM(total_sales) OVER (ORDER BY total_sales DESC) as cumulative_sales
|
||||||
|
FROM customer_sales
|
||||||
|
),
|
||||||
|
customer_contribution AS (
|
||||||
|
SELECT *,
|
||||||
|
ROUND(rank_num / total_customers * 100, 2) as customer_percentile,
|
||||||
|
ROUND(cumulative_sales / grand_total * 100, 2) as sales_percentile
|
||||||
|
FROM customer_ranking
|
||||||
|
)
|
||||||
|
SELECT customer_name, total_sales, customer_percentile, sales_percentile
|
||||||
|
FROM customer_contribution
|
||||||
|
WHERE sales_percentile <= 80
|
||||||
|
ORDER BY total_sales DESC;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 实际案例1:用户行为分析
|
||||||
|
|
||||||
|
**场景**:电商网站需要分析用户访问行为,优化用户体验。
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 综合用户行为分析
|
||||||
|
SELECT
|
||||||
|
country,
|
||||||
|
device_type,
|
||||||
|
COUNT(DISTINCT user_id) as unique_users,
|
||||||
|
COUNT(*) as total_sessions,
|
||||||
|
ROUND(COUNT(*) / COUNT(DISTINCT user_id), 2) as sessions_per_user,
|
||||||
|
ROUND(AVG(session_duration), 2) as avg_session_duration,
|
||||||
|
SUM(page_views) as total_page_views,
|
||||||
|
ROUND(SUM(page_views) / COUNT(*), 2) as pages_per_session,
|
||||||
|
ROUND(SUM(session_duration) / 60, 2) as total_hours
|
||||||
|
FROM web_logs
|
||||||
|
GROUP BY country, device_type
|
||||||
|
HAVING COUNT(DISTINCT user_id) >= 1
|
||||||
|
ORDER BY unique_users DESC, avg_session_duration DESC;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 实际案例2:销售业绩分析
|
||||||
|
|
||||||
|
**场景**:销售部门需要全面分析销售业绩,制定奖励策略。
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 销售代表综合业绩分析
|
||||||
|
WITH sales_performance AS (
|
||||||
|
SELECT sales_rep,
|
||||||
|
COUNT(*) as order_count,
|
||||||
|
COUNT(DISTINCT customer_name) as customer_count,
|
||||||
|
SUM(quantity) as total_quantity,
|
||||||
|
SUM(quantity * unit_price * (1 - discount_rate)) as total_sales,
|
||||||
|
ROUND(AVG(quantity * unit_price * (1 - discount_rate)), 2) as avg_order_value,
|
||||||
|
MAX(quantity * unit_price * (1 - discount_rate)) as max_order_value,
|
||||||
|
COUNT(DISTINCT category) as category_diversity
|
||||||
|
FROM sales_orders
|
||||||
|
GROUP BY sales_rep
|
||||||
|
),
|
||||||
|
performance_ranking AS (
|
||||||
|
SELECT *,
|
||||||
|
RANK() OVER (ORDER BY total_sales DESC) as sales_rank,
|
||||||
|
RANK() OVER (ORDER BY customer_count DESC) as customer_rank,
|
||||||
|
RANK() OVER (ORDER BY avg_order_value DESC) as avg_value_rank,
|
||||||
|
ROUND(total_sales / SUM(total_sales) OVER () * 100, 2) as sales_share
|
||||||
|
FROM sales_performance
|
||||||
|
)
|
||||||
|
SELECT sales_rep, total_sales, customer_count, avg_order_value,
|
||||||
|
sales_rank, customer_rank, sales_share,
|
||||||
|
CASE
|
||||||
|
WHEN sales_rank <= 2 THEN '金牌销售'
|
||||||
|
WHEN sales_rank <= 4 THEN '银牌销售'
|
||||||
|
ELSE '铜牌销售'
|
||||||
|
END as performance_level
|
||||||
|
FROM performance_ranking
|
||||||
|
ORDER BY sales_rank;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 实际案例3:库存管理优化
|
||||||
|
|
||||||
|
**场景**:仓库管理需要优化库存配置,减少缺货和积压。
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 库存分析和补货建议
|
||||||
|
SELECT
|
||||||
|
category,
|
||||||
|
warehouse_location,
|
||||||
|
COUNT(*) as product_count,
|
||||||
|
SUM(stock_quantity) as total_stock,
|
||||||
|
SUM(CASE WHEN stock_quantity <= reorder_level THEN 1 ELSE 0 END) as need_reorder,
|
||||||
|
ROUND(AVG(stock_quantity), 2) as avg_stock,
|
||||||
|
ROUND(AVG(unit_cost), 2) as avg_cost,
|
||||||
|
SUM(stock_quantity * unit_cost) as inventory_value,
|
||||||
|
ROUND(SUM(CASE WHEN stock_quantity <= reorder_level THEN unit_cost * reorder_level ELSE 0 END), 2) as reorder_investment
|
||||||
|
FROM inventory
|
||||||
|
GROUP BY category, warehouse_location
|
||||||
|
HAVING COUNT(*) >= 1
|
||||||
|
ORDER BY inventory_value DESC, need_reorder DESC;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 面试题4:数据质量检查
|
||||||
|
|
||||||
|
**题目**:检查数据中的异常情况。
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 多维度数据质量检查
|
||||||
|
SELECT
|
||||||
|
'sales_orders' as table_name,
|
||||||
|
'order_date' as check_field,
|
||||||
|
COUNT(*) as total_records,
|
||||||
|
SUM(CASE WHEN order_date IS NULL THEN 1 ELSE 0 END) as null_count,
|
||||||
|
SUM(CASE WHEN order_date > CURDATE() THEN 1 ELSE 0 END) as future_date_count,
|
||||||
|
MIN(order_date) as min_date,
|
||||||
|
MAX(order_date) as max_date
|
||||||
|
FROM sales_orders
|
||||||
|
|
||||||
|
UNION ALL
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
'employees' as table_name,
|
||||||
|
'salary' as check_field,
|
||||||
|
COUNT(*) as total_records,
|
||||||
|
SUM(CASE WHEN salary IS NULL THEN 1 ELSE 0 END) as null_count,
|
||||||
|
SUM(CASE WHEN salary <= 0 THEN 1 ELSE 0 END) as invalid_salary_count,
|
||||||
|
MIN(salary) as min_salary,
|
||||||
|
MAX(salary) as max_salary
|
||||||
|
FROM employees;
|
||||||
|
```
|
||||||
|
|
||||||
|
## 性能优化和最佳实践
|
||||||
|
|
||||||
|
### 1. 索引优化
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 为分组字段创建索引
|
||||||
|
CREATE INDEX idx_sales_orders_category ON sales_orders(category);
|
||||||
|
CREATE INDEX idx_sales_orders_sales_rep ON sales_orders(sales_rep);
|
||||||
|
CREATE INDEX idx_employees_department ON employees(department);
|
||||||
|
|
||||||
|
-- 复合索引优化分组查询
|
||||||
|
CREATE INDEX idx_sales_orders_date_region ON sales_orders(order_date, region);
|
||||||
|
CREATE INDEX idx_web_logs_user_date ON web_logs(user_id, visit_date);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 查询优化技巧
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- ✅ 推荐:使用WHERE过滤再分组
|
||||||
|
SELECT department, COUNT(*)
|
||||||
|
FROM employees
|
||||||
|
WHERE salary > 10000 -- 先过滤
|
||||||
|
GROUP BY department;
|
||||||
|
|
||||||
|
-- ❌ 避免:分组后再过滤
|
||||||
|
SELECT department, COUNT(*)
|
||||||
|
FROM employees
|
||||||
|
GROUP BY department
|
||||||
|
HAVING AVG(salary) > 10000; -- 这会计算所有组的平均值
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 避免不必要的分组
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- ❌ 不必要的分组
|
||||||
|
SELECT customer_name, SUM(quantity)
|
||||||
|
FROM sales_orders
|
||||||
|
WHERE customer_name = '阿里巴巴'
|
||||||
|
GROUP BY customer_name;
|
||||||
|
|
||||||
|
-- ✅ 优化:直接使用WHERE
|
||||||
|
SELECT '阿里巴巴' as customer_name, SUM(quantity)
|
||||||
|
FROM sales_orders
|
||||||
|
WHERE customer_name = '阿里巴巴';
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 大数据量分组优化
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 使用分区表优化大数据量分组
|
||||||
|
-- CREATE TABLE sales_orders_partitioned (
|
||||||
|
-- ...
|
||||||
|
-- ) PARTITION BY RANGE (YEAR(order_date)) (
|
||||||
|
-- PARTITION p2023 VALUES LESS THAN (2024),
|
||||||
|
-- PARTITION p2024 VALUES LESS THAN (2025)
|
||||||
|
-- );
|
||||||
|
|
||||||
|
-- 优化GROUP BY的内存使用
|
||||||
|
SET SESSION group_concat_max_len = 1000000;
|
||||||
|
SET SESSION tmp_table_size = 256000000;
|
||||||
|
SET SESSION max_heap_table_size = 256000000;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. 监控和调优
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 查看分组查询的执行计划
|
||||||
|
EXPLAIN SELECT department, COUNT(*), AVG(salary)
|
||||||
|
FROM employees
|
||||||
|
GROUP BY department;
|
||||||
|
|
||||||
|
-- 监控分组查询性能
|
||||||
|
SELECT
|
||||||
|
sql_text,
|
||||||
|
exec_count,
|
||||||
|
avg_timer_wait/1000000000000 as avg_time_sec,
|
||||||
|
sum_rows_examined/exec_count as avg_rows_examined
|
||||||
|
FROM performance_schema.events_statements_summary_by_digest
|
||||||
|
WHERE sql_text LIKE '%GROUP BY%'
|
||||||
|
ORDER BY avg_timer_wait DESC
|
||||||
|
LIMIT 10;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6. 最佳实践总结
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- ✅ 好的GROUP BY实践
|
||||||
|
SELECT
|
||||||
|
category, -- 分组字段
|
||||||
|
COUNT(*) as order_count, -- 明确的聚合函数
|
||||||
|
SUM(quantity) as total_qty -- 有意义的别名
|
||||||
|
FROM sales_orders
|
||||||
|
WHERE order_date >= '2024-01-01' -- 先过滤数据
|
||||||
|
GROUP BY category -- 简洁的分组
|
||||||
|
HAVING COUNT(*) > 1 -- 分组后过滤
|
||||||
|
ORDER BY total_qty DESC -- 有序输出
|
||||||
|
LIMIT 10; -- 限制结果数量
|
||||||
|
|
||||||
|
-- ❌ 避免的问题
|
||||||
|
-- 1. 在SELECT中使用非分组字段(MySQL 5.7+会报错)
|
||||||
|
-- 2. GROUP BY使用复杂表达式而不创建索引
|
||||||
|
-- 3. 不必要的HAVING条件
|
||||||
|
-- 4. 大量数据不加WHERE条件直接分组
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**总结:**
|
||||||
|
- GROUP BY 是数据分析的核心工具,配合聚合函数使用
|
||||||
|
- 理解执行顺序:WHERE → GROUP BY → HAVING → SELECT → ORDER BY
|
||||||
|
- 合理使用索引可以显著提升分组查询性能
|
||||||
|
- HAVING 用于过滤分组结果,WHERE 用于过滤原始数据
|
||||||
|
- 复杂分析可以结合窗口函数和子查询实现
|
||||||
|
- 注意数据类型和NULL值的处理
|
||||||
951
MySQL_HAVING语法使用文档.md
Normal file
951
MySQL_HAVING语法使用文档.md
Normal file
@ -0,0 +1,951 @@
|
|||||||
|
# MySQL HAVING 语法使用文档
|
||||||
|
|
||||||
|
## 目录
|
||||||
|
1. [HAVING 基础语法](#having-基础语法)
|
||||||
|
2. [HAVING 规则和特性](#having-规则和特性)
|
||||||
|
3. [HAVING vs WHERE 详细对比](#having-vs-where-详细对比)
|
||||||
|
4. [示例数据准备](#示例数据准备)
|
||||||
|
5. [基础 HAVING 查询](#基础-having-查询)
|
||||||
|
6. [复杂 HAVING 条件](#复杂-having-条件)
|
||||||
|
7. [HAVING 与聚合函数组合](#having-与聚合函数组合)
|
||||||
|
8. [多层嵌套和子查询](#多层嵌套和子查询)
|
||||||
|
9. [面试题和实际案例](#面试题和实际案例)
|
||||||
|
10. [性能优化和最佳实践](#性能优化和最佳实践)
|
||||||
|
|
||||||
|
## HAVING 基础语法
|
||||||
|
|
||||||
|
HAVING 子句用于过滤 GROUP BY 分组后的结果,类似于 WHERE 子句,但作用于分组之后。
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SELECT column1, aggregate_function(column2)
|
||||||
|
FROM table_name
|
||||||
|
[WHERE condition]
|
||||||
|
GROUP BY column1, column2, ...
|
||||||
|
HAVING group_condition
|
||||||
|
[ORDER BY column]
|
||||||
|
[LIMIT number];
|
||||||
|
```
|
||||||
|
|
||||||
|
**语法要点:**
|
||||||
|
- HAVING 必须在 GROUP BY 之后
|
||||||
|
- HAVING 条件可以使用聚合函数
|
||||||
|
- HAVING 可以引用 SELECT 列表中的别名
|
||||||
|
- HAVING 可以与 WHERE 同时使用
|
||||||
|
|
||||||
|
## HAVING 规则和特性
|
||||||
|
|
||||||
|
### 1. **执行顺序**
|
||||||
|
```
|
||||||
|
FROM → WHERE → GROUP BY → 聚合计算 → HAVING → SELECT → ORDER BY → LIMIT
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. **可用条件类型**
|
||||||
|
- 聚合函数条件:`COUNT(*) > 5`
|
||||||
|
- 分组字段条件:`department = '技术部'`
|
||||||
|
- 聚合函数比较:`AVG(salary) > MAX(bonus)`
|
||||||
|
- 复合条件:`COUNT(*) > 2 AND AVG(score) >= 80`
|
||||||
|
|
||||||
|
### 3. **与 WHERE 的区别**
|
||||||
|
| 特性 | WHERE | HAVING |
|
||||||
|
|------|-------|--------|
|
||||||
|
| 作用时机 | 分组前过滤行 | 分组后过滤组 |
|
||||||
|
| 可用函数 | 不能使用聚合函数 | 可以使用聚合函数 |
|
||||||
|
| 性能 | 更高效(减少分组数据) | 相对较低(先分组再过滤) |
|
||||||
|
| 使用场景 | 过滤原始数据 | 过滤聚合结果 |
|
||||||
|
|
||||||
|
## HAVING vs WHERE 详细对比
|
||||||
|
|
||||||
|
### 1. 基础区别演示
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 示例数据:员工表
|
||||||
|
CREATE TABLE demo_employees (
|
||||||
|
id INT PRIMARY KEY,
|
||||||
|
name VARCHAR(50),
|
||||||
|
department VARCHAR(30),
|
||||||
|
salary DECIMAL(10,2),
|
||||||
|
bonus DECIMAL(8,2)
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO demo_employees VALUES
|
||||||
|
(1, '张三', '技术部', 15000, 3000),
|
||||||
|
(2, '李四', '技术部', 18000, 4000),
|
||||||
|
(3, '王五', '销售部', 12000, 8000),
|
||||||
|
(4, '赵六', '销售部', 14000, 6000),
|
||||||
|
(5, '钱七', '技术部', 16000, 3500),
|
||||||
|
(6, '孙八', '人事部', 13000, 2000);
|
||||||
|
```
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- WHERE:先过滤薪资 > 14000 的员工,再分组统计
|
||||||
|
SELECT department, COUNT(*) as emp_count, AVG(salary) as avg_salary
|
||||||
|
FROM demo_employees
|
||||||
|
WHERE salary > 14000
|
||||||
|
GROUP BY department;
|
||||||
|
```
|
||||||
|
|
||||||
|
**结果:**
|
||||||
|
```
|
||||||
|
+----------+-----------+------------+
|
||||||
|
| department | emp_count | avg_salary |
|
||||||
|
+----------+-----------+------------+
|
||||||
|
| 技术部 | 3 | 16333.33 |
|
||||||
|
| 销售部 | 1 | 14000.00 |
|
||||||
|
+----------+-----------+------------+
|
||||||
|
```
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- HAVING:先分组统计,再过滤平均薪资 > 14000 的部门
|
||||||
|
SELECT department, COUNT(*) as emp_count, AVG(salary) as avg_salary
|
||||||
|
FROM demo_employees
|
||||||
|
GROUP BY department
|
||||||
|
HAVING AVG(salary) > 14000;
|
||||||
|
```
|
||||||
|
|
||||||
|
**结果:**
|
||||||
|
```
|
||||||
|
+----------+-----------+------------+
|
||||||
|
| department | emp_count | avg_salary |
|
||||||
|
+----------+-----------+------------+
|
||||||
|
| 技术部 | 3 | 16333.33 |
|
||||||
|
+----------+-----------+------------+
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 同时使用 WHERE 和 HAVING
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 组合使用:先过滤 bonus > 2500 的员工,分组后再过滤员工数量 > 1 的部门
|
||||||
|
SELECT department,
|
||||||
|
COUNT(*) as emp_count,
|
||||||
|
ROUND(AVG(salary), 2) as avg_salary,
|
||||||
|
ROUND(AVG(bonus), 2) as avg_bonus
|
||||||
|
FROM demo_employees
|
||||||
|
WHERE bonus > 2500 -- 先过滤原始数据
|
||||||
|
GROUP BY department
|
||||||
|
HAVING COUNT(*) > 1; -- 再过滤分组结果
|
||||||
|
```
|
||||||
|
|
||||||
|
**结果:**
|
||||||
|
```
|
||||||
|
+----------+-----------+------------+-----------+
|
||||||
|
| department | emp_count | avg_salary | avg_bonus |
|
||||||
|
+----------+-----------+------------+-----------+
|
||||||
|
| 技术部 | 3 | 16333.33 | 3500.00 |
|
||||||
|
| 销售部 | 2 | 13000.00 | 7000.00 |
|
||||||
|
+----------+-----------+------------+-----------+
|
||||||
|
```
|
||||||
|
|
||||||
|
## 示例数据准备
|
||||||
|
|
||||||
|
### 创建业务场景表
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 销售业绩表
|
||||||
|
CREATE TABLE sales_performance (
|
||||||
|
id INT PRIMARY KEY AUTO_INCREMENT,
|
||||||
|
sales_rep VARCHAR(50),
|
||||||
|
region VARCHAR(30),
|
||||||
|
product_category VARCHAR(50),
|
||||||
|
sale_date DATE,
|
||||||
|
amount DECIMAL(10,2),
|
||||||
|
quantity INT,
|
||||||
|
customer_type ENUM('新客户', '老客户', 'VIP客户'),
|
||||||
|
commission_rate DECIMAL(3,2)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 课程选课表
|
||||||
|
CREATE TABLE course_enrollments (
|
||||||
|
student_id INT,
|
||||||
|
student_name VARCHAR(50),
|
||||||
|
course_id VARCHAR(20),
|
||||||
|
course_name VARCHAR(100),
|
||||||
|
credits INT,
|
||||||
|
score DECIMAL(4,1),
|
||||||
|
semester VARCHAR(20),
|
||||||
|
teacher VARCHAR(30),
|
||||||
|
department VARCHAR(30),
|
||||||
|
PRIMARY KEY (student_id, course_id, semester)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 电商订单表
|
||||||
|
CREATE TABLE ecommerce_orders (
|
||||||
|
order_id INT PRIMARY KEY AUTO_INCREMENT,
|
||||||
|
customer_id INT,
|
||||||
|
customer_segment VARCHAR(20),
|
||||||
|
product_id INT,
|
||||||
|
category VARCHAR(50),
|
||||||
|
subcategory VARCHAR(50),
|
||||||
|
order_date DATE,
|
||||||
|
ship_date DATE,
|
||||||
|
quantity INT,
|
||||||
|
unit_price DECIMAL(8,2),
|
||||||
|
discount DECIMAL(3,2),
|
||||||
|
profit DECIMAL(8,2),
|
||||||
|
region VARCHAR(30),
|
||||||
|
state VARCHAR(30)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 网站流量分析表
|
||||||
|
CREATE TABLE website_analytics (
|
||||||
|
session_id VARCHAR(50) PRIMARY KEY,
|
||||||
|
user_id INT,
|
||||||
|
visit_date DATE,
|
||||||
|
page_views INT,
|
||||||
|
session_duration INT, -- 秒
|
||||||
|
bounce_rate DECIMAL(3,2),
|
||||||
|
conversion_flag BOOLEAN,
|
||||||
|
traffic_source VARCHAR(30),
|
||||||
|
device_category VARCHAR(20),
|
||||||
|
country VARCHAR(30),
|
||||||
|
revenue DECIMAL(8,2)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 员工绩效表
|
||||||
|
CREATE TABLE employee_performance (
|
||||||
|
emp_id INT,
|
||||||
|
name VARCHAR(50),
|
||||||
|
department VARCHAR(30),
|
||||||
|
position VARCHAR(50),
|
||||||
|
quarter VARCHAR(10),
|
||||||
|
kpi_score DECIMAL(3,1),
|
||||||
|
project_count INT,
|
||||||
|
overtime_hours INT,
|
||||||
|
team_rating DECIMAL(2,1),
|
||||||
|
bonus DECIMAL(8,2),
|
||||||
|
PRIMARY KEY (emp_id, quarter)
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 插入示例数据
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 插入销售业绩数据
|
||||||
|
INSERT INTO sales_performance (sales_rep, region, product_category, sale_date, amount, quantity, customer_type, commission_rate) VALUES
|
||||||
|
('张明', '华北', '电子产品', '2024-01-15', 15000.00, 5, '新客户', 0.08),
|
||||||
|
('张明', '华北', '家电', '2024-01-20', 8000.00, 2, '老客户', 0.06),
|
||||||
|
('张明', '华北', '电子产品', '2024-01-25', 25000.00, 8, 'VIP客户', 0.10),
|
||||||
|
('李华', '华东', '服装', '2024-01-18', 12000.00, 20, '新客户', 0.07),
|
||||||
|
('李华', '华东', '电子产品', '2024-01-22', 18000.00, 6, 'VIP客户', 0.09),
|
||||||
|
('李华', '华东', '服装', '2024-01-28', 9000.00, 15, '老客户', 0.05),
|
||||||
|
('王强', '华南', '家电', '2024-01-16', 22000.00, 4, 'VIP客户', 0.08),
|
||||||
|
('王强', '华南', '电子产品', '2024-01-24', 16000.00, 7, '新客户', 0.07),
|
||||||
|
('王强', '华南', '家电', '2024-01-30', 11000.00, 3, '老客户', 0.06),
|
||||||
|
('赵丽', '华西', '服装', '2024-01-19', 7000.00, 12, '新客户', 0.06),
|
||||||
|
('赵丽', '华西', '服装', '2024-01-26', 13000.00, 18, 'VIP客户', 0.08),
|
||||||
|
('孙涛', '华北', '电子产品', '2024-01-21', 20000.00, 6, 'VIP客户', 0.09),
|
||||||
|
('孙涛', '华北', '家电', '2024-01-27', 14000.00, 5, '老客户', 0.07);
|
||||||
|
|
||||||
|
-- 插入课程选课数据
|
||||||
|
INSERT INTO course_enrollments VALUES
|
||||||
|
(1001, '张小明', 'CS101', '计算机科学导论', 3, 85.5, '2024春', '王教授', '计算机系'),
|
||||||
|
(1001, '张小明', 'MATH201', '高等数学', 4, 78.0, '2024春', '李教授', '数学系'),
|
||||||
|
(1001, '张小明', 'ENG101', '大学英语', 2, 88.5, '2024春', '陈教授', '外语系'),
|
||||||
|
(1002, '李小红', 'CS101', '计算机科学导论', 3, 92.0, '2024春', '王教授', '计算机系'),
|
||||||
|
(1002, '李小红', 'MATH201', '高等数学', 4, 86.5, '2024春', '李教授', '数学系'),
|
||||||
|
(1002, '李小红', 'PHYS101', '大学物理', 3, 79.0, '2024春', '张教授', '物理系'),
|
||||||
|
(1003, '王小刚', 'CS102', '程序设计', 3, 76.5, '2024春', '赵教授', '计算机系'),
|
||||||
|
(1003, '王小刚', 'MATH201', '高等数学', 4, 82.0, '2024春', '李教授', '数学系'),
|
||||||
|
(1003, '王小刚', 'ENG101', '大学英语', 2, 74.5, '2024春', '陈教授', '外语系'),
|
||||||
|
(1004, '赵小丽', 'CS101', '计算机科学导论', 3, 89.0, '2024春', '王教授', '计算机系'),
|
||||||
|
(1004, '赵小丽', 'MATH202', '线性代数', 3, 91.5, '2024春', '孙教授', '数学系'),
|
||||||
|
(1004, '赵小丽', 'PHYS101', '大学物理', 3, 84.5, '2024春', '张教授', '物理系'),
|
||||||
|
(1005, '钱小伟', 'CS102', '程序设计', 3, 95.0, '2024春', '赵教授', '计算机系'),
|
||||||
|
(1005, '钱小伟', 'MATH202', '线性代数', 3, 88.0, '2024春', '孙教授', '数学系'),
|
||||||
|
(1005, '钱小伟', 'ENG102', '英语写作', 2, 85.5, '2024春', '周教授', '外语系');
|
||||||
|
|
||||||
|
-- 插入电商订单数据
|
||||||
|
INSERT INTO ecommerce_orders (customer_id, customer_segment, product_id, category, subcategory, order_date, ship_date, quantity, unit_price, discount, profit, region, state) VALUES
|
||||||
|
(1001, 'Consumer', 2001, 'Technology', 'Phones', '2024-01-15', '2024-01-17', 2, 999.99, 0.10, 400.00, 'East', 'New York'),
|
||||||
|
(1001, 'Consumer', 2002, 'Technology', 'Accessories', '2024-01-16', '2024-01-18', 5, 29.99, 0.05, 50.00, 'East', 'New York'),
|
||||||
|
(1002, 'Corporate', 2003, 'Office Supplies', 'Storage', '2024-01-18', '2024-01-20', 10, 15.99, 0.15, 80.00, 'West', 'California'),
|
||||||
|
(1002, 'Corporate', 2004, 'Furniture', 'Chairs', '2024-01-19', '2024-01-22', 3, 299.99, 0.20, 300.00, 'West', 'California'),
|
||||||
|
(1003, 'Home Office', 2005, 'Technology', 'Computers', '2024-01-20', '2024-01-23', 1, 1299.99, 0.08, 200.00, 'Central', 'Texas'),
|
||||||
|
(1003, 'Home Office', 2006, 'Office Supplies', 'Paper', '2024-01-21', '2024-01-24', 20, 12.99, 0.00, 100.00, 'Central', 'Texas'),
|
||||||
|
(1004, 'Consumer', 2007, 'Technology', 'Phones', '2024-01-22', '2024-01-25', 1, 1199.99, 0.05, 300.00, 'East', 'Florida'),
|
||||||
|
(1004, 'Consumer', 2008, 'Furniture', 'Tables', '2024-01-23', '2024-01-26', 2, 199.99, 0.10, 150.00, 'East', 'Florida'),
|
||||||
|
(1005, 'Corporate', 2009, 'Office Supplies', 'Binders', '2024-01-24', '2024-01-27', 50, 8.99, 0.25, 200.00, 'West', 'Oregon'),
|
||||||
|
(1005, 'Corporate', 2010, 'Technology', 'Accessories', '2024-01-25', '2024-01-28', 15, 49.99, 0.12, 180.00, 'West', 'Oregon');
|
||||||
|
|
||||||
|
-- 插入网站流量数据
|
||||||
|
INSERT INTO website_analytics VALUES
|
||||||
|
('sess_001', 1001, '2024-01-15', 8, 450, 0.00, TRUE, 'Google', 'Desktop', 'USA', 299.99),
|
||||||
|
('sess_002', 1002, '2024-01-15', 3, 120, 1.00, FALSE, 'Facebook', 'Mobile', 'Canada', 0.00),
|
||||||
|
('sess_003', 1003, '2024-01-16', 12, 780, 0.00, TRUE, 'Direct', 'Desktop', 'UK', 599.99),
|
||||||
|
('sess_004', 1004, '2024-01-16', 5, 230, 0.00, FALSE, 'Google', 'Tablet', 'Germany', 0.00),
|
||||||
|
('sess_005', 1001, '2024-01-17', 15, 920, 0.00, TRUE, 'Google', 'Desktop', 'USA', 1299.99),
|
||||||
|
('sess_006', 1005, '2024-01-17', 6, 340, 0.00, TRUE, 'Bing', 'Mobile', 'Australia', 199.99),
|
||||||
|
('sess_007', 1006, '2024-01-18', 2, 45, 1.00, FALSE, 'Facebook', 'Mobile', 'Brazil', 0.00),
|
||||||
|
('sess_008', 1007, '2024-01-18', 9, 560, 0.00, TRUE, 'Direct', 'Desktop', 'Japan', 799.99),
|
||||||
|
('sess_009', 1008, '2024-01-19', 4, 180, 0.50, FALSE, 'Google', 'Mobile', 'India', 0.00),
|
||||||
|
('sess_010', 1002, '2024-01-19', 11, 650, 0.00, TRUE, 'Direct', 'Desktop', 'Canada', 399.99);
|
||||||
|
|
||||||
|
-- 插入员工绩效数据
|
||||||
|
INSERT INTO employee_performance VALUES
|
||||||
|
(1001, '张工程师', '技术部', '高级工程师', '2024Q1', 8.5, 3, 45, 4.2, 8000.00),
|
||||||
|
(1001, '张工程师', '技术部', '高级工程师', '2023Q4', 9.2, 4, 52, 4.5, 12000.00),
|
||||||
|
(1002, '李架构师', '技术部', '系统架构师', '2024Q1', 9.0, 2, 38, 4.8, 15000.00),
|
||||||
|
(1002, '李架构师', '技术部', '系统架构师', '2023Q4', 8.8, 3, 42, 4.6, 13000.00),
|
||||||
|
(1003, '王产品', '产品部', '产品经理', '2024Q1', 7.8, 5, 35, 4.0, 6000.00),
|
||||||
|
(1003, '王产品', '产品部', '产品经理', '2023Q4', 8.2, 4, 28, 4.3, 7500.00),
|
||||||
|
(1004, '赵销售', '销售部', '销售经理', '2024Q1', 9.5, 8, 60, 4.7, 18000.00),
|
||||||
|
(1004, '赵销售', '销售部', '销售经理', '2023Q4', 9.1, 6, 55, 4.4, 16000.00),
|
||||||
|
(1005, '钱测试', '技术部', '测试工程师', '2024Q1', 8.0, 4, 40, 3.9, 5000.00),
|
||||||
|
(1005, '钱测试', '技术部', '测试工程师', '2023Q4', 7.5, 3, 35, 3.7, 4000.00),
|
||||||
|
(1006, '孙设计', '产品部', 'UI设计师', '2024Q1', 8.7, 6, 25, 4.1, 7000.00);
|
||||||
|
```
|
||||||
|
|
||||||
|
## 基础 HAVING 查询
|
||||||
|
|
||||||
|
### 1. 简单聚合函数过滤
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 查找订单数量超过1个的销售代表
|
||||||
|
SELECT sales_rep,
|
||||||
|
COUNT(*) as order_count,
|
||||||
|
ROUND(SUM(amount), 2) as total_sales
|
||||||
|
FROM sales_performance
|
||||||
|
GROUP BY sales_rep
|
||||||
|
HAVING COUNT(*) > 1
|
||||||
|
ORDER BY total_sales DESC;
|
||||||
|
```
|
||||||
|
|
||||||
|
**结果:**
|
||||||
|
```
|
||||||
|
+----------+-------------+-------------+
|
||||||
|
| sales_rep | order_count | total_sales |
|
||||||
|
+----------+-------------+-------------+
|
||||||
|
| 王强 | 3 | 49000.00 |
|
||||||
|
| 张明 | 3 | 48000.00 |
|
||||||
|
| 李华 | 3 | 39000.00 |
|
||||||
|
| 赵丽 | 2 | 20000.00 |
|
||||||
|
+----------+-------------+-------------+
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 平均值过滤
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 查找平均成绩大于85分的学生
|
||||||
|
SELECT student_name,
|
||||||
|
COUNT(*) as course_count,
|
||||||
|
ROUND(AVG(score), 2) as avg_score,
|
||||||
|
ROUND(SUM(credits * score) / SUM(credits), 2) as weighted_avg
|
||||||
|
FROM course_enrollments
|
||||||
|
GROUP BY student_id, student_name
|
||||||
|
HAVING AVG(score) > 85
|
||||||
|
ORDER BY avg_score DESC;
|
||||||
|
```
|
||||||
|
|
||||||
|
**结果:**
|
||||||
|
```
|
||||||
|
+-----------+--------------+-----------+-------------+
|
||||||
|
| student_name | course_count | avg_score | weighted_avg |
|
||||||
|
+-----------+--------------+-----------+-------------+
|
||||||
|
| 钱小伟 | 3 | 89.50 | 89.45 |
|
||||||
|
| 李小红 | 3 | 85.83 | 85.95 |
|
||||||
|
+-----------+--------------+-----------+-------------+
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 最值过滤
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 查找最高销售额超过20000的地区
|
||||||
|
SELECT region,
|
||||||
|
COUNT(*) as order_count,
|
||||||
|
MAX(amount) as max_sale,
|
||||||
|
MIN(amount) as min_sale,
|
||||||
|
ROUND(AVG(amount), 2) as avg_sale
|
||||||
|
FROM sales_performance
|
||||||
|
GROUP BY region
|
||||||
|
HAVING MAX(amount) > 20000
|
||||||
|
ORDER BY max_sale DESC;
|
||||||
|
```
|
||||||
|
|
||||||
|
**结果:**
|
||||||
|
```
|
||||||
|
+--------+-------------+----------+----------+----------+
|
||||||
|
| region | order_count | max_sale | min_sale | avg_sale |
|
||||||
|
+--------+-------------+----------+----------+----------+
|
||||||
|
| 华北 | 5 | 25000.00 | 8000.00 | 16400.00 |
|
||||||
|
| 华南 | 3 | 22000.00 | 11000.00 | 16333.33 |
|
||||||
|
+--------+-------------+----------+----------+----------+
|
||||||
|
```
|
||||||
|
|
||||||
|
## 复杂 HAVING 条件
|
||||||
|
|
||||||
|
### 1. 多条件组合
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 查找课程数量>=3且平均分>=80且总学分>=8的学生
|
||||||
|
SELECT student_name,
|
||||||
|
COUNT(*) as course_count,
|
||||||
|
ROUND(AVG(score), 2) as avg_score,
|
||||||
|
SUM(credits) as total_credits,
|
||||||
|
GROUP_CONCAT(course_name ORDER BY score DESC) as courses
|
||||||
|
FROM course_enrollments
|
||||||
|
GROUP BY student_id, student_name
|
||||||
|
HAVING COUNT(*) >= 3
|
||||||
|
AND AVG(score) >= 80
|
||||||
|
AND SUM(credits) >= 8
|
||||||
|
ORDER BY avg_score DESC;
|
||||||
|
```
|
||||||
|
|
||||||
|
**结果:**
|
||||||
|
```
|
||||||
|
+-----------+--------------+-----------+---------------+--------------------------------------------------+
|
||||||
|
| student_name | course_count | avg_score | total_credits | courses |
|
||||||
|
+-----------+--------------+-----------+---------------+--------------------------------------------------+
|
||||||
|
| 钱小伟 | 3 | 89.50 | 8 | 程序设计,线性代数,英语写作 |
|
||||||
|
| 李小红 | 3 | 85.83 | 10 | 计算机科学导论,高等数学,大学物理 |
|
||||||
|
| 张小明 | 3 | 84.00 | 9 | 大学英语,计算机科学导论,高等数学 |
|
||||||
|
| 赵小丽 | 3 | 88.33 | 9 | 线性代数,计算机科学导论,大学物理 |
|
||||||
|
+-----------+--------------+-----------+---------------+--------------------------------------------------+
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 范围条件
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 查找平均页面浏览量在5-10之间的流量来源
|
||||||
|
SELECT traffic_source,
|
||||||
|
COUNT(*) as session_count,
|
||||||
|
ROUND(AVG(page_views), 2) as avg_page_views,
|
||||||
|
ROUND(AVG(session_duration), 2) as avg_duration,
|
||||||
|
SUM(conversion_flag) as conversions
|
||||||
|
FROM website_analytics
|
||||||
|
GROUP BY traffic_source
|
||||||
|
HAVING AVG(page_views) BETWEEN 5 AND 10
|
||||||
|
ORDER BY avg_page_views DESC;
|
||||||
|
```
|
||||||
|
|
||||||
|
**结果:**
|
||||||
|
```
|
||||||
|
+---------------+---------------+-----------------+--------------+-------------+
|
||||||
|
| traffic_source | session_count | avg_page_views | avg_duration | conversions |
|
||||||
|
+---------------+---------------+-----------------+--------------+-------------+
|
||||||
|
| Direct | 3 | 7.33 | 428.33 | 2 |
|
||||||
|
| Google | 4 | 7.00 | 372.50 | 2 |
|
||||||
|
+---------------+---------------+-----------------+--------------+-------------+
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 百分比和比率条件
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 查找转化率大于50%的设备类型
|
||||||
|
SELECT device_category,
|
||||||
|
COUNT(*) as total_sessions,
|
||||||
|
SUM(conversion_flag) as conversions,
|
||||||
|
ROUND(SUM(conversion_flag) / COUNT(*) * 100, 2) as conversion_rate,
|
||||||
|
ROUND(AVG(revenue), 2) as avg_revenue
|
||||||
|
FROM website_analytics
|
||||||
|
GROUP BY device_category
|
||||||
|
HAVING (SUM(conversion_flag) / COUNT(*) * 100) > 50
|
||||||
|
ORDER BY conversion_rate DESC;
|
||||||
|
```
|
||||||
|
|
||||||
|
**结果:**
|
||||||
|
```
|
||||||
|
+-----------------+----------------+-------------+-----------------+-------------+
|
||||||
|
| device_category | total_sessions | conversions | conversion_rate | avg_revenue |
|
||||||
|
+-----------------+----------------+-------------+-----------------+-------------+
|
||||||
|
| Desktop | 5 | 4 | 80.00 | 619.99 |
|
||||||
|
+-----------------+----------------+-------------+-----------------+-------------+
|
||||||
|
```
|
||||||
|
|
||||||
|
## HAVING 与聚合函数组合
|
||||||
|
|
||||||
|
### 1. COUNT 相关条件
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 多维度计数条件
|
||||||
|
SELECT department,
|
||||||
|
quarter,
|
||||||
|
COUNT(*) as emp_count,
|
||||||
|
COUNT(CASE WHEN kpi_score >= 9.0 THEN 1 END) as excellent_count,
|
||||||
|
COUNT(CASE WHEN overtime_hours > 50 THEN 1 END) as overtime_count,
|
||||||
|
ROUND(AVG(kpi_score), 2) as avg_kpi
|
||||||
|
FROM employee_performance
|
||||||
|
GROUP BY department, quarter
|
||||||
|
HAVING COUNT(*) >= 2 -- 至少2名员工
|
||||||
|
AND COUNT(CASE WHEN kpi_score >= 9.0 THEN 1 END) > 0 -- 至少1名优秀员工
|
||||||
|
ORDER BY department, quarter;
|
||||||
|
```
|
||||||
|
|
||||||
|
**结果:**
|
||||||
|
```
|
||||||
|
+----------+---------+-----------+-----------------+----------------+---------+
|
||||||
|
| department | quarter | emp_count | excellent_count | overtime_count | avg_kpi |
|
||||||
|
+----------+---------+-----------+-----------------+----------------+---------+
|
||||||
|
| 技术部 | 2023Q4 | 2 | 1 | 2 | 9.00 |
|
||||||
|
| 技术部 | 2024Q1 | 2 | 1 | 2 | 8.25 |
|
||||||
|
+----------+---------+-----------+-----------------+----------------+---------+
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. SUM 和计算字段
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 基于计算字段的过滤
|
||||||
|
SELECT customer_segment,
|
||||||
|
state,
|
||||||
|
COUNT(*) as order_count,
|
||||||
|
SUM(quantity) as total_quantity,
|
||||||
|
ROUND(SUM(unit_price * quantity * (1 - discount)), 2) as revenue,
|
||||||
|
ROUND(SUM(profit), 2) as total_profit,
|
||||||
|
ROUND(SUM(profit) / SUM(unit_price * quantity * (1 - discount)) * 100, 2) as profit_margin
|
||||||
|
FROM ecommerce_orders
|
||||||
|
GROUP BY customer_segment, state
|
||||||
|
HAVING SUM(unit_price * quantity * (1 - discount)) > 1000 -- 收入超过1000
|
||||||
|
AND SUM(profit) / SUM(unit_price * quantity * (1 - discount)) > 0.15 -- 利润率超过15%
|
||||||
|
ORDER BY profit_margin DESC;
|
||||||
|
```
|
||||||
|
|
||||||
|
**结果:**
|
||||||
|
```
|
||||||
|
+------------------+------------+-------------+----------------+---------+-------------+--------------+
|
||||||
|
| customer_segment | state | order_count | total_quantity | revenue | total_profit | profit_margin |
|
||||||
|
+------------------+------------+-------------+----------------+---------+-------------+--------------+
|
||||||
|
| Home Office | Texas | 2 | 21 | 1559.79 | 300.00 | 19.23 |
|
||||||
|
| Consumer | Florida | 2 | 3 | 1499.99 | 450.00 | 30.00 |
|
||||||
|
+------------------+------------+-------------+----------------+---------+-------------+--------------+
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 复杂聚合条件
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 组合多种聚合函数的复杂条件
|
||||||
|
SELECT product_category,
|
||||||
|
customer_type,
|
||||||
|
COUNT(*) as sale_count,
|
||||||
|
ROUND(AVG(amount), 2) as avg_amount,
|
||||||
|
ROUND(STD(amount), 2) as amount_std,
|
||||||
|
MIN(amount) as min_amount,
|
||||||
|
MAX(amount) as max_amount,
|
||||||
|
ROUND(SUM(amount * commission_rate), 2) as total_commission
|
||||||
|
FROM sales_performance
|
||||||
|
GROUP BY product_category, customer_type
|
||||||
|
HAVING COUNT(*) >= 2 -- 至少2笔销售
|
||||||
|
AND AVG(amount) > 10000 -- 平均金额超过10000
|
||||||
|
AND (MAX(amount) - MIN(amount)) > 5000 -- 金额差异超过5000
|
||||||
|
AND STD(amount) < 8000 -- 标准差小于8000(相对稳定)
|
||||||
|
ORDER BY avg_amount DESC;
|
||||||
|
```
|
||||||
|
|
||||||
|
**结果:**
|
||||||
|
```
|
||||||
|
+------------------+---------------+------------+------------+------------+------------+------------+------------------+
|
||||||
|
| product_category | customer_type | sale_count | avg_amount | amount_std | min_amount | max_amount | total_commission |
|
||||||
|
+------------------+---------------+------------+------------+------------+------------+------------+------------------+
|
||||||
|
| 电子产品 | VIP客户 | 2 | 21500.00 | 3500.00 | 18000.00 | 25000.00 | 1620.00 |
|
||||||
|
| 家电 | VIP客户 | 2 | 16500.00 | 7778.17 | 11000.00 | 22000.00 | 1320.00 |
|
||||||
|
+------------------+---------------+------------+------------+------------+------------+------------+------------------+
|
||||||
|
```
|
||||||
|
|
||||||
|
## 多层嵌套和子查询
|
||||||
|
|
||||||
|
### 1. HAVING 中使用子查询
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 查找销售额超过平均水平的销售代表
|
||||||
|
SELECT sales_rep,
|
||||||
|
COUNT(*) as order_count,
|
||||||
|
ROUND(SUM(amount), 2) as total_sales,
|
||||||
|
ROUND(AVG(amount), 2) as avg_order_value
|
||||||
|
FROM sales_performance
|
||||||
|
GROUP BY sales_rep
|
||||||
|
HAVING SUM(amount) > (
|
||||||
|
SELECT AVG(total_sales)
|
||||||
|
FROM (
|
||||||
|
SELECT SUM(amount) as total_sales
|
||||||
|
FROM sales_performance
|
||||||
|
GROUP BY sales_rep
|
||||||
|
) as rep_totals
|
||||||
|
)
|
||||||
|
ORDER BY total_sales DESC;
|
||||||
|
```
|
||||||
|
|
||||||
|
**结果:**
|
||||||
|
```
|
||||||
|
+----------+-------------+-------------+-----------------+
|
||||||
|
| sales_rep | order_count | total_sales | avg_order_value |
|
||||||
|
+----------+-------------+-------------+-----------------+
|
||||||
|
| 王强 | 3 | 49000.00 | 16333.33 |
|
||||||
|
| 张明 | 3 | 48000.00 | 16000.00 |
|
||||||
|
| 李华 | 3 | 39000.00 | 13000.00 |
|
||||||
|
+----------+-------------+-------------+-----------------+
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 嵌套分组查询
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 查找在多个系别都有课程且平均分都超过80的教师
|
||||||
|
SELECT teacher,
|
||||||
|
COUNT(DISTINCT department) as dept_count,
|
||||||
|
COUNT(*) as course_count,
|
||||||
|
ROUND(AVG(score), 2) as overall_avg_score,
|
||||||
|
GROUP_CONCAT(DISTINCT department) as departments
|
||||||
|
FROM course_enrollments
|
||||||
|
GROUP BY teacher
|
||||||
|
HAVING COUNT(DISTINCT department) > 1
|
||||||
|
AND teacher IN (
|
||||||
|
SELECT teacher
|
||||||
|
FROM course_enrollments
|
||||||
|
GROUP BY teacher, department
|
||||||
|
HAVING AVG(score) > 80
|
||||||
|
)
|
||||||
|
ORDER BY overall_avg_score DESC;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 窗口函数与 HAVING 结合
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 查找在部门内排名前50%的员工组成的部门(平均绩效高的部门)
|
||||||
|
WITH ranked_employees AS (
|
||||||
|
SELECT *,
|
||||||
|
ROW_NUMBER() OVER (PARTITION BY department ORDER BY kpi_score DESC) as dept_rank,
|
||||||
|
COUNT(*) OVER (PARTITION BY department) as dept_size
|
||||||
|
FROM employee_performance
|
||||||
|
WHERE quarter = '2024Q1'
|
||||||
|
),
|
||||||
|
top_performers AS (
|
||||||
|
SELECT *
|
||||||
|
FROM ranked_employees
|
||||||
|
WHERE dept_rank <= dept_size / 2
|
||||||
|
)
|
||||||
|
SELECT department,
|
||||||
|
COUNT(*) as top_performer_count,
|
||||||
|
ROUND(AVG(kpi_score), 2) as avg_top_kpi,
|
||||||
|
ROUND(AVG(bonus), 2) as avg_top_bonus
|
||||||
|
FROM top_performers
|
||||||
|
GROUP BY department
|
||||||
|
HAVING AVG(kpi_score) > 8.5
|
||||||
|
ORDER BY avg_top_kpi DESC;
|
||||||
|
```
|
||||||
|
|
||||||
|
## 面试题和实际案例
|
||||||
|
|
||||||
|
### 面试题1:连续增长分析
|
||||||
|
|
||||||
|
**题目**:找出连续两个季度绩效都在提升的员工。
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 解答:使用自连接和HAVING
|
||||||
|
SELECT e1.name,
|
||||||
|
e1.department,
|
||||||
|
e1.quarter as current_quarter,
|
||||||
|
e1.kpi_score as current_kpi,
|
||||||
|
e2.quarter as prev_quarter,
|
||||||
|
e2.kpi_score as prev_kpi,
|
||||||
|
ROUND(e1.kpi_score - e2.kpi_score, 2) as improvement
|
||||||
|
FROM employee_performance e1
|
||||||
|
JOIN employee_performance e2 ON e1.emp_id = e2.emp_id
|
||||||
|
WHERE e1.quarter = '2024Q1'
|
||||||
|
AND e2.quarter = '2023Q4'
|
||||||
|
AND e1.kpi_score > e2.kpi_score
|
||||||
|
GROUP BY e1.emp_id, e1.name, e1.department, e1.quarter, e1.kpi_score, e2.quarter, e2.kpi_score
|
||||||
|
HAVING e1.kpi_score > e2.kpi_score
|
||||||
|
ORDER BY improvement DESC;
|
||||||
|
```
|
||||||
|
|
||||||
|
**结果:**
|
||||||
|
```
|
||||||
|
+----------+----------+-----------------+-------------+--------------+----------+-------------+
|
||||||
|
| name | department | current_quarter | current_kpi | prev_quarter | prev_kpi | improvement |
|
||||||
|
+----------+----------+-----------------+-------------+--------------+----------+-------------+
|
||||||
|
| 钱测试 | 技术部 | 2024Q1 | 8.0 | 2023Q4 | 7.5 | 0.50 |
|
||||||
|
| 赵销售 | 销售部 | 2024Q1 | 9.5 | 2023Q4 | 9.1 | 0.40 |
|
||||||
|
+----------+----------+-----------------+-------------+--------------+----------+-------------+
|
||||||
|
```
|
||||||
|
|
||||||
|
### 面试题2:帕累托分析(80/20原则)
|
||||||
|
|
||||||
|
**题目**:找出贡献了80%收入的前20%客户群体。
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 解答:使用累计计算和HAVING
|
||||||
|
WITH customer_revenue AS (
|
||||||
|
SELECT customer_segment,
|
||||||
|
SUM(unit_price * quantity * (1 - discount)) as total_revenue
|
||||||
|
FROM ecommerce_orders
|
||||||
|
GROUP BY customer_segment
|
||||||
|
),
|
||||||
|
revenue_ranking AS (
|
||||||
|
SELECT customer_segment,
|
||||||
|
total_revenue,
|
||||||
|
SUM(total_revenue) OVER() as grand_total,
|
||||||
|
SUM(total_revenue) OVER(ORDER BY total_revenue DESC) as cumulative_revenue
|
||||||
|
FROM customer_revenue
|
||||||
|
),
|
||||||
|
pareto_analysis AS (
|
||||||
|
SELECT customer_segment,
|
||||||
|
total_revenue,
|
||||||
|
ROUND(total_revenue / grand_total * 100, 2) as revenue_percentage,
|
||||||
|
ROUND(cumulative_revenue / grand_total * 100, 2) as cumulative_percentage
|
||||||
|
FROM revenue_ranking
|
||||||
|
)
|
||||||
|
SELECT customer_segment,
|
||||||
|
total_revenue,
|
||||||
|
revenue_percentage,
|
||||||
|
cumulative_percentage
|
||||||
|
FROM pareto_analysis
|
||||||
|
GROUP BY customer_segment, total_revenue, revenue_percentage, cumulative_percentage
|
||||||
|
HAVING cumulative_percentage <= 80
|
||||||
|
ORDER BY total_revenue DESC;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 面试题3:同期对比分析
|
||||||
|
|
||||||
|
**题目**:比较每个产品类别在不同客户类型中的表现差异。
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 解答:使用条件聚合和HAVING
|
||||||
|
SELECT product_category,
|
||||||
|
COUNT(*) as total_sales,
|
||||||
|
SUM(CASE WHEN customer_type = 'VIP客户' THEN amount ELSE 0 END) as vip_sales,
|
||||||
|
SUM(CASE WHEN customer_type = '新客户' THEN amount ELSE 0 END) as new_customer_sales,
|
||||||
|
SUM(CASE WHEN customer_type = '老客户' THEN amount ELSE 0 END) as old_customer_sales,
|
||||||
|
ROUND(
|
||||||
|
SUM(CASE WHEN customer_type = 'VIP客户' THEN amount ELSE 0 END) /
|
||||||
|
SUM(amount) * 100, 2
|
||||||
|
) as vip_percentage
|
||||||
|
FROM sales_performance
|
||||||
|
GROUP BY product_category
|
||||||
|
HAVING COUNT(DISTINCT customer_type) >= 2 -- 至少有2种客户类型
|
||||||
|
AND SUM(CASE WHEN customer_type = 'VIP客户' THEN amount ELSE 0 END) >
|
||||||
|
SUM(CASE WHEN customer_type = '新客户' THEN amount ELSE 0 END) -- VIP销售额超过新客户
|
||||||
|
ORDER BY vip_percentage DESC;
|
||||||
|
```
|
||||||
|
|
||||||
|
**结果:**
|
||||||
|
```
|
||||||
|
+------------------+-------------+-----------+--------------------+--------------------+----------------+
|
||||||
|
| product_category | total_sales | vip_sales | new_customer_sales | old_customer_sales | vip_percentage |
|
||||||
|
+------------------+-------------+-----------+--------------------+--------------------+----------------+
|
||||||
|
| 电子产品 | 5 | 43000.00 | 35000.00 | 16000.00 | 45.74 |
|
||||||
|
| 家电 | 3 | 22000.00 | 0.00 | 25000.00 | 46.81 |
|
||||||
|
+------------------+-------------+-----------+--------------------+--------------------+----------------+
|
||||||
|
```
|
||||||
|
|
||||||
|
### 实际案例1:电商业务分析
|
||||||
|
|
||||||
|
**场景**:电商平台需要识别高价值客户群体和优质产品类别。
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 综合业务分析:识别高价值客户群体
|
||||||
|
SELECT customer_segment,
|
||||||
|
region,
|
||||||
|
COUNT(*) as order_count,
|
||||||
|
COUNT(DISTINCT customer_id) as unique_customers,
|
||||||
|
ROUND(AVG(unit_price * quantity * (1 - discount)), 2) as avg_order_value,
|
||||||
|
SUM(profit) as total_profit,
|
||||||
|
ROUND(SUM(profit) / SUM(unit_price * quantity * (1 - discount)) * 100, 2) as profit_margin,
|
||||||
|
ROUND(COUNT(*) / COUNT(DISTINCT customer_id), 2) as orders_per_customer
|
||||||
|
FROM ecommerce_orders
|
||||||
|
GROUP BY customer_segment, region
|
||||||
|
HAVING COUNT(*) >= 2 -- 至少2个订单
|
||||||
|
AND COUNT(DISTINCT customer_id) >= 1 -- 至少1个客户
|
||||||
|
AND AVG(unit_price * quantity * (1 - discount)) > 200 -- 平均订单价值超过200
|
||||||
|
AND SUM(profit) / SUM(unit_price * quantity * (1 - discount)) > 0.20 -- 利润率超过20%
|
||||||
|
ORDER BY profit_margin DESC, avg_order_value DESC;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 实际案例2:教育数据分析
|
||||||
|
|
||||||
|
**场景**:教务处需要分析课程质量和学生表现。
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 课程质量分析:识别优质课程和问题课程
|
||||||
|
SELECT course_name,
|
||||||
|
teacher,
|
||||||
|
department,
|
||||||
|
COUNT(*) as student_count,
|
||||||
|
ROUND(AVG(score), 2) as avg_score,
|
||||||
|
ROUND(STD(score), 2) as score_std,
|
||||||
|
MIN(score) as min_score,
|
||||||
|
MAX(score) as max_score,
|
||||||
|
COUNT(CASE WHEN score >= 90 THEN 1 END) as excellent_count,
|
||||||
|
COUNT(CASE WHEN score < 70 THEN 1 END) as poor_count
|
||||||
|
FROM course_enrollments
|
||||||
|
GROUP BY course_id, course_name, teacher, department
|
||||||
|
HAVING COUNT(*) >= 3 -- 至少3名学生
|
||||||
|
AND AVG(score) >= 80 -- 平均分不低于80
|
||||||
|
AND STD(score) <= 10 -- 分数差异不过大
|
||||||
|
AND COUNT(CASE WHEN score < 70 THEN 1 END) = 0 -- 没有不及格学生
|
||||||
|
ORDER BY avg_score DESC, score_std ASC;
|
||||||
|
```
|
||||||
|
|
||||||
|
**结果:**
|
||||||
|
```
|
||||||
|
+----------------+----------+----------+---------------+-----------+-----------+-----------+-----------+-----------------+------------+
|
||||||
|
| course_name | teacher | department | student_count | avg_score | score_std | min_score | max_score | excellent_count | poor_count |
|
||||||
|
+----------------+----------+----------+---------------+-----------+-----------+-----------+-----------+-----------------+------------+
|
||||||
|
| 计算机科学导论 | 王教授 | 计算机系 | 3 | 88.83 | 3.64 | 85.5 | 92.0 | 1 | 0 |
|
||||||
|
| 线性代数 | 孙教授 | 数学系 | 2 | 89.75 | 2.47 | 88.0 | 91.5 | 1 | 0 |
|
||||||
|
+----------------+----------+----------+---------------+-----------+-----------+-----------+-----------+-----------------+------------+
|
||||||
|
```
|
||||||
|
|
||||||
|
### 实际案例3:网站运营优化
|
||||||
|
|
||||||
|
**场景**:网站运营团队需要优化流量渠道和用户体验。
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 流量渠道效果分析
|
||||||
|
SELECT traffic_source,
|
||||||
|
device_category,
|
||||||
|
COUNT(*) as session_count,
|
||||||
|
ROUND(AVG(page_views), 2) as avg_page_views,
|
||||||
|
ROUND(AVG(session_duration/60), 2) as avg_minutes,
|
||||||
|
SUM(conversion_flag) as conversions,
|
||||||
|
ROUND(SUM(conversion_flag)/COUNT(*)*100, 2) as conversion_rate,
|
||||||
|
ROUND(SUM(revenue), 2) as total_revenue,
|
||||||
|
ROUND(SUM(revenue)/COUNT(*), 2) as revenue_per_session
|
||||||
|
FROM website_analytics
|
||||||
|
GROUP BY traffic_source, device_category
|
||||||
|
HAVING COUNT(*) >= 2 -- 足够的样本量
|
||||||
|
AND AVG(session_duration) >= 180 -- 平均会话时长超过3分钟
|
||||||
|
AND SUM(conversion_flag)/COUNT(*) >= 0.3 -- 转化率至少30%
|
||||||
|
ORDER BY conversion_rate DESC, revenue_per_session DESC;
|
||||||
|
```
|
||||||
|
|
||||||
|
**结果:**
|
||||||
|
```
|
||||||
|
+----------------+-----------------+---------------+-----------------+-------------+-------------+-----------------+---------------+--------------------+
|
||||||
|
| traffic_source | device_category | session_count | avg_page_views | avg_minutes | conversions | conversion_rate | total_revenue | revenue_per_session |
|
||||||
|
+----------------+-----------------+---------------+-----------------+-------------+-------------+-----------------+---------------+--------------------+
|
||||||
|
| Google | Desktop | 2 | 11.50 | 7.58 | 2 | 100.00 | 1599.98 | 799.99 |
|
||||||
|
| Direct | Desktop | 3 | 7.33 | 7.14 | 2 | 66.67 | 1599.98 | 533.33 |
|
||||||
|
+----------------+-----------------+---------------+-----------------+-------------+-------------+-----------------+---------------+--------------------+
|
||||||
|
```
|
||||||
|
|
||||||
|
## 性能优化和最佳实践
|
||||||
|
|
||||||
|
### 1. 索引优化
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 为GROUP BY和HAVING中使用的列创建索引
|
||||||
|
CREATE INDEX idx_sales_performance_rep_region ON sales_performance(sales_rep, region);
|
||||||
|
CREATE INDEX idx_course_enrollments_student ON course_enrollments(student_id, student_name);
|
||||||
|
CREATE INDEX idx_ecommerce_orders_segment_state ON ecommerce_orders(customer_segment, state);
|
||||||
|
|
||||||
|
-- 为聚合计算创建覆盖索引
|
||||||
|
CREATE INDEX idx_sales_performance_covering ON sales_performance(sales_rep, amount, quantity, customer_type);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 查询优化策略
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- ✅ 推荐:先用WHERE过滤,减少分组数据量
|
||||||
|
SELECT department, AVG(salary)
|
||||||
|
FROM employees
|
||||||
|
WHERE hire_date >= '2020-01-01' -- 先过滤
|
||||||
|
GROUP BY department
|
||||||
|
HAVING AVG(salary) > 15000;
|
||||||
|
|
||||||
|
-- ❌ 避免:直接分组后过滤
|
||||||
|
SELECT department, AVG(salary)
|
||||||
|
FROM employees
|
||||||
|
GROUP BY department
|
||||||
|
HAVING AVG(salary) > 15000 AND MIN(hire_date) >= '2020-01-01';
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 避免复杂HAVING条件
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- ❌ 复杂的HAVING条件
|
||||||
|
SELECT sales_rep,
|
||||||
|
COUNT(*) as order_count,
|
||||||
|
SUM(amount) as total_sales
|
||||||
|
FROM sales_performance
|
||||||
|
GROUP BY sales_rep
|
||||||
|
HAVING SUM(amount) > (SELECT AVG(total) FROM (SELECT SUM(amount) as total FROM sales_performance GROUP BY sales_rep) t)
|
||||||
|
AND COUNT(*) > (SELECT AVG(cnt) FROM (SELECT COUNT(*) as cnt FROM sales_performance GROUP BY sales_rep) t);
|
||||||
|
|
||||||
|
-- ✅ 优化:分步处理
|
||||||
|
WITH rep_stats AS (
|
||||||
|
SELECT sales_rep,
|
||||||
|
COUNT(*) as order_count,
|
||||||
|
SUM(amount) as total_sales
|
||||||
|
FROM sales_performance
|
||||||
|
GROUP BY sales_rep
|
||||||
|
),
|
||||||
|
benchmarks AS (
|
||||||
|
SELECT AVG(total_sales) as avg_sales,
|
||||||
|
AVG(order_count) as avg_orders
|
||||||
|
FROM rep_stats
|
||||||
|
)
|
||||||
|
SELECT rs.sales_rep, rs.order_count, rs.total_sales
|
||||||
|
FROM rep_stats rs, benchmarks b
|
||||||
|
WHERE rs.total_sales > b.avg_sales
|
||||||
|
AND rs.order_count > b.avg_orders;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 内存优化
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 优化GROUP BY相关的内存设置
|
||||||
|
SET SESSION group_concat_max_len = 10240;
|
||||||
|
SET SESSION tmp_table_size = 134217728; -- 128MB
|
||||||
|
SET SESSION max_heap_table_size = 134217728; -- 128MB
|
||||||
|
|
||||||
|
-- 对于大数据量,考虑分页处理
|
||||||
|
SELECT sales_rep, COUNT(*), SUM(amount)
|
||||||
|
FROM sales_performance
|
||||||
|
WHERE sale_date BETWEEN '2024-01-01' AND '2024-01-31' -- 限制时间范围
|
||||||
|
GROUP BY sales_rep
|
||||||
|
HAVING COUNT(*) > 5
|
||||||
|
ORDER BY SUM(amount) DESC
|
||||||
|
LIMIT 20; -- 限制结果数量
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. 监控和调优
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 查看HAVING相关查询的执行计划
|
||||||
|
EXPLAIN FORMAT=JSON
|
||||||
|
SELECT department, COUNT(*), AVG(salary)
|
||||||
|
FROM employees
|
||||||
|
GROUP BY department
|
||||||
|
HAVING COUNT(*) > 3;
|
||||||
|
|
||||||
|
-- 监控分组查询性能
|
||||||
|
SELECT
|
||||||
|
SUBSTRING(sql_text, 1, 100) as query_start,
|
||||||
|
exec_count,
|
||||||
|
avg_timer_wait/1000000000000 as avg_time_sec,
|
||||||
|
sum_rows_examined/exec_count as avg_rows_examined
|
||||||
|
FROM performance_schema.events_statements_summary_by_digest
|
||||||
|
WHERE sql_text LIKE '%HAVING%'
|
||||||
|
ORDER BY avg_timer_wait DESC
|
||||||
|
LIMIT 10;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6. 最佳实践总结
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- ✅ HAVING 最佳实践
|
||||||
|
SELECT
|
||||||
|
category, -- 明确的分组字段
|
||||||
|
COUNT(*) as product_count, -- 清晰的聚合函数
|
||||||
|
ROUND(AVG(price), 2) as avg_price -- 合理的精度
|
||||||
|
FROM products
|
||||||
|
WHERE status = 'active' -- 先过滤原始数据
|
||||||
|
GROUP BY category -- 简单的分组
|
||||||
|
HAVING COUNT(*) >= 5 -- 简单明确的HAVING条件
|
||||||
|
AND AVG(price) > 100 -- 避免过复杂的条件
|
||||||
|
ORDER BY avg_price DESC -- 合理排序
|
||||||
|
LIMIT 20; -- 限制结果数量
|
||||||
|
|
||||||
|
-- ❌ 避免的问题
|
||||||
|
-- 1. HAVING中使用非聚合函数(应该用WHERE)
|
||||||
|
-- 2. 过于复杂的HAVING条件
|
||||||
|
-- 3. 在HAVING中进行大量计算
|
||||||
|
-- 4. 不必要的子查询在HAVING中
|
||||||
|
-- 5. 缺少适当的索引支持
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**总结:**
|
||||||
|
- HAVING 专门用于过滤 GROUP BY 分组后的结果
|
||||||
|
- 理解与 WHERE 的区别:时机、功能、性能
|
||||||
|
- 可以使用聚合函数进行复杂的条件判断
|
||||||
|
- 合理使用索引和分步查询优化性能
|
||||||
|
- 避免过于复杂的 HAVING 条件
|
||||||
|
- 结合实际业务场景灵活运用
|
||||||
769
MySQL_ORDER_BY语法使用文档.md
Normal file
769
MySQL_ORDER_BY语法使用文档.md
Normal file
@ -0,0 +1,769 @@
|
|||||||
|
# MySQL ORDER BY 语法使用文档
|
||||||
|
|
||||||
|
## 目录
|
||||||
|
1. [ORDER BY 基础语法](#order-by-基础语法)
|
||||||
|
2. [ORDER BY 规则和特性](#order-by-规则和特性)
|
||||||
|
3. [示例数据准备](#示例数据准备)
|
||||||
|
4. [基础排序示例](#基础排序示例)
|
||||||
|
5. [多列排序](#多列排序)
|
||||||
|
6. [特殊排序场景](#特殊排序场景)
|
||||||
|
7. [复杂排序查询](#复杂排序查询)
|
||||||
|
8. [面试题和实际案例](#面试题和实际案例)
|
||||||
|
9. [性能优化和最佳实践](#性能优化和最佳实践)
|
||||||
|
|
||||||
|
## ORDER BY 基础语法
|
||||||
|
|
||||||
|
ORDER BY 用于对查询结果进行排序。
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SELECT column1, column2, ...
|
||||||
|
FROM table_name
|
||||||
|
[WHERE condition]
|
||||||
|
ORDER BY column1 [ASC|DESC], column2 [ASC|DESC], ...
|
||||||
|
[LIMIT number];
|
||||||
|
```
|
||||||
|
|
||||||
|
**关键字说明:**
|
||||||
|
- `ASC`:升序排列(默认)
|
||||||
|
- `DESC`:降序排列
|
||||||
|
- 可以指定多个排序列
|
||||||
|
- NULL 值默认排在最前面(ASC)或最后面(DESC)
|
||||||
|
|
||||||
|
## ORDER BY 规则和特性
|
||||||
|
|
||||||
|
1. **排序优先级**:从左到右依次排序
|
||||||
|
2. **数据类型排序**:
|
||||||
|
- 数字:按数值大小
|
||||||
|
- 字符串:按字典序(字母顺序)
|
||||||
|
- 日期:按时间先后
|
||||||
|
- NULL:默认最小值处理
|
||||||
|
3. **性能影响**:ORDER BY 可能触发文件排序,影响查询性能
|
||||||
|
4. **与 LIMIT 结合**:获取排序后的前 N 条记录
|
||||||
|
|
||||||
|
## 示例数据准备
|
||||||
|
|
||||||
|
### 创建示例表
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 学生表
|
||||||
|
CREATE TABLE students (
|
||||||
|
id INT PRIMARY KEY AUTO_INCREMENT,
|
||||||
|
name VARCHAR(50) NOT NULL,
|
||||||
|
age INT,
|
||||||
|
gender ENUM('男', '女'),
|
||||||
|
class_id INT,
|
||||||
|
score DECIMAL(5,2),
|
||||||
|
enrollment_date DATE,
|
||||||
|
city VARCHAR(30)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 订单表
|
||||||
|
CREATE TABLE orders (
|
||||||
|
order_id INT PRIMARY KEY AUTO_INCREMENT,
|
||||||
|
customer_name VARCHAR(50),
|
||||||
|
product_name VARCHAR(100),
|
||||||
|
order_date DATETIME,
|
||||||
|
amount DECIMAL(10,2),
|
||||||
|
status ENUM('pending', 'processing', 'completed', 'cancelled'),
|
||||||
|
priority INT,
|
||||||
|
region VARCHAR(30)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 员工表
|
||||||
|
CREATE TABLE staff (
|
||||||
|
emp_id INT PRIMARY KEY,
|
||||||
|
name VARCHAR(50),
|
||||||
|
department VARCHAR(30),
|
||||||
|
salary DECIMAL(10,2),
|
||||||
|
hire_date DATE,
|
||||||
|
manager_id INT,
|
||||||
|
performance_score DECIMAL(3,1),
|
||||||
|
bonus DECIMAL(8,2)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 产品销售表
|
||||||
|
CREATE TABLE product_sales (
|
||||||
|
id INT PRIMARY KEY AUTO_INCREMENT,
|
||||||
|
product_id INT,
|
||||||
|
product_name VARCHAR(100),
|
||||||
|
category VARCHAR(50),
|
||||||
|
sale_date DATE,
|
||||||
|
quantity INT,
|
||||||
|
unit_price DECIMAL(8,2),
|
||||||
|
discount_rate DECIMAL(3,2),
|
||||||
|
sales_rep VARCHAR(50)
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 插入示例数据
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 插入学生数据
|
||||||
|
INSERT INTO students (name, age, gender, class_id, score, enrollment_date, city) VALUES
|
||||||
|
('张三', 20, '男', 1, 85.5, '2023-09-01', '北京'),
|
||||||
|
('李四', 19, '女', 1, 92.0, '2023-09-01', '上海'),
|
||||||
|
('王五', 21, '男', 2, 78.5, '2023-09-01', '深圳'),
|
||||||
|
('赵六', 20, '女', 2, 88.0, '2023-09-01', '广州'),
|
||||||
|
('钱七', 22, '男', 1, 76.0, '2022-09-01', '北京'),
|
||||||
|
('孙八', 19, '女', 3, 95.5, '2023-09-01', '杭州'),
|
||||||
|
('周九', 20, '男', 3, 82.0, '2023-09-01', '成都'),
|
||||||
|
('吴十', 21, '女', 2, 89.5, '2022-09-01', '南京'),
|
||||||
|
('郑一', 19, '男', 1, 91.0, '2023-09-01', '西安'),
|
||||||
|
('王二', NULL, '女', 3, 87.0, '2023-09-01', '重庆');
|
||||||
|
|
||||||
|
-- 插入订单数据
|
||||||
|
INSERT INTO orders (customer_name, product_name, order_date, amount, status, priority, region) VALUES
|
||||||
|
('客户A', '笔记本电脑', '2024-01-15 10:30:00', 5999.00, 'completed', 1, '华北'),
|
||||||
|
('客户B', '手机', '2024-01-16 14:20:00', 3999.00, 'processing', 2, '华东'),
|
||||||
|
('客户C', '平板电脑', '2024-01-16 16:45:00', 2999.00, 'pending', 3, '华南'),
|
||||||
|
('客户A', '耳机', '2024-01-17 09:15:00', 299.00, 'completed', 2, '华北'),
|
||||||
|
('客户D', '键盘', '2024-01-17 11:30:00', 199.00, 'cancelled', 3, '华西'),
|
||||||
|
('客户E', '鼠标', '2024-01-18 13:40:00', 99.00, 'completed', 1, '华东'),
|
||||||
|
('客户B', '显示器', '2024-01-18 15:20:00', 1299.00, 'processing', 1, '华东'),
|
||||||
|
('客户F', '音响', '2024-01-19 08:50:00', 899.00, 'pending', 2, '华南'),
|
||||||
|
('客户C', '摄像头', '2024-01-19 12:10:00', 399.00, 'completed', 3, '华南'),
|
||||||
|
('客户G', '打印机', '2024-01-20 16:30:00', 1599.00, 'processing', 1, '华北');
|
||||||
|
|
||||||
|
-- 插入员工数据
|
||||||
|
INSERT INTO staff (emp_id, name, department, salary, hire_date, manager_id, performance_score, bonus) VALUES
|
||||||
|
(1001, '张经理', '技术部', 15000.00, '2020-03-15', NULL, 9.2, 5000.00),
|
||||||
|
(1002, '李工程师', '技术部', 12000.00, '2021-05-20', 1001, 8.8, 3000.00),
|
||||||
|
(1003, '王设计师', '设计部', 10000.00, '2021-08-10', NULL, 8.5, 2500.00),
|
||||||
|
(1004, '赵分析师', '数据部', 11000.00, '2022-01-15', NULL, 9.0, 3500.00),
|
||||||
|
(1005, '钱开发', '技术部', 9000.00, '2022-04-20', 1001, 7.8, 2000.00),
|
||||||
|
(1006, '孙测试', '技术部', 8500.00, '2022-07-01', 1001, 8.2, 1500.00),
|
||||||
|
(1007, '周产品', '产品部', 13000.00, '2021-12-05', NULL, 8.9, 4000.00),
|
||||||
|
(1008, '吴运营', '运营部', 8000.00, '2023-02-10', NULL, 7.5, 1000.00),
|
||||||
|
(1009, '郑销售', '销售部', 7500.00, '2023-03-20', NULL, 8.1, 1800.00),
|
||||||
|
(1010, '刘助理', '技术部', 6000.00, '2023-09-01', 1002, NULL, 500.00);
|
||||||
|
|
||||||
|
-- 插入产品销售数据
|
||||||
|
INSERT INTO product_sales (product_id, product_name, category, sale_date, quantity, unit_price, discount_rate, sales_rep) VALUES
|
||||||
|
(101, 'iPhone 15', '手机', '2024-01-10', 50, 5999.00, 0.05, '张销售'),
|
||||||
|
(102, '华为Mate60', '手机', '2024-01-10', 30, 4999.00, 0.10, '李销售'),
|
||||||
|
(103, 'MacBook Pro', '笔记本', '2024-01-11', 20, 12999.00, 0.03, '张销售'),
|
||||||
|
(104, '联想ThinkPad', '笔记本', '2024-01-11', 40, 6999.00, 0.08, '王销售'),
|
||||||
|
(105, 'iPad Air', '平板', '2024-01-12', 25, 3999.00, 0.06, '李销售'),
|
||||||
|
(106, '小米平板', '平板', '2024-01-12', 35, 1999.00, 0.12, '赵销售'),
|
||||||
|
(107, 'AirPods Pro', '耳机', '2024-01-13', 100, 1999.00, 0.08, '张销售'),
|
||||||
|
(108, '索尼耳机', '耳机', '2024-01-13', 60, 2999.00, 0.15, '王销售'),
|
||||||
|
(109, 'Dell显示器', '显示器', '2024-01-14', 80, 1599.00, 0.10, '李销售'),
|
||||||
|
(110, '三星显示器', '显示器', '2024-01-14', 45, 2299.00, 0.07, '赵销售');
|
||||||
|
```
|
||||||
|
|
||||||
|
## 基础排序示例
|
||||||
|
|
||||||
|
### 1. 单列升序排序(默认)
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 按年龄升序排列学生
|
||||||
|
SELECT name, age FROM students ORDER BY age;
|
||||||
|
```
|
||||||
|
|
||||||
|
**结果:**
|
||||||
|
```
|
||||||
|
+------+------+
|
||||||
|
| name | age |
|
||||||
|
+------+------+
|
||||||
|
| 王二 | NULL |
|
||||||
|
| 李四 | 19 |
|
||||||
|
| 孙八 | 19 |
|
||||||
|
| 郑一 | 19 |
|
||||||
|
| 张三 | 20 |
|
||||||
|
| 赵六 | 20 |
|
||||||
|
| 周九 | 20 |
|
||||||
|
| 王五 | 21 |
|
||||||
|
| 吴十 | 21 |
|
||||||
|
| 钱七 | 22 |
|
||||||
|
+------+------+
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 单列降序排序
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 按分数降序排列学生
|
||||||
|
SELECT name, score FROM students ORDER BY score DESC;
|
||||||
|
```
|
||||||
|
|
||||||
|
**结果:**
|
||||||
|
```
|
||||||
|
+------+-------+
|
||||||
|
| name | score |
|
||||||
|
+------+-------+
|
||||||
|
| 孙八 | 95.50 |
|
||||||
|
| 李四 | 92.00 |
|
||||||
|
| 郑一 | 91.00 |
|
||||||
|
| 吴十 | 89.50 |
|
||||||
|
| 赵六 | 88.00 |
|
||||||
|
| 王二 | 87.00 |
|
||||||
|
| 张三 | 85.50 |
|
||||||
|
| 周九 | 82.00 |
|
||||||
|
| 王五 | 78.50 |
|
||||||
|
| 钱七 | 76.00 |
|
||||||
|
+------+-------+
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 字符串排序
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 按姓名字母顺序排列
|
||||||
|
SELECT name, age FROM students ORDER BY name;
|
||||||
|
```
|
||||||
|
|
||||||
|
**结果:**
|
||||||
|
```
|
||||||
|
+------+------+
|
||||||
|
| name | age |
|
||||||
|
+------+------+
|
||||||
|
| 钱七 | 22 |
|
||||||
|
| 孙八 | 19 |
|
||||||
|
| 王二 | NULL |
|
||||||
|
| 王五 | 21 |
|
||||||
|
| 吴十 | 21 |
|
||||||
|
| 张三 | 20 |
|
||||||
|
| 赵六 | 20 |
|
||||||
|
| 郑一 | 19 |
|
||||||
|
| 周九 | 20 |
|
||||||
|
| 李四 | 19 |
|
||||||
|
+------+-------+
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 日期排序
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 按入学日期排序
|
||||||
|
SELECT name, enrollment_date FROM students ORDER BY enrollment_date;
|
||||||
|
```
|
||||||
|
|
||||||
|
**结果:**
|
||||||
|
```
|
||||||
|
+------+-----------------+
|
||||||
|
| name | enrollment_date |
|
||||||
|
+------+-----------------+
|
||||||
|
| 钱七 | 2022-09-01 |
|
||||||
|
| 吴十 | 2022-09-01 |
|
||||||
|
| 张三 | 2023-09-01 |
|
||||||
|
| 李四 | 2023-09-01 |
|
||||||
|
| 王五 | 2023-09-01 |
|
||||||
|
| 赵六 | 2023-09-01 |
|
||||||
|
| 孙八 | 2023-09-01 |
|
||||||
|
| 周九 | 2023-09-01 |
|
||||||
|
| 郑一 | 2023-09-01 |
|
||||||
|
| 王二 | 2023-09-01 |
|
||||||
|
+------+-----------------+
|
||||||
|
```
|
||||||
|
|
||||||
|
## 多列排序
|
||||||
|
|
||||||
|
### 1. 多列升序排序
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 先按班级排序,再按分数排序
|
||||||
|
SELECT name, class_id, score
|
||||||
|
FROM students
|
||||||
|
ORDER BY class_id, score;
|
||||||
|
```
|
||||||
|
|
||||||
|
**结果:**
|
||||||
|
```
|
||||||
|
+------+----------+-------+
|
||||||
|
| name | class_id | score |
|
||||||
|
+------+----------+-------+
|
||||||
|
| 钱七 | 1 | 76.00 |
|
||||||
|
| 张三 | 1 | 85.50 |
|
||||||
|
| 郑一 | 1 | 91.00 |
|
||||||
|
| 李四 | 1 | 92.00 |
|
||||||
|
| 王五 | 2 | 78.50 |
|
||||||
|
| 赵六 | 2 | 88.00 |
|
||||||
|
| 吴十 | 2 | 89.50 |
|
||||||
|
| 周九 | 3 | 82.00 |
|
||||||
|
| 王二 | 3 | 87.00 |
|
||||||
|
| 孙八 | 3 | 95.50 |
|
||||||
|
+------+----------+-------+
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 混合排序(升序+降序)
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 按班级升序,分数降序
|
||||||
|
SELECT name, class_id, score
|
||||||
|
FROM students
|
||||||
|
ORDER BY class_id ASC, score DESC;
|
||||||
|
```
|
||||||
|
|
||||||
|
**结果:**
|
||||||
|
```
|
||||||
|
+------+----------+-------+
|
||||||
|
| name | class_id | score |
|
||||||
|
+------+----------+-------+
|
||||||
|
| 李四 | 1 | 92.00 |
|
||||||
|
| 郑一 | 1 | 91.00 |
|
||||||
|
| 张三 | 1 | 85.50 |
|
||||||
|
| 钱七 | 1 | 76.00 |
|
||||||
|
| 吴十 | 2 | 89.50 |
|
||||||
|
| 赵六 | 2 | 88.00 |
|
||||||
|
| 王五 | 2 | 78.50 |
|
||||||
|
| 孙八 | 3 | 95.50 |
|
||||||
|
| 王二 | 3 | 87.00 |
|
||||||
|
| 周九 | 3 | 82.00 |
|
||||||
|
+------+----------+-------+
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 三列排序
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 按部门、薪资、绩效排序
|
||||||
|
SELECT name, department, salary, performance_score
|
||||||
|
FROM staff
|
||||||
|
ORDER BY department, salary DESC, performance_score DESC;
|
||||||
|
```
|
||||||
|
|
||||||
|
**结果:**
|
||||||
|
```
|
||||||
|
+----------+----------+----------+------------------+
|
||||||
|
| name | department | salary | performance_score |
|
||||||
|
+----------+----------+----------+------------------+
|
||||||
|
| 王设计师 | 设计部 | 10000.00 | 8.50 |
|
||||||
|
| 赵分析师 | 数据部 | 11000.00 | 9.00 |
|
||||||
|
| 周产品 | 产品部 | 13000.00 | 8.90 |
|
||||||
|
| 张经理 | 技术部 | 15000.00 | 9.20 |
|
||||||
|
| 李工程师 | 技术部 | 12000.00 | 8.80 |
|
||||||
|
| 钱开发 | 技术部 | 9000.00 | 7.80 |
|
||||||
|
| 孙测试 | 技术部 | 8500.00 | 8.20 |
|
||||||
|
| 刘助理 | 技术部 | 6000.00 | NULL |
|
||||||
|
| 吴运营 | 运营部 | 8000.00 | 7.50 |
|
||||||
|
| 郑销售 | 销售部 | 7500.00 | 8.10 |
|
||||||
|
+----------+----------+----------+------------------+
|
||||||
|
```
|
||||||
|
|
||||||
|
## 特殊排序场景
|
||||||
|
|
||||||
|
### 1. NULL 值处理
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 查看NULL值的排序位置
|
||||||
|
SELECT name, age FROM students ORDER BY age;
|
||||||
|
|
||||||
|
-- 使用 ISNULL() 函数控制NULL值排序
|
||||||
|
SELECT name, age
|
||||||
|
FROM students
|
||||||
|
ORDER BY ISNULL(age), age; -- NULL值排在最后
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 自定义排序(FIELD函数)
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 按自定义状态优先级排序
|
||||||
|
SELECT customer_name, status, amount
|
||||||
|
FROM orders
|
||||||
|
ORDER BY FIELD(status, 'pending', 'processing', 'completed', 'cancelled'), amount DESC;
|
||||||
|
```
|
||||||
|
|
||||||
|
**结果:**
|
||||||
|
```
|
||||||
|
+------------+------------+---------+
|
||||||
|
| customer_name | status | amount |
|
||||||
|
+------------+------------+---------+
|
||||||
|
| 客户C | pending | 2999.00 |
|
||||||
|
| 客户F | pending | 899.00 |
|
||||||
|
| 客户B | processing | 3999.00 |
|
||||||
|
| 客户G | processing | 1599.00 |
|
||||||
|
| 客户B | processing | 1299.00 |
|
||||||
|
| 客户A | completed | 5999.00 |
|
||||||
|
| 客户F | completed | 899.00 |
|
||||||
|
| 客户C | completed | 399.00 |
|
||||||
|
| 客户A | completed | 299.00 |
|
||||||
|
| 客户E | completed | 99.00 |
|
||||||
|
| 客户D | cancelled | 199.00 |
|
||||||
|
+------------+------------+---------+
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 条件排序(CASE WHEN)
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 根据不同条件进行排序
|
||||||
|
SELECT name, department, salary,
|
||||||
|
CASE
|
||||||
|
WHEN department = '技术部' THEN 1
|
||||||
|
WHEN department = '产品部' THEN 2
|
||||||
|
WHEN department = '设计部' THEN 3
|
||||||
|
ELSE 4
|
||||||
|
END AS dept_priority
|
||||||
|
FROM staff
|
||||||
|
ORDER BY dept_priority, salary DESC;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 计算字段排序
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 按销售额排序(数量×单价×(1-折扣率))
|
||||||
|
SELECT product_name, quantity, unit_price, discount_rate,
|
||||||
|
ROUND(quantity * unit_price * (1 - discount_rate), 2) AS total_sales
|
||||||
|
FROM product_sales
|
||||||
|
ORDER BY total_sales DESC;
|
||||||
|
```
|
||||||
|
|
||||||
|
**结果:**
|
||||||
|
```
|
||||||
|
+-------------+----------+------------+---------------+-------------+
|
||||||
|
| product_name | quantity | unit_price | discount_rate | total_sales |
|
||||||
|
+-------------+----------+------------+---------------+-------------+
|
||||||
|
| iPhone 15 | 50 | 5999.00 | 0.05 | 284952.50 |
|
||||||
|
| MacBook Pro | 20 | 12999.00 | 0.03 | 251980.60 |
|
||||||
|
| 联想ThinkPad | 40 | 6999.00 | 0.08 | 257569.60 |
|
||||||
|
| AirPods Pro | 100 | 1999.00 | 0.08 | 183908.00 |
|
||||||
|
| 索尼耳机 | 60 | 2999.00 | 0.15 | 152949.00 |
|
||||||
|
| 华为Mate60 | 30 | 4999.00 | 0.10 | 134973.00 |
|
||||||
|
| Dell显示器 | 80 | 1599.00 | 0.10 | 115128.00 |
|
||||||
|
| iPad Air | 25 | 3999.00 | 0.06 | 93975.00 |
|
||||||
|
| 三星显示器 | 45 | 2299.00 | 0.07 | 96218.55 |
|
||||||
|
| 小米平板 | 35 | 1999.00 | 0.12 | 61652.40 |
|
||||||
|
+-------------+----------+------------+---------------+-------------+
|
||||||
|
```
|
||||||
|
|
||||||
|
## 复杂排序查询
|
||||||
|
|
||||||
|
### 1. 分组后排序
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 各部门平均薪资,按平均薪资降序
|
||||||
|
SELECT department,
|
||||||
|
COUNT(*) AS emp_count,
|
||||||
|
ROUND(AVG(salary), 2) AS avg_salary
|
||||||
|
FROM staff
|
||||||
|
GROUP BY department
|
||||||
|
ORDER BY avg_salary DESC;
|
||||||
|
```
|
||||||
|
|
||||||
|
**结果:**
|
||||||
|
```
|
||||||
|
+----------+-----------+------------+
|
||||||
|
| department | emp_count | avg_salary |
|
||||||
|
+----------+-----------+------------+
|
||||||
|
| 技术部 | 5 | 10100.00 |
|
||||||
|
| 产品部 | 1 | 13000.00 |
|
||||||
|
| 数据部 | 1 | 11000.00 |
|
||||||
|
| 设计部 | 1 | 10000.00 |
|
||||||
|
| 运营部 | 1 | 8000.00 |
|
||||||
|
| 销售部 | 1 | 7500.00 |
|
||||||
|
+----------+-----------+------------+
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 子查询结果排序
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 查找每个班级分数最高的学生
|
||||||
|
SELECT s1.name, s1.class_id, s1.score
|
||||||
|
FROM students s1
|
||||||
|
WHERE s1.score = (
|
||||||
|
SELECT MAX(s2.score)
|
||||||
|
FROM students s2
|
||||||
|
WHERE s2.class_id = s1.class_id
|
||||||
|
)
|
||||||
|
ORDER BY s1.class_id, s1.score DESC;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 窗口函数排序
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 使用ROW_NUMBER()进行排名
|
||||||
|
SELECT name, department, salary,
|
||||||
|
ROW_NUMBER() OVER (PARTITION BY department ORDER BY salary DESC) AS dept_rank,
|
||||||
|
ROW_NUMBER() OVER (ORDER BY salary DESC) AS overall_rank
|
||||||
|
FROM staff
|
||||||
|
ORDER BY department, dept_rank;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 复杂条件排序
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 优先显示高优先级且金额大的订单
|
||||||
|
SELECT customer_name, product_name, amount, priority, status,
|
||||||
|
CASE
|
||||||
|
WHEN priority = 1 AND amount > 1000 THEN 1
|
||||||
|
WHEN priority = 1 THEN 2
|
||||||
|
WHEN priority = 2 AND amount > 1000 THEN 3
|
||||||
|
WHEN priority = 2 THEN 4
|
||||||
|
ELSE 5
|
||||||
|
END AS sort_priority
|
||||||
|
FROM orders
|
||||||
|
ORDER BY sort_priority, amount DESC;
|
||||||
|
```
|
||||||
|
|
||||||
|
## 面试题和实际案例
|
||||||
|
|
||||||
|
### 面试题1:销售排名问题
|
||||||
|
|
||||||
|
**题目**:查询每个销售代表的销售总额,并按销售额降序排列,同时显示排名。
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 解答
|
||||||
|
SELECT sales_rep,
|
||||||
|
COUNT(*) AS product_count,
|
||||||
|
SUM(quantity * unit_price * (1 - discount_rate)) AS total_sales,
|
||||||
|
RANK() OVER (ORDER BY SUM(quantity * unit_price * (1 - discount_rate)) DESC) AS sales_rank
|
||||||
|
FROM product_sales
|
||||||
|
GROUP BY sales_rep
|
||||||
|
ORDER BY total_sales DESC;
|
||||||
|
```
|
||||||
|
|
||||||
|
**结果:**
|
||||||
|
```
|
||||||
|
+----------+---------------+-------------+------------+
|
||||||
|
| sales_rep | product_count | total_sales | sales_rank |
|
||||||
|
+----------+---------------+-------------+------------+
|
||||||
|
| 张销售 | 3 | 720841.10 | 1 |
|
||||||
|
| 李销售 | 3 | 344076.00 | 2 |
|
||||||
|
| 王销售 | 2 | 410518.20 | 3 |
|
||||||
|
| 赵销售 | 2 | 157870.95 | 4 |
|
||||||
|
+----------+---------------+-------------+------------+
|
||||||
|
```
|
||||||
|
|
||||||
|
### 面试题2:分页查询优化
|
||||||
|
|
||||||
|
**题目**:实现高效的分页查询,按ID排序。
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 传统分页(性能较差)
|
||||||
|
SELECT * FROM orders ORDER BY order_id LIMIT 1000000, 10;
|
||||||
|
|
||||||
|
-- 优化后的分页(使用索引)
|
||||||
|
SELECT o.* FROM orders o
|
||||||
|
WHERE o.order_id > (
|
||||||
|
SELECT order_id FROM orders ORDER BY order_id LIMIT 999999, 1
|
||||||
|
)
|
||||||
|
ORDER BY o.order_id LIMIT 10;
|
||||||
|
|
||||||
|
-- 更好的分页方案(记住上次的最后一条记录ID)
|
||||||
|
SELECT * FROM orders
|
||||||
|
WHERE order_id > 1000000 -- 上次查询的最后一个ID
|
||||||
|
ORDER BY order_id LIMIT 10;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 面试题3:Top-N问题
|
||||||
|
|
||||||
|
**题目**:查询每个类别销售额前2名的产品。
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 使用窗口函数解决
|
||||||
|
SELECT category, product_name, total_sales, category_rank
|
||||||
|
FROM (
|
||||||
|
SELECT category, product_name,
|
||||||
|
quantity * unit_price * (1 - discount_rate) AS total_sales,
|
||||||
|
ROW_NUMBER() OVER (PARTITION BY category ORDER BY quantity * unit_price * (1 - discount_rate) DESC) AS category_rank
|
||||||
|
FROM product_sales
|
||||||
|
) ranked_sales
|
||||||
|
WHERE category_rank <= 2
|
||||||
|
ORDER BY category, category_rank;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 实际案例1:电商订单分析
|
||||||
|
|
||||||
|
**场景**:电商平台需要分析订单数据,按多个维度排序。
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 复合排序:紧急订单优先,然后按金额降序,最后按时间升序
|
||||||
|
SELECT customer_name, product_name, amount, status, priority, order_date,
|
||||||
|
CASE
|
||||||
|
WHEN status = 'pending' AND priority = 1 THEN '紧急待处理'
|
||||||
|
WHEN status = 'processing' AND priority <= 2 THEN '优先处理中'
|
||||||
|
WHEN status = 'completed' THEN '已完成'
|
||||||
|
ELSE '普通订单'
|
||||||
|
END AS order_category
|
||||||
|
FROM orders
|
||||||
|
ORDER BY
|
||||||
|
CASE WHEN status = 'pending' AND priority = 1 THEN 1
|
||||||
|
WHEN status = 'processing' AND priority <= 2 THEN 2
|
||||||
|
WHEN status = 'pending' THEN 3
|
||||||
|
WHEN status = 'processing' THEN 4
|
||||||
|
WHEN status = 'completed' THEN 5
|
||||||
|
ELSE 6 END,
|
||||||
|
amount DESC,
|
||||||
|
order_date ASC;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 实际案例2:学生成绩分析
|
||||||
|
|
||||||
|
**场景**:学校需要按多个条件对学生排序。
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 综合排序:优秀学生优先(分数>90),然后按班级、分数排序
|
||||||
|
SELECT name, class_id, score, age,
|
||||||
|
CASE
|
||||||
|
WHEN score >= 90 THEN '优秀'
|
||||||
|
WHEN score >= 80 THEN '良好'
|
||||||
|
WHEN score >= 70 THEN '中等'
|
||||||
|
ELSE '待提高'
|
||||||
|
END AS grade_level
|
||||||
|
FROM students
|
||||||
|
ORDER BY
|
||||||
|
CASE WHEN score >= 90 THEN 1 ELSE 2 END, -- 优秀学生优先
|
||||||
|
class_id, -- 再按班级
|
||||||
|
score DESC, -- 最后按分数降序
|
||||||
|
age; -- 分数相同时按年龄升序
|
||||||
|
```
|
||||||
|
|
||||||
|
### 实际案例3:员工绩效排序
|
||||||
|
|
||||||
|
**场景**:HR部门需要按绩效和多个因素对员工排序。
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 复杂绩效排序
|
||||||
|
SELECT name, department, salary, performance_score, bonus, hire_date,
|
||||||
|
CASE
|
||||||
|
WHEN performance_score >= 9.0 THEN 'A'
|
||||||
|
WHEN performance_score >= 8.0 THEN 'B'
|
||||||
|
WHEN performance_score >= 7.0 THEN 'C'
|
||||||
|
ELSE 'D'
|
||||||
|
END AS performance_grade
|
||||||
|
FROM staff
|
||||||
|
WHERE performance_score IS NOT NULL
|
||||||
|
ORDER BY
|
||||||
|
performance_score DESC, -- 绩效评分降序
|
||||||
|
CASE WHEN department = '技术部' THEN 1 -- 技术部优先
|
||||||
|
WHEN department = '产品部' THEN 2
|
||||||
|
ELSE 3 END,
|
||||||
|
salary DESC, -- 薪资降序
|
||||||
|
hire_date; -- 入职时间升序(资历)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 面试题4:连续排名问题
|
||||||
|
|
||||||
|
**题目**:为员工按薪资排序,要求排名连续(即使薪资相同)。
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 使用不同的排名函数
|
||||||
|
SELECT name, salary,
|
||||||
|
ROW_NUMBER() OVER (ORDER BY salary DESC) AS row_num, -- 连续排名
|
||||||
|
RANK() OVER (ORDER BY salary DESC) AS rank_num, -- 跳跃排名
|
||||||
|
DENSE_RANK() OVER (ORDER BY salary DESC) AS dense_rank -- 密集排名
|
||||||
|
FROM staff
|
||||||
|
ORDER BY salary DESC, name;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 面试题5:移动平均排序
|
||||||
|
|
||||||
|
**题目**:计算每个学生的班级排名和移动平均分。
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 窗口函数实现移动平均
|
||||||
|
SELECT name, class_id, score,
|
||||||
|
ROW_NUMBER() OVER (PARTITION BY class_id ORDER BY score DESC) AS class_rank,
|
||||||
|
ROUND(AVG(score) OVER (
|
||||||
|
PARTITION BY class_id
|
||||||
|
ORDER BY score DESC
|
||||||
|
ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING
|
||||||
|
), 2) AS moving_avg
|
||||||
|
FROM students
|
||||||
|
WHERE score IS NOT NULL
|
||||||
|
ORDER BY class_id, class_rank;
|
||||||
|
```
|
||||||
|
|
||||||
|
## 性能优化和最佳实践
|
||||||
|
|
||||||
|
### 1. 索引优化
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 为经常排序的列创建索引
|
||||||
|
CREATE INDEX idx_students_score ON students(score);
|
||||||
|
CREATE INDEX idx_orders_date_amount ON orders(order_date, amount);
|
||||||
|
CREATE INDEX idx_staff_dept_salary ON staff(department, salary DESC);
|
||||||
|
|
||||||
|
-- 复合索引的排序优化
|
||||||
|
CREATE INDEX idx_orders_status_priority_amount ON orders(status, priority, amount DESC);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. LIMIT 与 ORDER BY 结合使用
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- ✅ 推荐:只获取需要的记录数
|
||||||
|
SELECT name, score FROM students ORDER BY score DESC LIMIT 10;
|
||||||
|
|
||||||
|
-- ❌ 避免:不必要的全表排序
|
||||||
|
SELECT name, score FROM students ORDER BY score DESC; -- 然后在应用层取前10条
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 避免文件排序
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- ✅ 使用索引排序(Using index)
|
||||||
|
EXPLAIN SELECT name, score FROM students ORDER BY score DESC;
|
||||||
|
|
||||||
|
-- 查看执行计划中是否出现 "Using filesort"
|
||||||
|
-- 出现则表示需要文件排序,性能较差
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 排序字段选择
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- ✅ 推荐:使用数值型字段排序
|
||||||
|
SELECT * FROM orders ORDER BY amount DESC;
|
||||||
|
|
||||||
|
-- ⚠️ 注意:字符串排序相对较慢
|
||||||
|
SELECT * FROM orders ORDER BY customer_name;
|
||||||
|
|
||||||
|
-- ✅ 优化:为常用字符串排序创建专门索引
|
||||||
|
CREATE INDEX idx_orders_customer_name ON orders(customer_name);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. 内存使用优化
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 调整排序缓冲区大小(根据实际情况)
|
||||||
|
SET SESSION sort_buffer_size = 2097152; -- 2MB
|
||||||
|
|
||||||
|
-- 监控排序性能
|
||||||
|
SHOW STATUS LIKE 'Sort%';
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6. 常见性能陷阱
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- ❌ 避免:在ORDER BY中使用函数
|
||||||
|
SELECT * FROM students ORDER BY UPPER(name);
|
||||||
|
|
||||||
|
-- ✅ 推荐:创建函数索引或使用计算列
|
||||||
|
ALTER TABLE students ADD COLUMN name_upper VARCHAR(50) GENERATED ALWAYS AS (UPPER(name));
|
||||||
|
CREATE INDEX idx_students_name_upper ON students(name_upper);
|
||||||
|
|
||||||
|
-- ❌ 避免:复杂的CASE WHEN排序
|
||||||
|
SELECT * FROM orders
|
||||||
|
ORDER BY CASE WHEN status = 'pending' THEN 1
|
||||||
|
WHEN status = 'processing' THEN 2
|
||||||
|
ELSE 3 END, amount DESC;
|
||||||
|
|
||||||
|
-- ✅ 推荐:添加排序辅助列
|
||||||
|
ALTER TABLE orders ADD COLUMN status_sort_order INT;
|
||||||
|
UPDATE orders SET status_sort_order = CASE
|
||||||
|
WHEN status = 'pending' THEN 1
|
||||||
|
WHEN status = 'processing' THEN 2
|
||||||
|
ELSE 3 END;
|
||||||
|
CREATE INDEX idx_orders_status_sort ON orders(status_sort_order, amount DESC);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7. 监控和调优
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 查看慢查询日志中的排序相关查询
|
||||||
|
SHOW VARIABLES LIKE 'slow_query_log%';
|
||||||
|
|
||||||
|
-- 分析排序操作的性能
|
||||||
|
SELECT
|
||||||
|
sql_text,
|
||||||
|
exec_count,
|
||||||
|
avg_timer_wait/1000000000000 as 'avg_time_sec'
|
||||||
|
FROM performance_schema.events_statements_summary_by_digest
|
||||||
|
WHERE sql_text LIKE '%ORDER BY%'
|
||||||
|
ORDER BY avg_timer_wait DESC
|
||||||
|
LIMIT 10;
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**总结:**
|
||||||
|
- ORDER BY 是数据排序的核心工具
|
||||||
|
- 合理使用索引可以显著提升排序性能
|
||||||
|
- 多列排序时注意优先级和索引设计
|
||||||
|
- 避免在排序字段上使用函数计算
|
||||||
|
- 结合 LIMIT 使用可以优化大数据量查询
|
||||||
|
- 理解不同排名函数的差异和适用场景
|
||||||
956
MySQL_RANGE查询优化详解.md
Normal file
956
MySQL_RANGE查询优化详解.md
Normal file
@ -0,0 +1,956 @@
|
|||||||
|
# MySQL RANGE 查询优化详解
|
||||||
|
|
||||||
|
## 目录
|
||||||
|
1. [RANGE 查询基本概念](#range-查询基本概念)
|
||||||
|
2. [RANGE 查询原理和类型](#range-查询原理和类型)
|
||||||
|
3. [示例数据准备](#示例数据准备)
|
||||||
|
4. [RANGE 查询优化技巧](#range-查询优化技巧)
|
||||||
|
5. [索引策略和执行计划分析](#索引策略和执行计划分析)
|
||||||
|
6. [实际开发场景案例](#实际开发场景案例)
|
||||||
|
7. [分区表与 RANGE 优化](#分区表与-range-优化)
|
||||||
|
8. [性能监控和调优](#性能监控和调优)
|
||||||
|
9. [常见问题和最佳实践](#常见问题和最佳实践)
|
||||||
|
|
||||||
|
## RANGE 查询基本概念
|
||||||
|
|
||||||
|
### 什么是 RANGE 查询
|
||||||
|
|
||||||
|
RANGE 查询是指查询某个字段在指定范围内的数据,在 MySQL 执行计划中表现为 `type=range`。这类查询在实际开发中非常常见,包括:
|
||||||
|
|
||||||
|
- 时间范围查询:`WHERE create_time BETWEEN '2024-01-01' AND '2024-12-31'`
|
||||||
|
- 数值范围查询:`WHERE price BETWEEN 100 AND 500`
|
||||||
|
- 大于小于查询:`WHERE id > 1000 AND id < 2000`
|
||||||
|
- IN 查询:`WHERE status IN ('active', 'pending')`
|
||||||
|
|
||||||
|
### RANGE 查询的重要性
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 典型的业务场景
|
||||||
|
-- 1. 订单查询:查询某个时间段的订单
|
||||||
|
SELECT * FROM orders WHERE order_date BETWEEN '2024-01-01' AND '2024-01-31';
|
||||||
|
|
||||||
|
-- 2. 商品筛选:查询某个价格区间的商品
|
||||||
|
SELECT * FROM products WHERE price >= 100 AND price <= 500;
|
||||||
|
|
||||||
|
-- 3. 日志分析:查询某个ID范围的日志
|
||||||
|
SELECT * FROM logs WHERE id > 10000 AND id <= 20000;
|
||||||
|
|
||||||
|
-- 4. 状态筛选:查询多个状态的记录
|
||||||
|
SELECT * FROM users WHERE status IN ('active', 'premium', 'vip');
|
||||||
|
```
|
||||||
|
|
||||||
|
## RANGE 查询原理和类型
|
||||||
|
|
||||||
|
### 1. MySQL 中 RANGE 查询的类型
|
||||||
|
|
||||||
|
| 类型 | 描述 | 示例 |
|
||||||
|
|------|------|------|
|
||||||
|
| **range** | 索引范围扫描 | `WHERE id BETWEEN 1 AND 100` |
|
||||||
|
| **index_merge** | 多个索引合并 | `WHERE id > 100 OR name = 'test'` |
|
||||||
|
| **ref_or_null** | 引用查询包含NULL | `WHERE id = 1 OR id IS NULL` |
|
||||||
|
|
||||||
|
### 2. RANGE 查询的执行流程
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 执行流程示意
|
||||||
|
1. 解析 SQL 语句和条件
|
||||||
|
2. 选择最优索引
|
||||||
|
3. 定位起始位置(起始键值)
|
||||||
|
4. 扫描到结束位置(结束键值)
|
||||||
|
5. 返回符合条件的记录
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 索引选择性和基数
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 查看索引选择性
|
||||||
|
SELECT
|
||||||
|
COUNT(DISTINCT column_name) / COUNT(*) as selectivity,
|
||||||
|
COUNT(DISTINCT column_name) as cardinality,
|
||||||
|
COUNT(*) as total_rows
|
||||||
|
FROM table_name;
|
||||||
|
|
||||||
|
-- 选择性越高,索引效果越好
|
||||||
|
-- 选择性 = 不重复值数量 / 总行数
|
||||||
|
-- 选择性接近 1 表示索引效果最好
|
||||||
|
```
|
||||||
|
|
||||||
|
## 示例数据准备
|
||||||
|
|
||||||
|
### 创建测试表和数据
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 创建订单表
|
||||||
|
CREATE TABLE orders (
|
||||||
|
order_id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||||
|
user_id INT NOT NULL,
|
||||||
|
product_id INT NOT NULL,
|
||||||
|
order_date DATE NOT NULL,
|
||||||
|
order_time DATETIME NOT NULL,
|
||||||
|
amount DECIMAL(10,2) NOT NULL,
|
||||||
|
quantity INT NOT NULL,
|
||||||
|
status ENUM('pending', 'paid', 'shipped', 'delivered', 'cancelled') NOT NULL,
|
||||||
|
region VARCHAR(50) NOT NULL,
|
||||||
|
channel VARCHAR(30) NOT NULL,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
|
|
||||||
|
KEY idx_user_id (user_id),
|
||||||
|
KEY idx_product_id (product_id),
|
||||||
|
KEY idx_order_date (order_date),
|
||||||
|
KEY idx_order_time (order_time),
|
||||||
|
KEY idx_amount (amount),
|
||||||
|
KEY idx_status (status),
|
||||||
|
KEY idx_region (region),
|
||||||
|
KEY idx_user_date (user_id, order_date),
|
||||||
|
KEY idx_status_date (status, order_date),
|
||||||
|
KEY idx_amount_date (amount, order_date)
|
||||||
|
) ENGINE=InnoDB;
|
||||||
|
|
||||||
|
-- 创建商品表
|
||||||
|
CREATE TABLE products (
|
||||||
|
product_id INT PRIMARY KEY AUTO_INCREMENT,
|
||||||
|
product_name VARCHAR(200) NOT NULL,
|
||||||
|
category_id INT NOT NULL,
|
||||||
|
price DECIMAL(8,2) NOT NULL,
|
||||||
|
cost DECIMAL(8,2) NOT NULL,
|
||||||
|
stock_quantity INT NOT NULL,
|
||||||
|
weight DECIMAL(6,3),
|
||||||
|
created_date DATE NOT NULL,
|
||||||
|
last_update_time DATETIME NOT NULL,
|
||||||
|
status TINYINT NOT NULL DEFAULT 1,
|
||||||
|
|
||||||
|
KEY idx_category (category_id),
|
||||||
|
KEY idx_price (price),
|
||||||
|
KEY idx_stock (stock_quantity),
|
||||||
|
KEY idx_created_date (created_date),
|
||||||
|
KEY idx_status (status),
|
||||||
|
KEY idx_category_price (category_id, price),
|
||||||
|
KEY idx_status_price (status, price)
|
||||||
|
) ENGINE=InnoDB;
|
||||||
|
|
||||||
|
-- 创建用户访问日志表
|
||||||
|
CREATE TABLE access_logs (
|
||||||
|
log_id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||||
|
user_id INT,
|
||||||
|
ip_address VARCHAR(45),
|
||||||
|
request_url VARCHAR(500),
|
||||||
|
request_method VARCHAR(10),
|
||||||
|
response_code INT,
|
||||||
|
response_time_ms INT,
|
||||||
|
user_agent TEXT,
|
||||||
|
access_time DATETIME NOT NULL,
|
||||||
|
session_id VARCHAR(64),
|
||||||
|
|
||||||
|
KEY idx_user_id (user_id),
|
||||||
|
KEY idx_access_time (access_time),
|
||||||
|
KEY idx_response_code (response_code),
|
||||||
|
KEY idx_response_time (response_time_ms),
|
||||||
|
KEY idx_user_time (user_id, access_time),
|
||||||
|
KEY idx_code_time (response_code, access_time)
|
||||||
|
) ENGINE=InnoDB;
|
||||||
|
|
||||||
|
-- 插入测试数据(使用存储过程快速生成大量数据)
|
||||||
|
DELIMITER //
|
||||||
|
CREATE PROCEDURE GenerateTestData()
|
||||||
|
BEGIN
|
||||||
|
DECLARE i INT DEFAULT 1;
|
||||||
|
DECLARE max_records INT DEFAULT 100000;
|
||||||
|
|
||||||
|
-- 清空表
|
||||||
|
TRUNCATE TABLE orders;
|
||||||
|
TRUNCATE TABLE products;
|
||||||
|
TRUNCATE TABLE access_logs;
|
||||||
|
|
||||||
|
-- 生成商品数据
|
||||||
|
WHILE i <= 1000 DO
|
||||||
|
INSERT INTO products (
|
||||||
|
product_name, category_id, price, cost, stock_quantity,
|
||||||
|
weight, created_date, last_update_time, status
|
||||||
|
) VALUES (
|
||||||
|
CONCAT('产品_', i),
|
||||||
|
(i % 50) + 1,
|
||||||
|
ROUND(RAND() * 1000 + 10, 2),
|
||||||
|
ROUND(RAND() * 500 + 5, 2),
|
||||||
|
FLOOR(RAND() * 1000),
|
||||||
|
ROUND(RAND() * 10, 3),
|
||||||
|
DATE_SUB(CURDATE(), INTERVAL FLOOR(RAND() * 365) DAY),
|
||||||
|
DATE_SUB(NOW(), INTERVAL FLOOR(RAND() * 30) DAY),
|
||||||
|
IF(RAND() > 0.1, 1, 0)
|
||||||
|
);
|
||||||
|
SET i = i + 1;
|
||||||
|
END WHILE;
|
||||||
|
|
||||||
|
-- 生成订单数据
|
||||||
|
SET i = 1;
|
||||||
|
WHILE i <= max_records DO
|
||||||
|
INSERT INTO orders (
|
||||||
|
user_id, product_id, order_date, order_time, amount, quantity,
|
||||||
|
status, region, channel
|
||||||
|
) VALUES (
|
||||||
|
FLOOR(RAND() * 10000) + 1,
|
||||||
|
FLOOR(RAND() * 1000) + 1,
|
||||||
|
DATE_SUB(CURDATE(), INTERVAL FLOOR(RAND() * 365) DAY),
|
||||||
|
DATE_SUB(NOW(), INTERVAL FLOOR(RAND() * 365 * 24 * 60) MINUTE),
|
||||||
|
ROUND(RAND() * 1000 + 10, 2),
|
||||||
|
FLOOR(RAND() * 5) + 1,
|
||||||
|
ELT(FLOOR(RAND() * 5) + 1, 'pending', 'paid', 'shipped', 'delivered', 'cancelled'),
|
||||||
|
ELT(FLOOR(RAND() * 6) + 1, '北京', '上海', '广州', '深圳', '杭州', '成都'),
|
||||||
|
ELT(FLOOR(RAND() * 4) + 1, 'web', 'mobile', 'api', 'admin')
|
||||||
|
);
|
||||||
|
|
||||||
|
IF i % 10000 = 0 THEN
|
||||||
|
COMMIT;
|
||||||
|
END IF;
|
||||||
|
SET i = i + 1;
|
||||||
|
END WHILE;
|
||||||
|
|
||||||
|
-- 生成访问日志数据
|
||||||
|
SET i = 1;
|
||||||
|
WHILE i <= max_records DO
|
||||||
|
INSERT INTO access_logs (
|
||||||
|
user_id, ip_address, request_url, request_method,
|
||||||
|
response_code, response_time_ms, access_time, session_id
|
||||||
|
) VALUES (
|
||||||
|
IF(RAND() > 0.3, FLOOR(RAND() * 10000) + 1, NULL),
|
||||||
|
CONCAT(
|
||||||
|
FLOOR(RAND() * 255), '.',
|
||||||
|
FLOOR(RAND() * 255), '.',
|
||||||
|
FLOOR(RAND() * 255), '.',
|
||||||
|
FLOOR(RAND() * 255)
|
||||||
|
),
|
||||||
|
CONCAT('/api/v1/', ELT(FLOOR(RAND() * 5) + 1, 'users', 'orders', 'products', 'search', 'cart')),
|
||||||
|
ELT(FLOOR(RAND() * 4) + 1, 'GET', 'POST', 'PUT', 'DELETE'),
|
||||||
|
ELT(FLOOR(RAND() * 10) + 1, 200, 200, 200, 200, 200, 404, 500, 403, 401, 302),
|
||||||
|
FLOOR(RAND() * 5000) + 10,
|
||||||
|
DATE_SUB(NOW(), INTERVAL FLOOR(RAND() * 30 * 24 * 60) MINUTE),
|
||||||
|
MD5(CONCAT(i, RAND()))
|
||||||
|
);
|
||||||
|
|
||||||
|
IF i % 10000 = 0 THEN
|
||||||
|
COMMIT;
|
||||||
|
END IF;
|
||||||
|
SET i = i + 1;
|
||||||
|
END WHILE;
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
|
END //
|
||||||
|
DELIMITER ;
|
||||||
|
|
||||||
|
-- 执行数据生成(注意:这会花费一些时间)
|
||||||
|
CALL GenerateTestData();
|
||||||
|
|
||||||
|
-- 更新表统计信息
|
||||||
|
ANALYZE TABLE orders, products, access_logs;
|
||||||
|
```
|
||||||
|
|
||||||
|
## RANGE 查询优化技巧
|
||||||
|
|
||||||
|
### 1. 日期范围查询优化
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- ❌ 低效的日期查询(无法使用索引)
|
||||||
|
SELECT * FROM orders
|
||||||
|
WHERE YEAR(order_date) = 2024 AND MONTH(order_date) = 1;
|
||||||
|
|
||||||
|
-- ❌ 函数导致全表扫描
|
||||||
|
SELECT * FROM orders
|
||||||
|
WHERE DATE_FORMAT(order_date, '%Y-%m') = '2024-01';
|
||||||
|
|
||||||
|
-- ✅ 高效的日期范围查询
|
||||||
|
SELECT * FROM orders
|
||||||
|
WHERE order_date >= '2024-01-01' AND order_date < '2024-02-01';
|
||||||
|
|
||||||
|
-- ✅ 使用 BETWEEN(包含边界)
|
||||||
|
SELECT * FROM orders
|
||||||
|
WHERE order_date BETWEEN '2024-01-01' AND '2024-01-31';
|
||||||
|
|
||||||
|
-- 执行计划对比
|
||||||
|
EXPLAIN FORMAT=JSON
|
||||||
|
SELECT * FROM orders
|
||||||
|
WHERE order_date >= '2024-01-01' AND order_date < '2024-02-01';
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 数值范围查询优化
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- ✅ 基本数值范围查询
|
||||||
|
SELECT * FROM products
|
||||||
|
WHERE price BETWEEN 100 AND 500;
|
||||||
|
|
||||||
|
-- ✅ 组合条件优化
|
||||||
|
SELECT * FROM products
|
||||||
|
WHERE price >= 100 AND price <= 500
|
||||||
|
AND status = 1;
|
||||||
|
|
||||||
|
-- 🔍 查看执行计划
|
||||||
|
EXPLAIN FORMAT=JSON
|
||||||
|
SELECT * FROM products
|
||||||
|
WHERE price >= 100 AND price <= 500 AND status = 1;
|
||||||
|
|
||||||
|
-- ✅ 多条件范围查询优化
|
||||||
|
SELECT * FROM orders
|
||||||
|
WHERE amount >= 100 AND amount <= 1000
|
||||||
|
AND order_date >= '2024-01-01' AND order_date <= '2024-12-31';
|
||||||
|
|
||||||
|
-- 🔍 分析索引选择
|
||||||
|
EXPLAIN FORMAT=JSON
|
||||||
|
SELECT * FROM orders
|
||||||
|
WHERE amount >= 100 AND amount <= 1000
|
||||||
|
AND order_date >= '2024-01-01' AND order_date <= '2024-12-31';
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. IN 查询优化
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- ✅ 基本 IN 查询
|
||||||
|
SELECT * FROM orders WHERE status IN ('paid', 'shipped', 'delivered');
|
||||||
|
|
||||||
|
-- ✅ 大量 IN 值的优化
|
||||||
|
SELECT * FROM orders
|
||||||
|
WHERE user_id IN (1, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50);
|
||||||
|
|
||||||
|
-- ❌ 避免过多的 IN 值(可能导致性能问题)
|
||||||
|
-- SELECT * FROM orders WHERE user_id IN (1,2,3,...,1000); -- 太多值
|
||||||
|
|
||||||
|
-- ✅ 使用 EXISTS 替代大量 IN 值
|
||||||
|
CREATE TEMPORARY TABLE temp_users (user_id INT PRIMARY KEY);
|
||||||
|
INSERT INTO temp_users VALUES (1), (5), (10), (15), (20);
|
||||||
|
|
||||||
|
SELECT o.* FROM orders o
|
||||||
|
WHERE EXISTS (SELECT 1 FROM temp_users t WHERE t.user_id = o.user_id);
|
||||||
|
|
||||||
|
-- ✅ 使用 JOIN 替代大量 IN 值
|
||||||
|
SELECT o.* FROM orders o
|
||||||
|
INNER JOIN temp_users t ON o.user_id = t.user_id;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 复合索引的 RANGE 查询优化
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 创建复合索引
|
||||||
|
CREATE INDEX idx_user_status_date ON orders(user_id, status, order_date);
|
||||||
|
|
||||||
|
-- ✅ 充分利用复合索引(遵循最左前缀原则)
|
||||||
|
SELECT * FROM orders
|
||||||
|
WHERE user_id = 1001
|
||||||
|
AND status IN ('paid', 'shipped')
|
||||||
|
AND order_date >= '2024-01-01';
|
||||||
|
|
||||||
|
-- ✅ 部分利用复合索引
|
||||||
|
SELECT * FROM orders
|
||||||
|
WHERE user_id = 1001
|
||||||
|
AND order_date >= '2024-01-01';
|
||||||
|
|
||||||
|
-- ❌ 无法利用复合索引(跳过了前缀列)
|
||||||
|
SELECT * FROM orders
|
||||||
|
WHERE status = 'paid'
|
||||||
|
AND order_date >= '2024-01-01';
|
||||||
|
|
||||||
|
-- 🔍 验证索引使用情况
|
||||||
|
EXPLAIN FORMAT=JSON
|
||||||
|
SELECT * FROM orders
|
||||||
|
WHERE user_id = 1001
|
||||||
|
AND status IN ('paid', 'shipped')
|
||||||
|
AND order_date >= '2024-01-01';
|
||||||
|
```
|
||||||
|
|
||||||
|
## 索引策略和执行计划分析
|
||||||
|
|
||||||
|
### 1. 执行计划解读
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 创建测试查询并分析执行计划
|
||||||
|
EXPLAIN FORMAT=JSON
|
||||||
|
SELECT * FROM orders
|
||||||
|
WHERE order_date BETWEEN '2024-01-01' AND '2024-01-31'
|
||||||
|
AND amount >= 100;
|
||||||
|
|
||||||
|
-- 关键信息解读:
|
||||||
|
-- "access_type": "range" - 表示使用了范围扫描
|
||||||
|
-- "key": "idx_order_date" - 使用的索引名称
|
||||||
|
-- "rows_examined_per_scan": 预估扫描行数
|
||||||
|
-- "cost_info": 查询成本信息
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 索引选择性分析
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 分析不同字段的选择性
|
||||||
|
SELECT
|
||||||
|
'order_date' as column_name,
|
||||||
|
COUNT(DISTINCT order_date) as distinct_values,
|
||||||
|
COUNT(*) as total_rows,
|
||||||
|
COUNT(DISTINCT order_date) / COUNT(*) as selectivity
|
||||||
|
FROM orders
|
||||||
|
|
||||||
|
UNION ALL
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
'amount' as column_name,
|
||||||
|
COUNT(DISTINCT amount) as distinct_values,
|
||||||
|
COUNT(*) as total_rows,
|
||||||
|
COUNT(DISTINCT amount) / COUNT(*) as selectivity
|
||||||
|
FROM orders
|
||||||
|
|
||||||
|
UNION ALL
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
'status' as column_name,
|
||||||
|
COUNT(DISTINCT status) as distinct_values,
|
||||||
|
COUNT(*) as total_rows,
|
||||||
|
COUNT(DISTINCT status) / COUNT(*) as selectivity
|
||||||
|
FROM orders;
|
||||||
|
|
||||||
|
-- 根据选择性结果调整索引策略
|
||||||
|
-- 高选择性字段适合做索引前缀
|
||||||
|
-- 低选择性字段适合放在组合索引后面
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 成本分析和索引选择
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 强制使用不同索引对比性能
|
||||||
|
-- 使用日期索引
|
||||||
|
SELECT * FROM orders USE INDEX (idx_order_date)
|
||||||
|
WHERE order_date BETWEEN '2024-01-01' AND '2024-01-31'
|
||||||
|
AND amount >= 100;
|
||||||
|
|
||||||
|
-- 使用金额索引
|
||||||
|
SELECT * FROM orders USE INDEX (idx_amount)
|
||||||
|
WHERE order_date BETWEEN '2024-01-01' AND '2024-01-31'
|
||||||
|
AND amount >= 100;
|
||||||
|
|
||||||
|
-- 使用复合索引
|
||||||
|
SELECT * FROM orders USE INDEX (idx_amount_date)
|
||||||
|
WHERE order_date BETWEEN '2024-01-01' AND '2024-01-31'
|
||||||
|
AND amount >= 100;
|
||||||
|
|
||||||
|
-- 让优化器自动选择
|
||||||
|
SELECT * FROM orders
|
||||||
|
WHERE order_date BETWEEN '2024-01-01' AND '2024-01-31'
|
||||||
|
AND amount >= 100;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 索引提示的使用
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 强制使用特定索引
|
||||||
|
SELECT * FROM orders FORCE INDEX (idx_order_date)
|
||||||
|
WHERE order_date >= '2024-01-01' AND amount > 100;
|
||||||
|
|
||||||
|
-- 忽略某个索引
|
||||||
|
SELECT * FROM orders IGNORE INDEX (idx_amount)
|
||||||
|
WHERE order_date >= '2024-01-01' AND amount > 100;
|
||||||
|
|
||||||
|
-- 建议使用某个索引
|
||||||
|
SELECT * FROM orders USE INDEX (idx_amount_date)
|
||||||
|
WHERE order_date >= '2024-01-01' AND amount > 100;
|
||||||
|
```
|
||||||
|
|
||||||
|
## 实际开发场景案例
|
||||||
|
|
||||||
|
### 案例1:电商订单查询系统
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 需求:查询某用户在指定时间段内的订单,按金额排序
|
||||||
|
-- 原始查询(可能性能较差)
|
||||||
|
SELECT * FROM orders
|
||||||
|
WHERE user_id = 1001
|
||||||
|
AND order_date BETWEEN '2024-01-01' AND '2024-01-31'
|
||||||
|
ORDER BY amount DESC;
|
||||||
|
|
||||||
|
-- 优化方案1:创建专门的复合索引
|
||||||
|
CREATE INDEX idx_user_date_amount ON orders(user_id, order_date, amount DESC);
|
||||||
|
|
||||||
|
-- 优化后的查询
|
||||||
|
SELECT * FROM orders
|
||||||
|
WHERE user_id = 1001
|
||||||
|
AND order_date BETWEEN '2024-01-01' AND '2024-01-31'
|
||||||
|
ORDER BY amount DESC;
|
||||||
|
|
||||||
|
-- 验证优化效果
|
||||||
|
EXPLAIN FORMAT=JSON
|
||||||
|
SELECT * FROM orders
|
||||||
|
WHERE user_id = 1001
|
||||||
|
AND order_date BETWEEN '2024-01-01' AND '2024-01-31'
|
||||||
|
ORDER BY amount DESC;
|
||||||
|
|
||||||
|
-- 如果需要分页
|
||||||
|
SELECT * FROM orders
|
||||||
|
WHERE user_id = 1001
|
||||||
|
AND order_date BETWEEN '2024-01-01' AND '2024-01-31'
|
||||||
|
ORDER BY amount DESC
|
||||||
|
LIMIT 20 OFFSET 0;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 案例2:实时数据分析查询
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 需求:按小时统计最近24小时的订单数量和金额
|
||||||
|
-- 原始查询
|
||||||
|
SELECT
|
||||||
|
DATE_FORMAT(order_time, '%Y-%m-%d %H:00:00') as hour_bucket,
|
||||||
|
COUNT(*) as order_count,
|
||||||
|
SUM(amount) as total_amount
|
||||||
|
FROM orders
|
||||||
|
WHERE order_time >= DATE_SUB(NOW(), INTERVAL 24 HOUR)
|
||||||
|
GROUP BY DATE_FORMAT(order_time, '%Y-%m-%d %H:00:00')
|
||||||
|
ORDER BY hour_bucket;
|
||||||
|
|
||||||
|
-- 优化方案:创建时间索引并使用覆盖索引
|
||||||
|
CREATE INDEX idx_order_time_amount ON orders(order_time, amount);
|
||||||
|
|
||||||
|
-- 优化查询(避免函数计算)
|
||||||
|
SELECT
|
||||||
|
FROM_UNIXTIME(UNIX_TIMESTAMP(order_time) - UNIX_TIMESTAMP(order_time) % 3600) as hour_bucket,
|
||||||
|
COUNT(*) as order_count,
|
||||||
|
SUM(amount) as total_amount
|
||||||
|
FROM orders
|
||||||
|
WHERE order_time >= DATE_SUB(NOW(), INTERVAL 24 HOUR)
|
||||||
|
GROUP BY FROM_UNIXTIME(UNIX_TIMESTAMP(order_time) - UNIX_TIMESTAMP(order_time) % 3600)
|
||||||
|
ORDER BY hour_bucket;
|
||||||
|
|
||||||
|
-- 进一步优化:预计算小时桶
|
||||||
|
ALTER TABLE orders ADD COLUMN hour_bucket DATETIME GENERATED ALWAYS AS
|
||||||
|
(FROM_UNIXTIME(UNIX_TIMESTAMP(order_time) - UNIX_TIMESTAMP(order_time) % 3600)) STORED;
|
||||||
|
|
||||||
|
CREATE INDEX idx_hour_bucket_amount ON orders(hour_bucket, amount);
|
||||||
|
|
||||||
|
-- 最优化查询
|
||||||
|
SELECT
|
||||||
|
hour_bucket,
|
||||||
|
COUNT(*) as order_count,
|
||||||
|
SUM(amount) as total_amount
|
||||||
|
FROM orders
|
||||||
|
WHERE hour_bucket >= DATE_SUB(DATE_SUB(NOW(), INTERVAL MINUTE(NOW()) MINUTE), INTERVAL 23 HOUR)
|
||||||
|
GROUP BY hour_bucket
|
||||||
|
ORDER BY hour_bucket;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 案例3:用户行为分析
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 需求:分析响应时间在不同区间的请求分布
|
||||||
|
-- 创建响应时间分析的优化索引
|
||||||
|
CREATE INDEX idx_response_time_code ON access_logs(response_time_ms, response_code);
|
||||||
|
|
||||||
|
-- 分析查询
|
||||||
|
SELECT
|
||||||
|
CASE
|
||||||
|
WHEN response_time_ms < 100 THEN '< 100ms'
|
||||||
|
WHEN response_time_ms < 500 THEN '100-500ms'
|
||||||
|
WHEN response_time_ms < 1000 THEN '500ms-1s'
|
||||||
|
WHEN response_time_ms < 3000 THEN '1-3s'
|
||||||
|
ELSE '> 3s'
|
||||||
|
END as response_time_bucket,
|
||||||
|
COUNT(*) as request_count,
|
||||||
|
COUNT(CASE WHEN response_code = 200 THEN 1 END) as success_count,
|
||||||
|
ROUND(AVG(response_time_ms), 2) as avg_response_time
|
||||||
|
FROM access_logs
|
||||||
|
WHERE access_time >= DATE_SUB(NOW(), INTERVAL 7 DAY)
|
||||||
|
GROUP BY
|
||||||
|
CASE
|
||||||
|
WHEN response_time_ms < 100 THEN '< 100ms'
|
||||||
|
WHEN response_time_ms < 500 THEN '100-500ms'
|
||||||
|
WHEN response_time_ms < 1000 THEN '500ms-1s'
|
||||||
|
WHEN response_time_ms < 3000 THEN '1-3s'
|
||||||
|
ELSE '> 3s'
|
||||||
|
END
|
||||||
|
ORDER BY
|
||||||
|
CASE
|
||||||
|
WHEN response_time_ms < 100 THEN 1
|
||||||
|
WHEN response_time_ms < 500 THEN 2
|
||||||
|
WHEN response_time_ms < 1000 THEN 3
|
||||||
|
WHEN response_time_ms < 3000 THEN 4
|
||||||
|
ELSE 5
|
||||||
|
END;
|
||||||
|
|
||||||
|
-- 优化版本:使用多个简单的RANGE查询代替复杂CASE
|
||||||
|
SELECT '< 100ms' as bucket, COUNT(*) as count FROM access_logs
|
||||||
|
WHERE access_time >= DATE_SUB(NOW(), INTERVAL 7 DAY) AND response_time_ms < 100
|
||||||
|
UNION ALL
|
||||||
|
SELECT '100-500ms', COUNT(*) FROM access_logs
|
||||||
|
WHERE access_time >= DATE_SUB(NOW(), INTERVAL 7 DAY) AND response_time_ms >= 100 AND response_time_ms < 500
|
||||||
|
UNION ALL
|
||||||
|
SELECT '500ms-1s', COUNT(*) FROM access_logs
|
||||||
|
WHERE access_time >= DATE_SUB(NOW(), INTERVAL 7 DAY) AND response_time_ms >= 500 AND response_time_ms < 1000
|
||||||
|
UNION ALL
|
||||||
|
SELECT '1-3s', COUNT(*) FROM access_logs
|
||||||
|
WHERE access_time >= DATE_SUB(NOW(), INTERVAL 7 DAY) AND response_time_ms >= 1000 AND response_time_ms < 3000
|
||||||
|
UNION ALL
|
||||||
|
SELECT '> 3s', COUNT(*) FROM access_logs
|
||||||
|
WHERE access_time >= DATE_SUB(NOW(), INTERVAL 7 DAY) AND response_time_ms >= 3000;
|
||||||
|
```
|
||||||
|
|
||||||
|
## 分区表与 RANGE 优化
|
||||||
|
|
||||||
|
### 1. 基于日期的范围分区
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 创建按月份分区的订单表
|
||||||
|
CREATE TABLE orders_partitioned (
|
||||||
|
order_id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||||
|
user_id INT NOT NULL,
|
||||||
|
product_id INT NOT NULL,
|
||||||
|
order_date DATE NOT NULL,
|
||||||
|
order_time DATETIME NOT NULL,
|
||||||
|
amount DECIMAL(10,2) NOT NULL,
|
||||||
|
status ENUM('pending', 'paid', 'shipped', 'delivered', 'cancelled') NOT NULL,
|
||||||
|
region VARCHAR(50) NOT NULL,
|
||||||
|
|
||||||
|
KEY idx_user_id (user_id),
|
||||||
|
KEY idx_order_date (order_date),
|
||||||
|
KEY idx_amount (amount),
|
||||||
|
KEY idx_user_date (user_id, order_date)
|
||||||
|
) ENGINE=InnoDB
|
||||||
|
PARTITION BY RANGE (YEAR(order_date) * 100 + MONTH(order_date)) (
|
||||||
|
PARTITION p202401 VALUES LESS THAN (202402),
|
||||||
|
PARTITION p202402 VALUES LESS THAN (202403),
|
||||||
|
PARTITION p202403 VALUES LESS THAN (202404),
|
||||||
|
PARTITION p202404 VALUES LESS THAN (202405),
|
||||||
|
PARTITION p202405 VALUES LESS THAN (202406),
|
||||||
|
PARTITION p202406 VALUES LESS THAN (202407),
|
||||||
|
PARTITION p202407 VALUES LESS THAN (202408),
|
||||||
|
PARTITION p202408 VALUES LESS THAN (202409),
|
||||||
|
PARTITION p202409 VALUES LESS THAN (202410),
|
||||||
|
PARTITION p202410 VALUES LESS THAN (202411),
|
||||||
|
PARTITION p202411 VALUES LESS THAN (202412),
|
||||||
|
PARTITION p202412 VALUES LESS THAN (202501),
|
||||||
|
PARTITION p_future VALUES LESS THAN MAXVALUE
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 分区表的RANGE查询优化
|
||||||
|
-- ✅ 可以利用分区剪枝的查询
|
||||||
|
SELECT * FROM orders_partitioned
|
||||||
|
WHERE order_date BETWEEN '2024-03-01' AND '2024-03-31';
|
||||||
|
|
||||||
|
-- 查看分区剪枝效果
|
||||||
|
EXPLAIN PARTITIONS
|
||||||
|
SELECT * FROM orders_partitioned
|
||||||
|
WHERE order_date BETWEEN '2024-03-01' AND '2024-03-31';
|
||||||
|
|
||||||
|
-- ❌ 无法利用分区剪枝的查询(使用函数)
|
||||||
|
SELECT * FROM orders_partitioned
|
||||||
|
WHERE YEAR(order_date) = 2024 AND MONTH(order_date) = 3;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 基于数值的范围分区
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 创建按用户ID范围分区的表
|
||||||
|
CREATE TABLE user_activities_partitioned (
|
||||||
|
activity_id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||||
|
user_id INT NOT NULL,
|
||||||
|
activity_type VARCHAR(50) NOT NULL,
|
||||||
|
activity_time DATETIME NOT NULL,
|
||||||
|
activity_data JSON,
|
||||||
|
|
||||||
|
KEY idx_user_time (user_id, activity_time),
|
||||||
|
KEY idx_activity_time (activity_time)
|
||||||
|
) ENGINE=InnoDB
|
||||||
|
PARTITION BY RANGE (user_id) (
|
||||||
|
PARTITION p0 VALUES LESS THAN (1000),
|
||||||
|
PARTITION p1 VALUES LESS THAN (5000),
|
||||||
|
PARTITION p2 VALUES LESS THAN (10000),
|
||||||
|
PARTITION p3 VALUES LESS THAN (50000),
|
||||||
|
PARTITION p4 VALUES LESS THAN MAXVALUE
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 针对特定用户范围的查询优化
|
||||||
|
SELECT * FROM user_activities_partitioned
|
||||||
|
WHERE user_id BETWEEN 1000 AND 4999
|
||||||
|
AND activity_time >= '2024-01-01';
|
||||||
|
|
||||||
|
-- 查看执行计划
|
||||||
|
EXPLAIN PARTITIONS
|
||||||
|
SELECT * FROM user_activities_partitioned
|
||||||
|
WHERE user_id BETWEEN 1000 AND 4999
|
||||||
|
AND activity_time >= '2024-01-01';
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 分区表维护
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 添加新分区
|
||||||
|
ALTER TABLE orders_partitioned
|
||||||
|
ADD PARTITION (PARTITION p202501 VALUES LESS THAN (202502));
|
||||||
|
|
||||||
|
-- 删除旧分区(删除数据)
|
||||||
|
ALTER TABLE orders_partitioned DROP PARTITION p202401;
|
||||||
|
|
||||||
|
-- 重组分区
|
||||||
|
ALTER TABLE orders_partitioned
|
||||||
|
REORGANIZE PARTITION p_future INTO (
|
||||||
|
PARTITION p202502 VALUES LESS THAN (202503),
|
||||||
|
PARTITION p202503 VALUES LESS THAN (202504),
|
||||||
|
PARTITION p_future VALUES LESS THAN MAXVALUE
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 查看分区信息
|
||||||
|
SELECT
|
||||||
|
PARTITION_NAME,
|
||||||
|
TABLE_ROWS,
|
||||||
|
DATA_LENGTH,
|
||||||
|
PARTITION_DESCRIPTION
|
||||||
|
FROM information_schema.PARTITIONS
|
||||||
|
WHERE TABLE_SCHEMA = DATABASE()
|
||||||
|
AND TABLE_NAME = 'orders_partitioned';
|
||||||
|
```
|
||||||
|
|
||||||
|
## 性能监控和调优
|
||||||
|
|
||||||
|
### 1. 监控 RANGE 查询性能
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 查看慢查询中的RANGE查询
|
||||||
|
SELECT
|
||||||
|
sql_text,
|
||||||
|
exec_count,
|
||||||
|
avg_timer_wait/1000000000000 as avg_time_sec,
|
||||||
|
sum_rows_examined/exec_count as avg_rows_examined,
|
||||||
|
sum_rows_sent/exec_count as avg_rows_sent,
|
||||||
|
(sum_rows_examined/exec_count) / (sum_rows_sent/exec_count) as examine_ratio
|
||||||
|
FROM performance_schema.events_statements_summary_by_digest
|
||||||
|
WHERE sql_text LIKE '%BETWEEN%'
|
||||||
|
OR sql_text LIKE '%>%'
|
||||||
|
OR sql_text LIKE '%<%'
|
||||||
|
ORDER BY avg_timer_wait DESC
|
||||||
|
LIMIT 10;
|
||||||
|
|
||||||
|
-- 监控索引使用情况
|
||||||
|
SELECT
|
||||||
|
object_schema,
|
||||||
|
object_name,
|
||||||
|
index_name,
|
||||||
|
count_star as usage_count,
|
||||||
|
sum_timer_wait/1000000000000 as total_time_sec
|
||||||
|
FROM performance_schema.table_io_waits_summary_by_index_usage
|
||||||
|
WHERE object_schema = DATABASE()
|
||||||
|
AND count_star > 0
|
||||||
|
ORDER BY sum_timer_wait DESC;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 性能测试脚本
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 创建性能测试存储过程
|
||||||
|
DELIMITER //
|
||||||
|
CREATE PROCEDURE TestRangeQueryPerformance()
|
||||||
|
BEGIN
|
||||||
|
DECLARE start_time DATETIME;
|
||||||
|
DECLARE end_time DATETIME;
|
||||||
|
DECLARE duration_ms INT;
|
||||||
|
|
||||||
|
-- 测试1:日期范围查询
|
||||||
|
SET start_time = NOW(6);
|
||||||
|
SELECT COUNT(*) FROM orders
|
||||||
|
WHERE order_date BETWEEN '2024-01-01' AND '2024-01-31';
|
||||||
|
SET end_time = NOW(6);
|
||||||
|
SET duration_ms = TIMESTAMPDIFF(MICROSECOND, start_time, end_time) / 1000;
|
||||||
|
SELECT 'Date Range Query' as test_name, duration_ms as duration_ms;
|
||||||
|
|
||||||
|
-- 测试2:数值范围查询
|
||||||
|
SET start_time = NOW(6);
|
||||||
|
SELECT COUNT(*) FROM orders
|
||||||
|
WHERE amount BETWEEN 100 AND 500;
|
||||||
|
SET end_time = NOW(6);
|
||||||
|
SET duration_ms = TIMESTAMPDIFF(MICROSECOND, start_time, end_time) / 1000;
|
||||||
|
SELECT 'Amount Range Query' as test_name, duration_ms as duration_ms;
|
||||||
|
|
||||||
|
-- 测试3:复合条件查询
|
||||||
|
SET start_time = NOW(6);
|
||||||
|
SELECT COUNT(*) FROM orders
|
||||||
|
WHERE order_date BETWEEN '2024-01-01' AND '2024-01-31'
|
||||||
|
AND amount BETWEEN 100 AND 500;
|
||||||
|
SET end_time = NOW(6);
|
||||||
|
SET duration_ms = TIMESTAMPDIFF(MICROSECOND, start_time, end_time) / 1000;
|
||||||
|
SELECT 'Combined Range Query' as test_name, duration_ms as duration_ms;
|
||||||
|
|
||||||
|
END //
|
||||||
|
DELIMITER ;
|
||||||
|
|
||||||
|
-- 执行性能测试
|
||||||
|
CALL TestRangeQueryPerformance();
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 自动化索引建议
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 分析表的查询模式,建议索引
|
||||||
|
CREATE VIEW range_query_analysis AS
|
||||||
|
SELECT
|
||||||
|
'orders' as table_name,
|
||||||
|
'order_date' as column_name,
|
||||||
|
COUNT(DISTINCT order_date) as distinct_values,
|
||||||
|
COUNT(*) as total_rows,
|
||||||
|
COUNT(DISTINCT order_date) / COUNT(*) as selectivity,
|
||||||
|
CASE
|
||||||
|
WHEN COUNT(DISTINCT order_date) / COUNT(*) > 0.1 THEN '高选择性,建议单独建索引'
|
||||||
|
WHEN COUNT(DISTINCT order_date) / COUNT(*) > 0.01 THEN '中等选择性,建议组合索引'
|
||||||
|
ELSE '低选择性,不建议建索引'
|
||||||
|
END as index_recommendation
|
||||||
|
FROM orders
|
||||||
|
|
||||||
|
UNION ALL
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
'orders' as table_name,
|
||||||
|
'amount' as column_name,
|
||||||
|
COUNT(DISTINCT amount) as distinct_values,
|
||||||
|
COUNT(*) as total_rows,
|
||||||
|
COUNT(DISTINCT amount) / COUNT(*) as selectivity,
|
||||||
|
CASE
|
||||||
|
WHEN COUNT(DISTINCT amount) / COUNT(*) > 0.1 THEN '高选择性,建议单独建索引'
|
||||||
|
WHEN COUNT(DISTINCT amount) / COUNT(*) > 0.01 THEN '中等选择性,建议组合索引'
|
||||||
|
ELSE '低选择性,不建议建索引'
|
||||||
|
END as index_recommendation
|
||||||
|
FROM orders;
|
||||||
|
|
||||||
|
-- 查看分析结果
|
||||||
|
SELECT * FROM range_query_analysis;
|
||||||
|
```
|
||||||
|
|
||||||
|
## 常见问题和最佳实践
|
||||||
|
|
||||||
|
### 1. 常见性能陷阱
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- ❌ 陷阱1:在WHERE条件中使用函数
|
||||||
|
-- 错误示例
|
||||||
|
SELECT * FROM orders WHERE YEAR(order_date) = 2024;
|
||||||
|
SELECT * FROM orders WHERE DATE_ADD(order_date, INTERVAL 1 DAY) = '2024-01-02';
|
||||||
|
|
||||||
|
-- ✅ 正确做法
|
||||||
|
SELECT * FROM orders WHERE order_date >= '2024-01-01' AND order_date < '2025-01-01';
|
||||||
|
SELECT * FROM orders WHERE order_date = '2024-01-01';
|
||||||
|
|
||||||
|
-- ❌ 陷阱2:数据类型不匹配导致的隐式转换
|
||||||
|
-- 错误示例(假设user_id是INT类型)
|
||||||
|
SELECT * FROM orders WHERE user_id = '1001'; -- 字符串比较整数
|
||||||
|
|
||||||
|
-- ✅ 正确做法
|
||||||
|
SELECT * FROM orders WHERE user_id = 1001;
|
||||||
|
|
||||||
|
-- ❌ 陷阱3:LIKE查询的误用
|
||||||
|
-- 错误示例
|
||||||
|
SELECT * FROM orders WHERE order_id LIKE '1001%'; -- 应该用数值比较
|
||||||
|
|
||||||
|
-- ✅ 正确做法
|
||||||
|
SELECT * FROM orders WHERE order_id >= 1001 AND order_id < 1002;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 索引设计最佳实践
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- ✅ 最佳实践1:根据查询频率设计索引
|
||||||
|
-- 高频查询的索引设计
|
||||||
|
CREATE INDEX idx_user_status_date ON orders(user_id, status, order_date);
|
||||||
|
CREATE INDEX idx_date_amount ON orders(order_date, amount);
|
||||||
|
|
||||||
|
-- ✅ 最佳实践2:考虑排序需求的索引设计
|
||||||
|
-- 如果经常需要按金额降序排序
|
||||||
|
CREATE INDEX idx_date_amount_desc ON orders(order_date, amount DESC);
|
||||||
|
|
||||||
|
-- ✅ 最佳实践3:覆盖索引的设计
|
||||||
|
-- 如果只查询特定列,可以设计覆盖索引
|
||||||
|
CREATE INDEX idx_covering_order_summary ON orders(order_date, user_id, amount, status);
|
||||||
|
|
||||||
|
-- 该查询只需访问索引,无需回表
|
||||||
|
SELECT user_id, amount, status FROM orders
|
||||||
|
WHERE order_date BETWEEN '2024-01-01' AND '2024-01-31';
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 查询优化检查清单
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 优化检查清单
|
||||||
|
/*
|
||||||
|
1. ✅ 避免在WHERE条件中使用函数
|
||||||
|
2. ✅ 确保数据类型匹配,避免隐式转换
|
||||||
|
3. ✅ 合理使用复合索引,遵循最左前缀原则
|
||||||
|
4. ✅ 考虑使用覆盖索引减少回表操作
|
||||||
|
5. ✅ 对于大量IN值,考虑使用JOIN或EXISTS
|
||||||
|
6. ✅ 利用分区表的分区剪枝特性
|
||||||
|
7. ✅ 定期分析表统计信息
|
||||||
|
8. ✅ 监控慢查询日志和执行计划
|
||||||
|
9. ✅ 考虑查询结果的数据量,适当使用LIMIT
|
||||||
|
10. ✅ 根据业务特点选择合适的索引策略
|
||||||
|
*/
|
||||||
|
|
||||||
|
-- 索引效果验证模板
|
||||||
|
EXPLAIN FORMAT=JSON SELECT * FROM table_name WHERE conditions;
|
||||||
|
|
||||||
|
-- 性能对比模板
|
||||||
|
SELECT
|
||||||
|
BENCHMARK(10000, (SELECT COUNT(*) FROM table_name WHERE conditions)) as execution_time;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 实际生产环境建议
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 生产环境优化建议
|
||||||
|
|
||||||
|
-- 1. 定期收集表统计信息
|
||||||
|
ANALYZE TABLE orders, products, access_logs;
|
||||||
|
|
||||||
|
-- 2. 监控索引使用情况
|
||||||
|
CREATE EVENT check_index_usage
|
||||||
|
ON SCHEDULE EVERY 1 DAY
|
||||||
|
DO
|
||||||
|
INSERT INTO index_usage_log
|
||||||
|
SELECT
|
||||||
|
NOW() as check_time,
|
||||||
|
object_name,
|
||||||
|
index_name,
|
||||||
|
count_star as usage_count
|
||||||
|
FROM performance_schema.table_io_waits_summary_by_index_usage
|
||||||
|
WHERE object_schema = DATABASE()
|
||||||
|
AND count_star = 0; -- 未使用的索引
|
||||||
|
|
||||||
|
-- 3. 自动优化建议
|
||||||
|
DELIMITER //
|
||||||
|
CREATE PROCEDURE GenerateOptimizationSuggestions()
|
||||||
|
BEGIN
|
||||||
|
-- 查找可能需要索引的列
|
||||||
|
SELECT
|
||||||
|
'Consider adding index' as suggestion,
|
||||||
|
CONCAT('CREATE INDEX idx_', table_name, '_', column_name,
|
||||||
|
' ON ', table_name, '(', column_name, ');') as sql_statement
|
||||||
|
FROM (
|
||||||
|
SELECT 'orders' as table_name, 'region' as column_name
|
||||||
|
UNION ALL
|
||||||
|
SELECT 'products', 'category_id'
|
||||||
|
UNION ALL
|
||||||
|
SELECT 'access_logs', 'user_agent'
|
||||||
|
) potential_indexes;
|
||||||
|
|
||||||
|
-- 查找可能需要删除的索引
|
||||||
|
SELECT
|
||||||
|
'Consider dropping unused index' as suggestion,
|
||||||
|
CONCAT('DROP INDEX ', index_name, ' ON ', object_name, ';') as sql_statement
|
||||||
|
FROM performance_schema.table_io_waits_summary_by_index_usage
|
||||||
|
WHERE object_schema = DATABASE()
|
||||||
|
AND count_star = 0
|
||||||
|
AND index_name != 'PRIMARY';
|
||||||
|
END //
|
||||||
|
DELIMITER ;
|
||||||
|
|
||||||
|
-- 调用优化建议
|
||||||
|
CALL GenerateOptimizationSuggestions();
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**总结:**
|
||||||
|
|
||||||
|
RANGE 查询优化是数据库性能优化的重要组成部分,关键要点包括:
|
||||||
|
|
||||||
|
1. **理解原理**:掌握 RANGE 查询的执行机制和索引选择逻辑
|
||||||
|
2. **索引设计**:根据查询模式设计合适的单列和复合索引
|
||||||
|
3. **查询优化**:避免函数使用,保证数据类型匹配,合理使用索引提示
|
||||||
|
4. **分区策略**:对于大表,考虑使用分区表提高查询效率
|
||||||
|
5. **性能监控**:建立完善的监控体系,持续优化查询性能
|
||||||
|
6. **最佳实践**:遵循数据库设计和查询优化的最佳实践
|
||||||
|
|
||||||
|
通过系统的 RANGE 查询优化,可以显著提升数据库查询性能,改善用户体验。
|
||||||
532
MySQL_UNION语法使用文档.md
Normal file
532
MySQL_UNION语法使用文档.md
Normal file
@ -0,0 +1,532 @@
|
|||||||
|
# MySQL UNION 语法使用文档
|
||||||
|
|
||||||
|
## 目录
|
||||||
|
1. [UNION 基础语法](#union-基础语法)
|
||||||
|
2. [UNION 规则和限制](#union-规则和限制)
|
||||||
|
3. [示例数据准备](#示例数据准备)
|
||||||
|
4. [UNION 基础使用示例](#union-基础使用示例)
|
||||||
|
5. [UNION vs UNION ALL](#union-vs-union-all)
|
||||||
|
6. [复杂查询示例](#复杂查询示例)
|
||||||
|
7. [最佳实践和注意事项](#最佳实践和注意事项)
|
||||||
|
|
||||||
|
## UNION 基础语法
|
||||||
|
|
||||||
|
UNION 用于合并两个或多个 SELECT 语句的结果集。
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SELECT column1, column2, ... FROM table1
|
||||||
|
UNION [ALL]
|
||||||
|
SELECT column1, column2, ... FROM table2
|
||||||
|
[UNION [ALL]
|
||||||
|
SELECT column1, column2, ... FROM table3]
|
||||||
|
...
|
||||||
|
[ORDER BY column_name]
|
||||||
|
```
|
||||||
|
|
||||||
|
## UNION 规则和限制
|
||||||
|
|
||||||
|
1. **列数相同**:每个 SELECT 语句必须拥有相同数量的列
|
||||||
|
2. **数据类型兼容**:对应位置的列必须具有兼容的数据类型
|
||||||
|
3. **列名**:结果集使用第一个 SELECT 语句的列名
|
||||||
|
4. **去重**:UNION 默认去除重复记录,UNION ALL 保留所有记录
|
||||||
|
5. **ORDER BY**:只能在最后使用,对整个结果集排序
|
||||||
|
|
||||||
|
## 示例数据准备
|
||||||
|
|
||||||
|
### 创建示例表
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 创建员工表
|
||||||
|
CREATE TABLE employees (
|
||||||
|
id INT PRIMARY KEY,
|
||||||
|
name VARCHAR(50),
|
||||||
|
department VARCHAR(30),
|
||||||
|
salary DECIMAL(10,2),
|
||||||
|
city VARCHAR(30)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 创建前员工表
|
||||||
|
CREATE TABLE former_employees (
|
||||||
|
id INT PRIMARY KEY,
|
||||||
|
name VARCHAR(50),
|
||||||
|
department VARCHAR(30),
|
||||||
|
last_salary DECIMAL(10,2),
|
||||||
|
city VARCHAR(30),
|
||||||
|
leave_date DATE
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 创建管理层表
|
||||||
|
CREATE TABLE managers (
|
||||||
|
id INT PRIMARY KEY,
|
||||||
|
name VARCHAR(50),
|
||||||
|
level VARCHAR(20),
|
||||||
|
salary DECIMAL(10,2),
|
||||||
|
region VARCHAR(30)
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 插入示例数据
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 插入员工数据
|
||||||
|
INSERT INTO employees (id, name, department, salary, city) VALUES
|
||||||
|
(1, '张三', '技术部', 8000.00, '北京'),
|
||||||
|
(2, '李四', '销售部', 6000.00, '上海'),
|
||||||
|
(3, '王五', '技术部', 9000.00, '深圳'),
|
||||||
|
(4, '赵六', '人事部', 5500.00, '北京'),
|
||||||
|
(5, '钱七', '财务部', 7000.00, '广州'),
|
||||||
|
(6, '孙八', '技术部', 8500.00, '杭州'),
|
||||||
|
(7, '周九', '市场部', 6500.00, '成都');
|
||||||
|
|
||||||
|
-- 插入前员工数据
|
||||||
|
INSERT INTO former_employees (id, name, department, last_salary, city, leave_date) VALUES
|
||||||
|
(8, '吴十', '技术部', 7500.00, '北京', '2023-08-15'),
|
||||||
|
(9, '郑一', '销售部', 5800.00, '上海', '2023-09-20'),
|
||||||
|
(10, '王五', '技术部', 8000.00, '深圳', '2023-07-10'),
|
||||||
|
(11, '刘二', '人事部', 5200.00, '广州', '2023-10-05');
|
||||||
|
|
||||||
|
-- 插入管理层数据
|
||||||
|
INSERT INTO managers (id, name, level, salary, region) VALUES
|
||||||
|
(12, '陈总', '高级经理', 15000.00, '华北'),
|
||||||
|
(13, '林总', '部门经理', 12000.00, '华东'),
|
||||||
|
(14, '张三', '项目经理', 10000.00, '华南'),
|
||||||
|
(15, '黄总', '区域经理', 11000.00, '华西');
|
||||||
|
```
|
||||||
|
|
||||||
|
## UNION 基础使用示例
|
||||||
|
|
||||||
|
### 1. 简单合并查询
|
||||||
|
|
||||||
|
合并当前员工和前员工的姓名:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SELECT name FROM employees
|
||||||
|
UNION
|
||||||
|
SELECT name FROM former_employees;
|
||||||
|
```
|
||||||
|
|
||||||
|
**结果:**
|
||||||
|
```
|
||||||
|
+------+
|
||||||
|
| name |
|
||||||
|
+------+
|
||||||
|
| 张三 |
|
||||||
|
| 李四 |
|
||||||
|
| 王五 |
|
||||||
|
| 赵六 |
|
||||||
|
| 钱七 |
|
||||||
|
| 孙八 |
|
||||||
|
| 周九 |
|
||||||
|
| 吴十 |
|
||||||
|
| 郑一 |
|
||||||
|
| 刘二 |
|
||||||
|
+------+
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 多列合并查询
|
||||||
|
|
||||||
|
合并员工和管理层的姓名和薪资:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SELECT name, salary FROM employees
|
||||||
|
UNION
|
||||||
|
SELECT name, salary FROM managers
|
||||||
|
ORDER BY salary DESC;
|
||||||
|
```
|
||||||
|
|
||||||
|
**结果:**
|
||||||
|
```
|
||||||
|
+------+----------+
|
||||||
|
| name | salary |
|
||||||
|
+------+----------+
|
||||||
|
| 陈总 | 15000.00 |
|
||||||
|
| 林总 | 12000.00 |
|
||||||
|
| 黄总 | 11000.00 |
|
||||||
|
| 张三 | 10000.00 |
|
||||||
|
| 王五 | 9000.00 |
|
||||||
|
| 孙八 | 8500.00 |
|
||||||
|
| 张三 | 8000.00 |
|
||||||
|
| 钱七 | 7000.00 |
|
||||||
|
| 周九 | 6500.00 |
|
||||||
|
| 李四 | 6000.00 |
|
||||||
|
| 赵六 | 5500.00 |
|
||||||
|
+------+----------+
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 使用别名统一列名
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SELECT name, department AS work_area, salary FROM employees
|
||||||
|
UNION
|
||||||
|
SELECT name, region AS work_area, salary FROM managers
|
||||||
|
ORDER BY work_area;
|
||||||
|
```
|
||||||
|
|
||||||
|
**结果:**
|
||||||
|
```
|
||||||
|
+------+-----------+----------+
|
||||||
|
| name | work_area | salary |
|
||||||
|
+------+-----------+----------+
|
||||||
|
| 钱七 | 财务部 | 7000.00 |
|
||||||
|
| 陈总 | 华北 | 15000.00 |
|
||||||
|
| 林总 | 华东 | 12000.00 |
|
||||||
|
| 张三 | 华南 | 10000.00 |
|
||||||
|
| 黄总 | 华西 | 11000.00 |
|
||||||
|
| 赵六 | 人事部 | 5500.00 |
|
||||||
|
| 李四 | 销售部 | 6000.00 |
|
||||||
|
| 周九 | 市场部 | 6500.00 |
|
||||||
|
| 张三 | 技术部 | 8000.00 |
|
||||||
|
| 王五 | 技术部 | 9000.00 |
|
||||||
|
| 孙八 | 技术部 | 8500.00 |
|
||||||
|
+------+-----------+----------+
|
||||||
|
```
|
||||||
|
|
||||||
|
## UNION vs UNION ALL
|
||||||
|
|
||||||
|
### UNION(去重)
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SELECT city FROM employees
|
||||||
|
UNION
|
||||||
|
SELECT city FROM former_employees;
|
||||||
|
```
|
||||||
|
|
||||||
|
**结果:**
|
||||||
|
```
|
||||||
|
+------+
|
||||||
|
| city |
|
||||||
|
+------+
|
||||||
|
| 北京 |
|
||||||
|
| 上海 |
|
||||||
|
| 深圳 |
|
||||||
|
| 广州 |
|
||||||
|
| 杭州 |
|
||||||
|
| 成都 |
|
||||||
|
+------+
|
||||||
|
```
|
||||||
|
|
||||||
|
### UNION ALL(保留重复)
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SELECT city FROM employees
|
||||||
|
UNION ALL
|
||||||
|
SELECT city FROM former_employees;
|
||||||
|
```
|
||||||
|
|
||||||
|
**结果:**
|
||||||
|
```
|
||||||
|
+------+
|
||||||
|
| city |
|
||||||
|
+------+
|
||||||
|
| 北京 |
|
||||||
|
| 上海 |
|
||||||
|
| 深圳 |
|
||||||
|
| 北京 |
|
||||||
|
| 广州 |
|
||||||
|
| 杭州 |
|
||||||
|
| 成都 |
|
||||||
|
| 北京 |
|
||||||
|
| 上海 |
|
||||||
|
| 深圳 |
|
||||||
|
| 广州 |
|
||||||
|
+------+
|
||||||
|
```
|
||||||
|
|
||||||
|
### 性能对比示例
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 统计重复记录数量
|
||||||
|
SELECT
|
||||||
|
'UNION' AS query_type,
|
||||||
|
COUNT(*) AS record_count
|
||||||
|
FROM (
|
||||||
|
SELECT city FROM employees
|
||||||
|
UNION
|
||||||
|
SELECT city FROM former_employees
|
||||||
|
) AS union_result
|
||||||
|
|
||||||
|
UNION ALL
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
'UNION ALL' AS query_type,
|
||||||
|
COUNT(*) AS record_count
|
||||||
|
FROM (
|
||||||
|
SELECT city FROM employees
|
||||||
|
UNION ALL
|
||||||
|
SELECT city FROM former_employees
|
||||||
|
) AS union_all_result;
|
||||||
|
```
|
||||||
|
|
||||||
|
**结果:**
|
||||||
|
```
|
||||||
|
+------------+--------------+
|
||||||
|
| query_type | record_count |
|
||||||
|
+------------+--------------+
|
||||||
|
| UNION | 6 |
|
||||||
|
| UNION ALL | 11 |
|
||||||
|
+------------+--------------+
|
||||||
|
```
|
||||||
|
|
||||||
|
## 复杂查询示例
|
||||||
|
|
||||||
|
### 1. 条件过滤与UNION
|
||||||
|
|
||||||
|
查询高薪员工和所有管理层:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SELECT name, salary, '高薪员工' AS category
|
||||||
|
FROM employees
|
||||||
|
WHERE salary > 8000
|
||||||
|
|
||||||
|
UNION
|
||||||
|
|
||||||
|
SELECT name, salary, '管理层' AS category
|
||||||
|
FROM managers
|
||||||
|
|
||||||
|
ORDER BY salary DESC;
|
||||||
|
```
|
||||||
|
|
||||||
|
**结果:**
|
||||||
|
```
|
||||||
|
+------+----------+----------+
|
||||||
|
| name | salary | category |
|
||||||
|
+------+----------+----------+
|
||||||
|
| 陈总 | 15000.00 | 管理层 |
|
||||||
|
| 林总 | 12000.00 | 管理层 |
|
||||||
|
| 黄总 | 11000.00 | 管理层 |
|
||||||
|
| 张三 | 10000.00 | 管理层 |
|
||||||
|
| 王五 | 9000.00 | 高薪员工 |
|
||||||
|
| 孙八 | 8500.00 | 高薪员工 |
|
||||||
|
+------+----------+----------+
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 聚合查询与UNION
|
||||||
|
|
||||||
|
各部门薪资统计:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SELECT department AS name, AVG(salary) AS avg_salary, '部门平均' AS type
|
||||||
|
FROM employees
|
||||||
|
GROUP BY department
|
||||||
|
|
||||||
|
UNION ALL
|
||||||
|
|
||||||
|
SELECT '公司总体' AS name, AVG(salary) AS avg_salary, '总体平均' AS type
|
||||||
|
FROM employees
|
||||||
|
|
||||||
|
UNION ALL
|
||||||
|
|
||||||
|
SELECT '管理层' AS name, AVG(salary) AS avg_salary, '管理平均' AS type
|
||||||
|
FROM managers
|
||||||
|
|
||||||
|
ORDER BY avg_salary DESC;
|
||||||
|
```
|
||||||
|
|
||||||
|
**结果:**
|
||||||
|
```
|
||||||
|
+----------+-------------+----------+
|
||||||
|
| name | avg_salary | type |
|
||||||
|
+----------+-------------+----------+
|
||||||
|
| 管理层 | 12000.00000 | 管理平均 |
|
||||||
|
| 技术部 | 8500.00000 | 部门平均 |
|
||||||
|
| 公司总体 | 7142.85714 | 总体平均 |
|
||||||
|
| 财务部 | 7000.00000 | 部门平均 |
|
||||||
|
| 市场部 | 6500.00000 | 部门平均 |
|
||||||
|
| 销售部 | 6000.00000 | 部门平均 |
|
||||||
|
| 人事部 | 5500.00000 | 部门平均 |
|
||||||
|
+----------+-------------+----------+
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 多表复杂联合查询
|
||||||
|
|
||||||
|
创建完整的人员名册:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
department,
|
||||||
|
salary,
|
||||||
|
city,
|
||||||
|
'在职' AS status
|
||||||
|
FROM employees
|
||||||
|
|
||||||
|
UNION ALL
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
department,
|
||||||
|
last_salary AS salary,
|
||||||
|
city,
|
||||||
|
'离职' AS status
|
||||||
|
FROM former_employees
|
||||||
|
|
||||||
|
UNION ALL
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
'管理层' AS department,
|
||||||
|
salary,
|
||||||
|
region AS city,
|
||||||
|
'管理' AS status
|
||||||
|
FROM managers
|
||||||
|
|
||||||
|
ORDER BY status, salary DESC;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 使用子查询的UNION
|
||||||
|
|
||||||
|
查询每个城市的最高薪资员工:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SELECT name, city, salary, '当前最高薪' AS note
|
||||||
|
FROM employees e1
|
||||||
|
WHERE salary = (
|
||||||
|
SELECT MAX(salary)
|
||||||
|
FROM employees e2
|
||||||
|
WHERE e2.city = e1.city
|
||||||
|
)
|
||||||
|
|
||||||
|
UNION
|
||||||
|
|
||||||
|
SELECT name, city, last_salary AS salary, '前员工最高薪' AS note
|
||||||
|
FROM former_employees f1
|
||||||
|
WHERE last_salary = (
|
||||||
|
SELECT MAX(last_salary)
|
||||||
|
FROM former_employees f2
|
||||||
|
WHERE f2.city = f1.city
|
||||||
|
)
|
||||||
|
|
||||||
|
ORDER BY salary DESC;
|
||||||
|
```
|
||||||
|
|
||||||
|
## 最佳实践和注意事项
|
||||||
|
|
||||||
|
### 1. 性能优化建议
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- ❌ 避免:不必要的UNION,可以用OR替代
|
||||||
|
SELECT * FROM employees WHERE department = '技术部'
|
||||||
|
UNION
|
||||||
|
SELECT * FROM employees WHERE department = '销售部';
|
||||||
|
|
||||||
|
-- ✅ 推荐:使用OR条件
|
||||||
|
SELECT * FROM employees
|
||||||
|
WHERE department IN ('技术部', '销售部');
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 数据类型兼容性
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- ✅ 正确:确保数据类型兼容
|
||||||
|
SELECT id, name, salary FROM employees
|
||||||
|
UNION
|
||||||
|
SELECT id, name, CAST(last_salary AS DECIMAL(10,2)) FROM former_employees;
|
||||||
|
|
||||||
|
-- ❌ 错误:数据类型不兼容可能导致错误
|
||||||
|
SELECT id, name, salary FROM employees
|
||||||
|
UNION
|
||||||
|
SELECT id, name, leave_date FROM former_employees; -- leave_date是DATE类型
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 使用索引优化
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 为UNION查询中的过滤条件创建索引
|
||||||
|
CREATE INDEX idx_employees_dept ON employees(department);
|
||||||
|
CREATE INDEX idx_employees_salary ON employees(salary);
|
||||||
|
CREATE INDEX idx_former_employees_dept ON former_employees(department);
|
||||||
|
|
||||||
|
-- 优化后的查询
|
||||||
|
SELECT name, department FROM employees WHERE department = '技术部'
|
||||||
|
UNION
|
||||||
|
SELECT name, department FROM former_employees WHERE department = '技术部';
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 内存使用注意事项
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 对于大型结果集,考虑使用LIMIT
|
||||||
|
SELECT name, salary FROM employees
|
||||||
|
UNION ALL
|
||||||
|
SELECT name, salary FROM managers
|
||||||
|
ORDER BY salary DESC
|
||||||
|
LIMIT 10;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. 常见错误和解决方案
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- ❌ 错误:列数不匹配
|
||||||
|
SELECT name, department FROM employees
|
||||||
|
UNION
|
||||||
|
SELECT name FROM managers; -- 缺少一列
|
||||||
|
|
||||||
|
-- ✅ 修正:补齐列数
|
||||||
|
SELECT name, department FROM employees
|
||||||
|
UNION
|
||||||
|
SELECT name, level AS department FROM managers;
|
||||||
|
|
||||||
|
-- ❌ 错误:ORDER BY位置错误
|
||||||
|
SELECT name FROM employees ORDER BY name
|
||||||
|
UNION
|
||||||
|
SELECT name FROM managers ORDER BY name;
|
||||||
|
|
||||||
|
-- ✅ 修正:ORDER BY只能在最后
|
||||||
|
SELECT name FROM employees
|
||||||
|
UNION
|
||||||
|
SELECT name FROM managers
|
||||||
|
ORDER BY name;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6. 实用查询模式
|
||||||
|
|
||||||
|
#### 数据完整性检查
|
||||||
|
```sql
|
||||||
|
-- 检查是否有重复的员工记录
|
||||||
|
SELECT name, COUNT(*) as count
|
||||||
|
FROM (
|
||||||
|
SELECT name FROM employees
|
||||||
|
UNION ALL
|
||||||
|
SELECT name FROM former_employees
|
||||||
|
UNION ALL
|
||||||
|
SELECT name FROM managers
|
||||||
|
) AS all_people
|
||||||
|
GROUP BY name
|
||||||
|
HAVING COUNT(*) > 1;
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 数据对比分析
|
||||||
|
```sql
|
||||||
|
-- 对比不同表中的数据分布
|
||||||
|
SELECT
|
||||||
|
'当前员工' AS source,
|
||||||
|
department,
|
||||||
|
COUNT(*) AS count,
|
||||||
|
AVG(salary) AS avg_salary
|
||||||
|
FROM employees
|
||||||
|
GROUP BY department
|
||||||
|
|
||||||
|
UNION ALL
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
'前员工' AS source,
|
||||||
|
department,
|
||||||
|
COUNT(*) AS count,
|
||||||
|
AVG(last_salary) AS avg_salary
|
||||||
|
FROM former_employees
|
||||||
|
GROUP BY department
|
||||||
|
|
||||||
|
ORDER BY source, department;
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**总结:**
|
||||||
|
- UNION 是合并查询结果的强大工具
|
||||||
|
- 注意列数、数据类型的匹配
|
||||||
|
- 根据需求选择 UNION 或 UNION ALL
|
||||||
|
- 合理使用索引和LIMIT提升性能
|
||||||
|
- 避免不必要的复杂UNION操作
|
||||||
1301
MySQL常用SQL语法使用文档.md
Normal file
1301
MySQL常用SQL语法使用文档.md
Normal file
File diff suppressed because it is too large
Load Diff
327
Spring三级缓存机制学习笔记.md
Normal file
327
Spring三级缓存机制学习笔记.md
Normal file
@ -0,0 +1,327 @@
|
|||||||
|
# Spring三级缓存机制学习笔记
|
||||||
|
|
||||||
|
## 📚 概述
|
||||||
|
|
||||||
|
Spring三级缓存是Spring框架用来解决**单例Bean循环依赖**问题的核心机制。它通过三个不同的缓存Map来存储Bean在不同生命周期阶段的状态。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎯 学习目标
|
||||||
|
|
||||||
|
- [ ] 理解什么是循环依赖问题
|
||||||
|
- [ ] 掌握三级缓存的结构和作用
|
||||||
|
- [ ] 理解循环依赖的解决流程
|
||||||
|
- [ ] 学会分析相关源码
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔍 核心概念
|
||||||
|
|
||||||
|
### 什么是循环依赖?
|
||||||
|
|
||||||
|
当两个或多个Bean相互依赖时形成的依赖环:
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Component
|
||||||
|
public class ServiceA {
|
||||||
|
@Autowired
|
||||||
|
private ServiceB serviceB; // A依赖B
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class ServiceB {
|
||||||
|
@Autowired
|
||||||
|
private ServiceA serviceA; // B依赖A
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**问题**:如果没有特殊处理,会陷入无限循环创建对象的死循环。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🏗️ 三级缓存结构
|
||||||
|
|
||||||
|
Spring使用三个Map来管理Bean的不同状态:
|
||||||
|
|
||||||
|
```java
|
||||||
|
/** 一级缓存:完整的单例Bean */
|
||||||
|
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
|
||||||
|
|
||||||
|
/** 二级缓存:早期暴露的Bean对象 */
|
||||||
|
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
|
||||||
|
|
||||||
|
/** 三级缓存:Bean工厂对象 */
|
||||||
|
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 各级缓存详解
|
||||||
|
|
||||||
|
| 缓存级别 | 名称 | 作用 | 存储内容 |
|
||||||
|
|---------|------|------|---------|
|
||||||
|
| **一级缓存** | singletonObjects | 成品仓库 | 完全初始化完成的Bean |
|
||||||
|
| **二级缓存** | earlySingletonObjects | 半成品仓库 | 实例化但未完成依赖注入的Bean |
|
||||||
|
| **三级缓存** | singletonFactories | 工厂仓库 | Bean的ObjectFactory,支持AOP代理 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔄 循环依赖解决流程
|
||||||
|
|
||||||
|
以ServiceA ↔ ServiceB循环依赖为例:
|
||||||
|
|
||||||
|
### 流程图
|
||||||
|
```
|
||||||
|
1. 创建ServiceA
|
||||||
|
↓
|
||||||
|
2. ServiceA实例化 → 放入三级缓存
|
||||||
|
↓
|
||||||
|
3. ServiceA需要ServiceB → 开始创建ServiceB
|
||||||
|
↓
|
||||||
|
4. ServiceB实例化 → 放入三级缓存
|
||||||
|
↓
|
||||||
|
5. ServiceB需要ServiceA → 从缓存获取ServiceA早期引用
|
||||||
|
↓
|
||||||
|
6. ServiceA从三级缓存移至二级缓存
|
||||||
|
↓
|
||||||
|
7. ServiceB完成创建 → 放入一级缓存
|
||||||
|
↓
|
||||||
|
8. ServiceA完成创建 → 从二级缓存移至一级缓存
|
||||||
|
```
|
||||||
|
|
||||||
|
### 详细步骤
|
||||||
|
|
||||||
|
#### 第1步:开始创建ServiceA
|
||||||
|
```java
|
||||||
|
// 1. 实例化ServiceA(构造函数)
|
||||||
|
ServiceA serviceA = new ServiceA();
|
||||||
|
|
||||||
|
// 2. 将ServiceA的工厂放入三级缓存
|
||||||
|
singletonFactories.put("serviceA", () -> getEarlyBeanReference("serviceA", serviceA));
|
||||||
|
|
||||||
|
// 3. 开始属性注入,发现需要ServiceB
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 第2步:开始创建ServiceB
|
||||||
|
```java
|
||||||
|
// 1. 实例化ServiceB(构造函数)
|
||||||
|
ServiceB serviceB = new ServiceB();
|
||||||
|
|
||||||
|
// 2. 将ServiceB的工厂放入三级缓存
|
||||||
|
singletonFactories.put("serviceB", () -> getEarlyBeanReference("serviceB", serviceB));
|
||||||
|
|
||||||
|
// 3. 开始属性注入,发现需要ServiceA
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 第3步:获取ServiceA早期引用
|
||||||
|
```java
|
||||||
|
// 调用getSingleton("serviceA", true)
|
||||||
|
Object serviceA = singletonObjects.get("serviceA"); // 一级缓存:null
|
||||||
|
if (serviceA == null) {
|
||||||
|
serviceA = earlySingletonObjects.get("serviceA"); // 二级缓存:null
|
||||||
|
if (serviceA == null) {
|
||||||
|
ObjectFactory factory = singletonFactories.get("serviceA"); // 三级缓存:找到!
|
||||||
|
serviceA = factory.getObject(); // 获取早期引用
|
||||||
|
earlySingletonObjects.put("serviceA", serviceA); // 移到二级缓存
|
||||||
|
singletonFactories.remove("serviceA"); // 从三级缓存移除
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 第4步:完成ServiceB创建
|
||||||
|
```java
|
||||||
|
// ServiceB获得ServiceA的早期引用,完成属性注入
|
||||||
|
serviceB.setServiceA(serviceA);
|
||||||
|
|
||||||
|
// ServiceB初始化完成,放入一级缓存
|
||||||
|
singletonObjects.put("serviceB", serviceB);
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 第5步:完成ServiceA创建
|
||||||
|
```java
|
||||||
|
// ServiceA获得ServiceB的完整引用,完成属性注入
|
||||||
|
serviceA.setServiceB(serviceB);
|
||||||
|
|
||||||
|
// ServiceA初始化完成,从二级缓存移到一级缓存
|
||||||
|
singletonObjects.put("serviceA", serviceA);
|
||||||
|
earlySingletonObjects.remove("serviceA");
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 💡 核心源码分析
|
||||||
|
|
||||||
|
### getSingleton方法(核心逻辑)
|
||||||
|
|
||||||
|
```java
|
||||||
|
/**
|
||||||
|
* 从缓存中获取单例Bean
|
||||||
|
* @param beanName Bean名称
|
||||||
|
* @param allowEarlyReference 是否允许早期引用
|
||||||
|
*/
|
||||||
|
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
|
||||||
|
// 一级缓存获取完整Bean
|
||||||
|
Object singletonObject = this.singletonObjects.get(beanName);
|
||||||
|
|
||||||
|
// 如果一级缓存没有,且Bean正在创建中
|
||||||
|
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
|
||||||
|
synchronized (this.singletonObjects) {
|
||||||
|
// 二级缓存获取早期Bean
|
||||||
|
singletonObject = this.earlySingletonObjects.get(beanName);
|
||||||
|
|
||||||
|
// 如果二级缓存也没有,且允许早期引用
|
||||||
|
if (singletonObject == null && allowEarlyReference) {
|
||||||
|
// 三级缓存获取Bean工厂
|
||||||
|
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
|
||||||
|
if (singletonFactory != null) {
|
||||||
|
// 通过工厂创建早期引用
|
||||||
|
singletonObject = singletonFactory.getObject();
|
||||||
|
// 升级到二级缓存
|
||||||
|
this.earlySingletonObjects.put(beanName, singletonObject);
|
||||||
|
// 从三级缓存移除
|
||||||
|
this.singletonFactories.remove(beanName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return singletonObject;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### addSingletonFactory方法
|
||||||
|
|
||||||
|
```java
|
||||||
|
/**
|
||||||
|
* 添加单例工厂到三级缓存
|
||||||
|
*/
|
||||||
|
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
|
||||||
|
synchronized (this.singletonObjects) {
|
||||||
|
if (!this.singletonObjects.containsKey(beanName)) {
|
||||||
|
// 放入三级缓存
|
||||||
|
this.singletonFactories.put(beanName, singletonFactory);
|
||||||
|
// 从二级缓存移除
|
||||||
|
this.earlySingletonObjects.remove(beanName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🤔 常见问题
|
||||||
|
|
||||||
|
### Q1: 为什么需要三级缓存?二级不够吗?
|
||||||
|
|
||||||
|
**答:** 三级缓存主要是为了支持AOP代理:
|
||||||
|
- 如果Bean需要被代理(如@Transactional),不能直接暴露原始对象
|
||||||
|
- 通过ObjectFactory可以在需要时创建代理对象
|
||||||
|
- 保证循环依赖中获取的是正确的代理对象
|
||||||
|
|
||||||
|
### Q2: 构造器循环依赖能解决吗?
|
||||||
|
|
||||||
|
**答:** 不能!三级缓存只能解决属性注入的循环依赖:
|
||||||
|
```java
|
||||||
|
// ❌ 构造器循环依赖 - 无法解决
|
||||||
|
@Component
|
||||||
|
public class ServiceA {
|
||||||
|
public ServiceA(ServiceB serviceB) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ 属性注入循环依赖 - 可以解决
|
||||||
|
@Component
|
||||||
|
public class ServiceA {
|
||||||
|
@Autowired
|
||||||
|
private ServiceB serviceB;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Q3: Prototype作用域的循环依赖呢?
|
||||||
|
|
||||||
|
**答:** 不能解决!因为:
|
||||||
|
- Prototype每次都创建新实例
|
||||||
|
- 没有缓存机制
|
||||||
|
- 会导致无限递归创建
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎯 实践建议
|
||||||
|
|
||||||
|
### 1. 避免循环依赖的最佳实践
|
||||||
|
|
||||||
|
```java
|
||||||
|
// ❌ 避免:直接的双向依赖
|
||||||
|
@Service
|
||||||
|
public class UserService {
|
||||||
|
@Autowired private OrderService orderService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class OrderService {
|
||||||
|
@Autowired private UserService userService;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ 推荐:引入第三方服务
|
||||||
|
@Service
|
||||||
|
public class UserOrderService {
|
||||||
|
@Autowired private UserService userService;
|
||||||
|
@Autowired private OrderService orderService;
|
||||||
|
|
||||||
|
public void processUserOrder(Long userId, Long orderId) {
|
||||||
|
// 协调两个服务的交互
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 使用@Lazy注解延迟加载
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Service
|
||||||
|
public class ServiceA {
|
||||||
|
@Autowired
|
||||||
|
@Lazy // 延迟加载,避免循环依赖
|
||||||
|
private ServiceB serviceB;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 通过ApplicationContext获取Bean
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Service
|
||||||
|
public class ServiceA implements ApplicationContextAware {
|
||||||
|
private ApplicationContext applicationContext;
|
||||||
|
|
||||||
|
public void someMethod() {
|
||||||
|
// 运行时获取,避免循环依赖
|
||||||
|
ServiceB serviceB = applicationContext.getBean(ServiceB.class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📝 学习总结
|
||||||
|
|
||||||
|
### 关键点记忆
|
||||||
|
1. **三级缓存目的**:解决单例Bean的循环依赖
|
||||||
|
2. **核心思想**:提前暴露Bean的早期引用
|
||||||
|
3. **适用范围**:仅限于属性注入的循环依赖
|
||||||
|
4. **AOP支持**:通过ObjectFactory支持代理对象
|
||||||
|
|
||||||
|
### 面试重点
|
||||||
|
- 能够清晰描述三级缓存的结构和作用
|
||||||
|
- 能够完整说出循环依赖的解决流程
|
||||||
|
- 了解哪些情况下循环依赖无法解决
|
||||||
|
- 知道避免循环依赖的最佳实践
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📚 扩展阅读
|
||||||
|
|
||||||
|
- Spring官方文档:Bean的生命周期
|
||||||
|
- 源码位置:`DefaultSingletonBeanRegistry`类
|
||||||
|
- 相关概念:Spring AOP、Bean作用域
|
||||||
|
- 设计模式:工厂模式、单例模式
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**创建时间**:2025-06-25
|
||||||
|
**最后更新**:2025-06-25
|
||||||
|
**难度等级**:⭐⭐⭐⭐☆
|
||||||
808
Vue新手完全指南-基于你的项目.md
Normal file
808
Vue新手完全指南-基于你的项目.md
Normal file
@ -0,0 +1,808 @@
|
|||||||
|
# Vue 新手完全指南 - 基于你的待办事项项目
|
||||||
|
|
||||||
|
## 📚 目录
|
||||||
|
1. [Vue 基础概念](#vue-基础概念)
|
||||||
|
2. [项目结构理解](#项目结构理解)
|
||||||
|
3. [Composition API 详解](#composition-api-详解)
|
||||||
|
4. [响应式数据系统](#响应式数据系统)
|
||||||
|
5. [模板语法与指令](#模板语法与指令)
|
||||||
|
6. [事件处理](#事件处理)
|
||||||
|
7. [计算属性](#计算属性)
|
||||||
|
8. [CSS 与样式处理](#css-与样式处理)
|
||||||
|
9. [组件开发模式](#组件开发模式)
|
||||||
|
10. [构建与部署](#构建与部署)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Vue 基础概念
|
||||||
|
|
||||||
|
### 🎯 什么是Vue?
|
||||||
|
Vue.js 是一个**渐进式JavaScript框架**,用于构建用户界面。你的项目使用的是Vue 3,这是目前最新的版本。
|
||||||
|
|
||||||
|
**核心特点:**
|
||||||
|
- **响应式**:数据变化时,界面自动更新
|
||||||
|
- **组件化**:将复杂界面拆分成小组件
|
||||||
|
- **声明式**:描述"要什么结果",而不是"怎么做"
|
||||||
|
|
||||||
|
### 🏗️ Vue应用的工作原理
|
||||||
|
```javascript
|
||||||
|
// 你的项目入口文件:src/main.js
|
||||||
|
import { createApp } from 'vue'
|
||||||
|
import App from './App.vue'
|
||||||
|
|
||||||
|
createApp(App).mount('#app')
|
||||||
|
```
|
||||||
|
|
||||||
|
**解释:**
|
||||||
|
1. `createApp()` - 创建Vue应用实例
|
||||||
|
2. `App` - 根组件(你的整个应用)
|
||||||
|
3. `mount('#app')` - 挂载到HTML元素上
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 项目结构理解
|
||||||
|
|
||||||
|
### 📁 你的项目文件结构
|
||||||
|
```
|
||||||
|
todo-app/
|
||||||
|
├── src/
|
||||||
|
│ ├── App.vue # 主组件(你的核心代码)
|
||||||
|
│ ├── main.js # 应用入口
|
||||||
|
│ └── assets/ # 静态资源
|
||||||
|
├── public/ # 公共文件
|
||||||
|
├── package.json # 项目配置
|
||||||
|
└── vite.config.js # 构建工具配置
|
||||||
|
```
|
||||||
|
|
||||||
|
### 📄 单文件组件 (.vue文件)
|
||||||
|
你的`App.vue`是一个**单文件组件**,包含三个部分:
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<template>
|
||||||
|
<!-- HTML模板:界面结构 -->
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
// JavaScript逻辑:数据和方法
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
/* CSS样式:外观设计 */
|
||||||
|
</style>
|
||||||
|
```
|
||||||
|
|
||||||
|
**新手要点:**
|
||||||
|
- `<template>` = 你看到的页面内容
|
||||||
|
- `<script setup>` = 页面的功能逻辑
|
||||||
|
- `<style scoped>` = 只影响当前组件的样式
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Composition API 详解
|
||||||
|
|
||||||
|
### 🔥 什么是 `<script setup>`?
|
||||||
|
你的项目使用了Vue 3的**Composition API**,这是推荐的新写法:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
<script setup>
|
||||||
|
import { ref, computed } from 'vue'
|
||||||
|
|
||||||
|
// 这里写你的代码逻辑
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
**为什么用`<script setup>`?**
|
||||||
|
- ✅ 代码更简洁
|
||||||
|
- ✅ 性能更好
|
||||||
|
- ✅ TypeScript支持更好
|
||||||
|
- ✅ 这是Vue 3推荐写法
|
||||||
|
|
||||||
|
### 📦 导入Vue功能
|
||||||
|
```javascript
|
||||||
|
// 你的项目第2行
|
||||||
|
import { ref, computed } from 'vue'
|
||||||
|
```
|
||||||
|
|
||||||
|
**解释:**
|
||||||
|
- `ref` - 创建响应式数据
|
||||||
|
- `computed` - 创建计算属性
|
||||||
|
- 这些是Vue提供的功能函数
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 响应式数据系统
|
||||||
|
|
||||||
|
### 🎪 什么是响应式?
|
||||||
|
**响应式** = 数据变化时,页面自动更新
|
||||||
|
|
||||||
|
### 📝 ref() - 基本响应式数据
|
||||||
|
```javascript
|
||||||
|
// 你的项目代码示例
|
||||||
|
const newTodo = ref('') // 字符串
|
||||||
|
const todos = ref([...]) // 数组
|
||||||
|
const filter = ref('all') // 字符串
|
||||||
|
|
||||||
|
// 使用方式
|
||||||
|
console.log(newTodo.value) // 读取值
|
||||||
|
newTodo.value = '新的待办事项' // 修改值
|
||||||
|
```
|
||||||
|
|
||||||
|
**新手重点:**
|
||||||
|
- 📌 `ref()` 创建响应式数据
|
||||||
|
- 📌 在JavaScript中用 `.value` 访问
|
||||||
|
- 📌 在模板中直接用变量名(自动解包)
|
||||||
|
|
||||||
|
### 🔄 响应式数组操作
|
||||||
|
```javascript
|
||||||
|
// 你的项目中的数组操作
|
||||||
|
const todos = ref([
|
||||||
|
{ id: 1, text: '学习Vue基础语法', completed: false },
|
||||||
|
{ id: 2, text: '理解响应式数据', completed: true }
|
||||||
|
])
|
||||||
|
|
||||||
|
// 添加项目(第32-38行)
|
||||||
|
todos.value.push({
|
||||||
|
id: Date.now(),
|
||||||
|
text: newTodo.value.trim(),
|
||||||
|
completed: false
|
||||||
|
})
|
||||||
|
|
||||||
|
// 删除项目(第42-46行)
|
||||||
|
const index = todos.value.findIndex(todo => todo.id === id)
|
||||||
|
todos.value.splice(index, 1)
|
||||||
|
|
||||||
|
// 过滤项目(第56行)
|
||||||
|
todos.value = todos.value.filter(todo => !todo.completed)
|
||||||
|
```
|
||||||
|
|
||||||
|
**新手要点:**
|
||||||
|
- 🎯 数组方法:`push()`, `splice()`, `filter()`, `find()`, `findIndex()`
|
||||||
|
- 🎯 修改数组后,页面自动更新
|
||||||
|
- 🎯 `Date.now()` 生成唯一ID
|
||||||
|
|
||||||
|
### 🧮 computed() - 计算属性
|
||||||
|
```javascript
|
||||||
|
// 你的项目计算属性示例
|
||||||
|
const filteredTodos = computed(() => {
|
||||||
|
switch (filter.value) {
|
||||||
|
case 'active':
|
||||||
|
return todos.value.filter(todo => !todo.completed)
|
||||||
|
case 'completed':
|
||||||
|
return todos.value.filter(todo => todo.completed)
|
||||||
|
default:
|
||||||
|
return todos.value
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const remainingCount = computed(() => {
|
||||||
|
return todos.value.filter(todo => !todo.completed).length
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
**计算属性特点:**
|
||||||
|
- ✨ 基于其他数据计算得出
|
||||||
|
- ✨ 依赖数据变化时自动重新计算
|
||||||
|
- ✨ 有缓存,性能更好
|
||||||
|
- ✨ 在模板中像普通数据一样使用
|
||||||
|
|
||||||
|
**什么时候用计算属性?**
|
||||||
|
- 📊 数据统计(如:未完成数量)
|
||||||
|
- 🔍 数据过滤(如:按条件筛选)
|
||||||
|
- 🔄 数据转换(如:格式化显示)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 模板语法与指令
|
||||||
|
|
||||||
|
### 📝 插值表达式 `{{}}`
|
||||||
|
```html
|
||||||
|
<!-- 你的项目中的插值示例 -->
|
||||||
|
<h1>Vue 待办事项</h1> <!-- 静态文本 -->
|
||||||
|
<span>还有 {{ remainingCount }} 项未完成</span> <!-- 动态数据 -->
|
||||||
|
<span>全部 ({{ todos.length }})</span> <!-- 表达式计算 -->
|
||||||
|
```
|
||||||
|
|
||||||
|
**新手要点:**
|
||||||
|
- 🔤 `{{ }}` 用于显示数据
|
||||||
|
- 🔤 可以是变量、表达式、方法调用
|
||||||
|
- 🔤 自动转换为字符串显示
|
||||||
|
|
||||||
|
### 🎛️ v-model - 双向数据绑定
|
||||||
|
```html
|
||||||
|
<!-- 你的项目第66行 -->
|
||||||
|
<input v-model="newTodo" placeholder="添加新的待办事项..." />
|
||||||
|
|
||||||
|
<!-- 你的项目第110行 -->
|
||||||
|
<input type="checkbox" v-model="todo.completed" />
|
||||||
|
```
|
||||||
|
|
||||||
|
**双向绑定的含义:**
|
||||||
|
- 📥 输入框内容变化 → 数据自动更新
|
||||||
|
- 📤 数据变化 → 输入框内容自动更新
|
||||||
|
|
||||||
|
**常见用法:**
|
||||||
|
```html
|
||||||
|
<!-- 文本输入 -->
|
||||||
|
<input v-model="message" />
|
||||||
|
|
||||||
|
<!-- 复选框 -->
|
||||||
|
<input type="checkbox" v-model="checked" />
|
||||||
|
|
||||||
|
<!-- 单选框 -->
|
||||||
|
<input type="radio" value="A" v-model="picked" />
|
||||||
|
|
||||||
|
<!-- 选择框 -->
|
||||||
|
<select v-model="selected">
|
||||||
|
<option value="apple">苹果</option>
|
||||||
|
</select>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 👁️ v-show 和 v-if - 条件显示
|
||||||
|
```html
|
||||||
|
<!-- 你的项目中的条件显示 -->
|
||||||
|
<main v-show="todos.length"> <!-- 有数据时显示 -->
|
||||||
|
<button v-show="todos.length > remainingCount"> <!-- 有已完成项时显示 -->
|
||||||
|
<div v-show="!todos.length"> <!-- 无数据时显示 -->
|
||||||
|
```
|
||||||
|
|
||||||
|
**v-show vs v-if 的区别:**
|
||||||
|
- `v-show`: 通过CSS `display` 控制显示/隐藏
|
||||||
|
- `v-if`: 真正的条件渲染,元素会被创建/销毁
|
||||||
|
|
||||||
|
**什么时候用哪个?**
|
||||||
|
- 频繁切换 → 用 `v-show`
|
||||||
|
- 很少改变 → 用 `v-if`
|
||||||
|
|
||||||
|
### 🔄 v-for - 列表渲染
|
||||||
|
```html
|
||||||
|
<!-- 你的项目第102-107行 -->
|
||||||
|
<li v-for="todo in filteredTodos" :key="todo.id" class="todo-item">
|
||||||
|
<div class="todo-content">
|
||||||
|
<input type="checkbox" v-model="todo.completed" />
|
||||||
|
<span class="todo-text">{{ todo.text }}</span>
|
||||||
|
</div>
|
||||||
|
<button @click="removeTodo(todo.id)">删除</button>
|
||||||
|
</li>
|
||||||
|
```
|
||||||
|
|
||||||
|
**v-for 重点:**
|
||||||
|
- 📋 遍历数组或对象
|
||||||
|
- 🔑 `:key` 必须提供(性能优化)
|
||||||
|
- 🔑 key应该是唯一值(如ID)
|
||||||
|
|
||||||
|
**常见用法:**
|
||||||
|
```html
|
||||||
|
<!-- 遍历数组 -->
|
||||||
|
<li v-for="item in items" :key="item.id">{{ item.name }}</li>
|
||||||
|
|
||||||
|
<!-- 遍历对象 -->
|
||||||
|
<li v-for="(value, key) in object" :key="key">{{ key }}: {{ value }}</li>
|
||||||
|
|
||||||
|
<!-- 遍历数字 -->
|
||||||
|
<span v-for="n in 10" :key="n">{{ n }}</span>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 🎨 :class - 动态类绑定
|
||||||
|
```html
|
||||||
|
<!-- 你的项目中的类绑定示例 -->
|
||||||
|
<button :class="{ active: filter === 'all' }">全部</button>
|
||||||
|
<li :class="{ completed: todo.completed }" class="todo-item">
|
||||||
|
```
|
||||||
|
|
||||||
|
**类绑定语法:**
|
||||||
|
```html
|
||||||
|
<!-- 对象语法 -->
|
||||||
|
<div :class="{ active: isActive, disabled: isDisabled }"></div>
|
||||||
|
|
||||||
|
<!-- 数组语法 -->
|
||||||
|
<div :class="[activeClass, errorClass]"></div>
|
||||||
|
|
||||||
|
<!-- 混合使用 -->
|
||||||
|
<div class="static-class" :class="{ dynamic: isDynamic }"></div>
|
||||||
|
```
|
||||||
|
|
||||||
|
**新手理解:**
|
||||||
|
- 🎯 `{ active: filter === 'all' }`
|
||||||
|
- 如果 `filter === 'all'` 为真,添加 `active` 类
|
||||||
|
- 如果为假,不添加
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 事件处理
|
||||||
|
|
||||||
|
### 🖱️ @click - 点击事件
|
||||||
|
```html
|
||||||
|
<!-- 你的项目中的点击事件 -->
|
||||||
|
<button @click="addTodo">添加</button>
|
||||||
|
<button @click="filter = 'all'">全部</button>
|
||||||
|
<button @click="removeTodo(todo.id)">删除</button>
|
||||||
|
<button @click="clearCompleted">清除已完成</button>
|
||||||
|
```
|
||||||
|
|
||||||
|
**事件处理方式:**
|
||||||
|
```html
|
||||||
|
<!-- 调用方法 -->
|
||||||
|
<button @click="handleClick">点击我</button>
|
||||||
|
|
||||||
|
<!-- 直接执行代码 -->
|
||||||
|
<button @click="count++">计数+1</button>
|
||||||
|
|
||||||
|
<!-- 传递参数 -->
|
||||||
|
<button @click="handleClick(item.id)">删除</button>
|
||||||
|
|
||||||
|
<!-- 传递事件对象 -->
|
||||||
|
<button @click="handleClick($event)">获取事件</button>
|
||||||
|
```
|
||||||
|
|
||||||
|
### ⌨️ @keyup - 键盘事件
|
||||||
|
```html
|
||||||
|
<!-- 你的项目第67行 -->
|
||||||
|
<input @keyup.enter="addTodo" />
|
||||||
|
```
|
||||||
|
|
||||||
|
**键盘事件修饰符:**
|
||||||
|
```html
|
||||||
|
<input @keyup.enter="submit"> <!-- 回车键 -->
|
||||||
|
<input @keyup.esc="cancel"> <!-- ESC键 -->
|
||||||
|
<input @keyup.space="toggle"> <!-- 空格键 -->
|
||||||
|
<input @keyup.tab="nextField"> <!-- Tab键 -->
|
||||||
|
<input @keyup.ctrl.enter="save"> <!-- Ctrl+Enter -->
|
||||||
|
```
|
||||||
|
|
||||||
|
### 📤 @change - 值变化事件
|
||||||
|
```html
|
||||||
|
<!-- 你的项目第111行 -->
|
||||||
|
<input type="checkbox" @change="toggleTodo(todo.id)" />
|
||||||
|
```
|
||||||
|
|
||||||
|
**常见事件类型:**
|
||||||
|
- `@click` - 点击
|
||||||
|
- `@input` - 输入(实时)
|
||||||
|
- `@change` - 值改变(失去焦点时)
|
||||||
|
- `@submit` - 表单提交
|
||||||
|
- `@focus` - 获得焦点
|
||||||
|
- `@blur` - 失去焦点
|
||||||
|
|
||||||
|
### 🛠️ 方法定义
|
||||||
|
```javascript
|
||||||
|
// 你的项目中的方法定义
|
||||||
|
const addTodo = () => {
|
||||||
|
if (newTodo.value.trim()) { // 检查输入不为空
|
||||||
|
todos.value.push({ // 添加到数组
|
||||||
|
id: Date.now(), // 生成唯一ID
|
||||||
|
text: newTodo.value.trim(), // 去除前后空格
|
||||||
|
completed: false // 初始状态
|
||||||
|
})
|
||||||
|
newTodo.value = '' // 清空输入框
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const removeTodo = (id) => {
|
||||||
|
const index = todos.value.findIndex(todo => todo.id === id)
|
||||||
|
if (index > -1) {
|
||||||
|
todos.value.splice(index, 1) // 从数组中删除
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const toggleTodo = (id) => {
|
||||||
|
const todo = todos.value.find(todo => todo.id === id)
|
||||||
|
if (todo) {
|
||||||
|
todo.completed = !todo.completed // 切换完成状态
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**方法编写规范:**
|
||||||
|
- ✅ 使用箭头函数 `const method = () => {}`
|
||||||
|
- ✅ 参数验证(检查数据有效性)
|
||||||
|
- ✅ 防御性编程(检查对象是否存在)
|
||||||
|
- ✅ 一个方法做一件事
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 计算属性深入
|
||||||
|
|
||||||
|
### 🧩 计算属性 vs 方法的区别
|
||||||
|
|
||||||
|
**计算属性(推荐):**
|
||||||
|
```javascript
|
||||||
|
// 你的项目使用的计算属性
|
||||||
|
const remainingCount = computed(() => {
|
||||||
|
return todos.value.filter(todo => !todo.completed).length
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
**方法写法(不推荐):**
|
||||||
|
```javascript
|
||||||
|
const getRemainingCount = () => {
|
||||||
|
return todos.value.filter(todo => !todo.completed).length
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**为什么用计算属性?**
|
||||||
|
- 🚀 **有缓存** - 依赖不变时不重新计算
|
||||||
|
- 🚀 **自动更新** - 依赖变化时自动重算
|
||||||
|
- 🚀 **性能更好** - 避免重复计算
|
||||||
|
|
||||||
|
### 🔍 复杂计算属性示例
|
||||||
|
```javascript
|
||||||
|
// 你的项目中的过滤计算属性
|
||||||
|
const filteredTodos = computed(() => {
|
||||||
|
switch (filter.value) {
|
||||||
|
case 'active':
|
||||||
|
return todos.value.filter(todo => !todo.completed)
|
||||||
|
case 'completed':
|
||||||
|
return todos.value.filter(todo => todo.completed)
|
||||||
|
default:
|
||||||
|
return todos.value
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
**这个计算属性做了什么?**
|
||||||
|
1. 监听 `filter.value` 的变化
|
||||||
|
2. 根据筛选条件返回不同的待办列表
|
||||||
|
3. 当 `filter` 或 `todos` 变化时自动重新计算
|
||||||
|
|
||||||
|
### 💡 编写计算属性的技巧
|
||||||
|
```javascript
|
||||||
|
// ✅ 好的计算属性
|
||||||
|
const completedTodos = computed(() => {
|
||||||
|
return todos.value.filter(todo => todo.completed)
|
||||||
|
})
|
||||||
|
|
||||||
|
// ✅ 链式计算属性
|
||||||
|
const completedTodosCount = computed(() => {
|
||||||
|
return completedTodos.value.length
|
||||||
|
})
|
||||||
|
|
||||||
|
// ❌ 避免副作用
|
||||||
|
const badComputed = computed(() => {
|
||||||
|
// 不要在计算属性中修改数据
|
||||||
|
todos.value.push({...}) // 错误!
|
||||||
|
return someValue
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## CSS 与样式处理
|
||||||
|
|
||||||
|
### 🎨 Scoped CSS
|
||||||
|
```vue
|
||||||
|
<style scoped>
|
||||||
|
.todo-app {
|
||||||
|
max-width: 600px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
```
|
||||||
|
|
||||||
|
**`scoped` 的作用:**
|
||||||
|
- 🔒 样式只影响当前组件
|
||||||
|
- 🔒 不会污染全局样式
|
||||||
|
- 🔒 避免样式冲突
|
||||||
|
|
||||||
|
### 🎭 CSS类的条件应用
|
||||||
|
```html
|
||||||
|
<!-- 你的项目中的条件样式 -->
|
||||||
|
<button :class="{ active: filter === 'all' }">
|
||||||
|
<li :class="{ completed: todo.completed }" class="todo-item">
|
||||||
|
```
|
||||||
|
|
||||||
|
对应的CSS:
|
||||||
|
```css
|
||||||
|
.filters button.active {
|
||||||
|
background-color: #42b883;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.todo-item.completed {
|
||||||
|
opacity: 0.6;
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.completed .todo-text {
|
||||||
|
text-decoration: line-through;
|
||||||
|
color: #95a5a6;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 🌈 CSS变量和主题色
|
||||||
|
```css
|
||||||
|
/* 你的项目使用的主题色 */
|
||||||
|
.new-todo:focus {
|
||||||
|
border-color: #42b883; /* Vue绿色 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-btn {
|
||||||
|
background-color: #42b883;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filters button.active {
|
||||||
|
background-color: #42b883;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 📱 响应式设计
|
||||||
|
```css
|
||||||
|
/* 你的项目中没有用到,但建议添加 */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.todo-app {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-container {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 组件开发模式
|
||||||
|
|
||||||
|
### 🧱 单文件组件的优势
|
||||||
|
你的项目使用单文件组件(`.vue`文件):
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<template>
|
||||||
|
<!-- 模板 -->
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
<!-- 逻辑 -->
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
/* 样式 */
|
||||||
|
</style>
|
||||||
|
```
|
||||||
|
|
||||||
|
**优势:**
|
||||||
|
- 📦 **高内聚** - 相关代码在一起
|
||||||
|
- 🔧 **易维护** - 修改功能只需改一个文件
|
||||||
|
- 🚀 **易复用** - 整个组件可以在其他地方使用
|
||||||
|
|
||||||
|
### 🔄 组件拆分建议
|
||||||
|
你的项目目前是单组件,可以考虑拆分:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// 可以拆分成的组件
|
||||||
|
components/
|
||||||
|
├── TodoApp.vue // 主容器组件
|
||||||
|
├── TodoInput.vue // 输入框组件
|
||||||
|
├── TodoList.vue // 列表组件
|
||||||
|
├── TodoItem.vue // 单个待办项组件
|
||||||
|
├── TodoFilter.vue // 过滤器组件
|
||||||
|
└── TodoFooter.vue // 底部统计组件
|
||||||
|
```
|
||||||
|
|
||||||
|
**组件拆分原则:**
|
||||||
|
- 🎯 单一职责 - 一个组件做一件事
|
||||||
|
- 🎯 合理大小 - 不要太大也不要太小
|
||||||
|
- 🎯 易于理解 - 功能清晰明确
|
||||||
|
|
||||||
|
### 📡 组件通信(进阶)
|
||||||
|
当你拆分组件后,需要组件间通信:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// 父组件传数据给子组件 (Props)
|
||||||
|
<TodoItem :todo="todo" @toggle="toggleTodo" />
|
||||||
|
|
||||||
|
// 子组件触发父组件事件 (Emit)
|
||||||
|
const emit = defineEmits(['toggle'])
|
||||||
|
emit('toggle', todo.id)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 构建与部署
|
||||||
|
|
||||||
|
### ⚡ Vite 构建工具
|
||||||
|
你的项目使用 Vite 作为构建工具:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// vite.config.js
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [
|
||||||
|
vue(), // Vue支持
|
||||||
|
vueDevTools(), // 开发工具
|
||||||
|
],
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@': fileURLToPath(new URL('./src', import.meta.url))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
**Vite的优势:**
|
||||||
|
- ⚡ 启动速度快
|
||||||
|
- ⚡ 热更新快
|
||||||
|
- ⚡ 构建速度快
|
||||||
|
- ⚡ 支持现代JavaScript特性
|
||||||
|
|
||||||
|
### 🚀 开发和构建命令
|
||||||
|
```bash
|
||||||
|
# 开发模式(你正在使用的)
|
||||||
|
npm run dev
|
||||||
|
|
||||||
|
# 构建生产版本
|
||||||
|
npm run build
|
||||||
|
|
||||||
|
# 预览构建结果
|
||||||
|
npm run preview
|
||||||
|
```
|
||||||
|
|
||||||
|
### 📦 依赖管理
|
||||||
|
```json
|
||||||
|
// package.json 中的依赖
|
||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"vue": "^3.5.13" // Vue 3框架
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@vitejs/plugin-vue": "^5.2.3", // Vue插件
|
||||||
|
"vite": "^6.2.4", // 构建工具
|
||||||
|
"vite-plugin-vue-devtools": "^7.7.2" // 开发工具
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎓 学习路径和下一步
|
||||||
|
|
||||||
|
### 📚 基于你项目的学习顺序
|
||||||
|
|
||||||
|
**第1阶段:巩固基础(你已经在用)**
|
||||||
|
- ✅ 响应式数据 (`ref`)
|
||||||
|
- ✅ 计算属性 (`computed`)
|
||||||
|
- ✅ 事件处理 (`@click`, `@keyup`)
|
||||||
|
- ✅ 条件渲染 (`v-show`)
|
||||||
|
- ✅ 列表渲染 (`v-for`)
|
||||||
|
- ✅ 双向绑定 (`v-model`)
|
||||||
|
|
||||||
|
**第2阶段:进阶功能**
|
||||||
|
- 🔄 组件拆分和复用
|
||||||
|
- 🔄 组件通信 (Props & Emit)
|
||||||
|
- 🔄 生命周期钩子 (`onMounted`, `onUpdated`)
|
||||||
|
- 🔄 侦听器 (`watch`, `watchEffect`)
|
||||||
|
|
||||||
|
**第3阶段:实用技能**
|
||||||
|
- 🚀 路由管理 (Vue Router)
|
||||||
|
- 🚀 状态管理 (Pinia)
|
||||||
|
- 🚀 HTTP请求 (Axios)
|
||||||
|
- 🚀 UI组件库 (Element Plus, Ant Design Vue)
|
||||||
|
|
||||||
|
### 💪 练习建议
|
||||||
|
|
||||||
|
**基于你的项目扩展:**
|
||||||
|
1. **添加编辑功能** - 双击编辑待办事项
|
||||||
|
2. **添加优先级** - 给待办事项分优先级
|
||||||
|
3. **添加分类** - 工作、生活、学习分类
|
||||||
|
4. **添加截止日期** - 为待办事项设置期限
|
||||||
|
5. **数据持久化** - 使用 localStorage 保存数据
|
||||||
|
|
||||||
|
### 🐛 常见新手错误和解决方案
|
||||||
|
|
||||||
|
**1. 忘记 `.value`**
|
||||||
|
```javascript
|
||||||
|
// ❌ 错误
|
||||||
|
const count = ref(0)
|
||||||
|
console.log(count) // 输出: RefImpl对象
|
||||||
|
count++ // 不会工作
|
||||||
|
|
||||||
|
// ✅ 正确
|
||||||
|
console.log(count.value) // 输出: 0
|
||||||
|
count.value++ // 正确的修改方式
|
||||||
|
```
|
||||||
|
|
||||||
|
**2. 直接修改 props**
|
||||||
|
```javascript
|
||||||
|
// ❌ 错误 - 不要直接修改父组件传来的数据
|
||||||
|
props.todo.text = 'new text'
|
||||||
|
|
||||||
|
// ✅ 正确 - 通过事件通知父组件
|
||||||
|
emit('update-todo', { id: props.todo.id, text: 'new text' })
|
||||||
|
```
|
||||||
|
|
||||||
|
**3. 缺少 key 属性**
|
||||||
|
```html
|
||||||
|
<!-- ❌ 错误 -->
|
||||||
|
<li v-for="item in items">{{ item.name }}</li>
|
||||||
|
|
||||||
|
<!-- ✅ 正确 -->
|
||||||
|
<li v-for="item in items" :key="item.id">{{ item.name }}</li>
|
||||||
|
```
|
||||||
|
|
||||||
|
**4. 在计算属性中修改数据**
|
||||||
|
```javascript
|
||||||
|
// ❌ 错误
|
||||||
|
const processedData = computed(() => {
|
||||||
|
originalData.value.push(newItem) // 不要在计算属性中修改数据
|
||||||
|
return originalData.value
|
||||||
|
})
|
||||||
|
|
||||||
|
// ✅ 正确
|
||||||
|
const processedData = computed(() => {
|
||||||
|
return originalData.value.map(item => ({ ...item, processed: true }))
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### 🛠️ 调试技巧
|
||||||
|
|
||||||
|
**1. 使用 Vue DevTools**
|
||||||
|
在浏览器中安装 Vue DevTools 扩展,可以:
|
||||||
|
- 查看组件树
|
||||||
|
- 检查响应式数据
|
||||||
|
- 追踪事件
|
||||||
|
|
||||||
|
**2. 控制台调试**
|
||||||
|
```javascript
|
||||||
|
// 在方法中添加调试输出
|
||||||
|
const addTodo = () => {
|
||||||
|
console.log('添加前的todos:', todos.value)
|
||||||
|
console.log('输入的内容:', newTodo.value)
|
||||||
|
|
||||||
|
if (newTodo.value.trim()) {
|
||||||
|
// ... 你的逻辑
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('添加后的todos:', todos.value)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**3. 模板调试**
|
||||||
|
```html
|
||||||
|
<!-- 在模板中显示数据进行调试 -->
|
||||||
|
<div>{{ todos }}</div>
|
||||||
|
<div>当前筛选: {{ filter }}</div>
|
||||||
|
<div>计算属性结果: {{ filteredTodos }}</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 📖 推荐学习资源
|
||||||
|
|
||||||
|
**官方文档:**
|
||||||
|
- [Vue 3 官方文档](https://vuejs.org/)
|
||||||
|
- [Vue 3 中文文档](https://cn.vuejs.org/)
|
||||||
|
|
||||||
|
**实用教程:**
|
||||||
|
- Vue 3 快速上手
|
||||||
|
- Composition API 深入理解
|
||||||
|
- Vue 生态系统指南
|
||||||
|
|
||||||
|
**练习项目建议:**
|
||||||
|
1. 完善当前的待办事项应用
|
||||||
|
2. 制作一个简单的计算器
|
||||||
|
3. 开发一个天气查询应用
|
||||||
|
4. 创建一个个人博客系统
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎉 总结
|
||||||
|
|
||||||
|
恭喜你!你的待办事项项目已经使用了Vue 3的核心特性:
|
||||||
|
|
||||||
|
**✅ 你已经掌握的:**
|
||||||
|
- Composition API (`<script setup>`)
|
||||||
|
- 响应式数据 (`ref`)
|
||||||
|
- 计算属性 (`computed`)
|
||||||
|
- 事件处理 (`@click`, `@keyup`)
|
||||||
|
- 模板语法 (`v-model`, `v-for`, `v-show`)
|
||||||
|
- 动态类绑定 (`:class`)
|
||||||
|
|
||||||
|
**🚀 你的代码质量很高:**
|
||||||
|
- 使用了现代的Vue 3语法
|
||||||
|
- 数据流清晰合理
|
||||||
|
- 用户体验良好
|
||||||
|
- 代码结构规范
|
||||||
|
|
||||||
|
**📈 继续提升的方向:**
|
||||||
|
- 组件化开发
|
||||||
|
- 更复杂的状态管理
|
||||||
|
- 与后端API交互
|
||||||
|
- 更丰富的用户界面
|
||||||
|
|
||||||
|
继续保持学习的热情,Vue.js的世界还有很多精彩等你探索!🌟
|
||||||
187
profile-card-app/README.md
Normal file
187
profile-card-app/README.md
Normal file
@ -0,0 +1,187 @@
|
|||||||
|
# Vue个人资料卡片学习示例
|
||||||
|
|
||||||
|
这是一个专为Vue初学者设计的实践项目,通过创建一个个人资料卡片来学习Vue.js的核心概念。
|
||||||
|
|
||||||
|
## 🎯 学习目标
|
||||||
|
|
||||||
|
通过这个项目,您将学会:
|
||||||
|
- Vue.js基础语法和概念
|
||||||
|
- 数据绑定和插值表达式
|
||||||
|
- 事件处理和方法定义
|
||||||
|
- 条件渲染和列表渲染
|
||||||
|
- 双向数据绑定
|
||||||
|
- 计算属性和监听器
|
||||||
|
- 组件生命周期
|
||||||
|
|
||||||
|
## 🚀 快速开始
|
||||||
|
|
||||||
|
1. **打开项目**
|
||||||
|
```bash
|
||||||
|
cd profile-card-app
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **运行项目**
|
||||||
|
- 直接在浏览器中打开 `index.html` 文件
|
||||||
|
- 或者使用本地服务器(推荐):
|
||||||
|
```bash
|
||||||
|
# 如果安装了Python
|
||||||
|
python -m http.server 8000
|
||||||
|
|
||||||
|
# 如果安装了Node.js
|
||||||
|
npx serve .
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **访问应用**
|
||||||
|
- 直接打开:双击 `index.html`
|
||||||
|
- 本地服务器:访问 `http://localhost:8000`
|
||||||
|
|
||||||
|
## 📁 项目结构
|
||||||
|
|
||||||
|
```
|
||||||
|
profile-card-app/
|
||||||
|
├── index.html # 主HTML文件
|
||||||
|
├── app.js # Vue应用逻辑
|
||||||
|
├── style.css # 样式文件
|
||||||
|
└── README.md # 说明文档
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔧 功能特性
|
||||||
|
|
||||||
|
### 1. 个人资料展示
|
||||||
|
- 头像、姓名、职位显示
|
||||||
|
- 基本信息(年龄、城市、邮箱)
|
||||||
|
- 技能标签展示
|
||||||
|
- 个人简介
|
||||||
|
|
||||||
|
### 2. 交互功能
|
||||||
|
- **编辑资料**:点击"编辑资料"按钮可以修改个人信息
|
||||||
|
- **主题切换**:支持浅色/深色主题切换
|
||||||
|
- **数据持久化**:主题设置会保存到本地存储
|
||||||
|
|
||||||
|
### 3. 响应式设计
|
||||||
|
- 适配不同屏幕尺寸
|
||||||
|
- 移动端友好的界面
|
||||||
|
|
||||||
|
## 📚 Vue概念学习
|
||||||
|
|
||||||
|
### 1. 数据绑定 (Data Binding)
|
||||||
|
```html
|
||||||
|
<!-- 文本插值 -->
|
||||||
|
<h2>{{ profile.name }}</h2>
|
||||||
|
|
||||||
|
<!-- 属性绑定 -->
|
||||||
|
<img :src="profile.avatar" :alt="profile.name + '的头像'">
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 事件处理 (Event Handling)
|
||||||
|
```html
|
||||||
|
<!-- 点击事件 -->
|
||||||
|
<button @click="toggleEdit">编辑资料</button>
|
||||||
|
<button @click="toggleTheme">切换主题</button>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 条件渲染 (Conditional Rendering)
|
||||||
|
```html
|
||||||
|
<!-- v-if 条件显示 -->
|
||||||
|
<div v-if="isEditing" class="edit-form">
|
||||||
|
<!-- 编辑表单内容 -->
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 列表渲染 (List Rendering)
|
||||||
|
```html
|
||||||
|
<!-- v-for 循环显示 -->
|
||||||
|
<span v-for="skill in profile.skills" :key="skill" class="skill-tag">
|
||||||
|
{{ skill }}
|
||||||
|
</span>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. 双向绑定 (Two-way Binding)
|
||||||
|
```html
|
||||||
|
<!-- v-model 双向绑定 -->
|
||||||
|
<input v-model="editProfile.name" type="text">
|
||||||
|
<input v-model.number="editProfile.age" type="number">
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6. 计算属性 (Computed Properties)
|
||||||
|
```javascript
|
||||||
|
computed: {
|
||||||
|
ageGroup() {
|
||||||
|
if (this.profile.age < 25) return '年轻有为';
|
||||||
|
if (this.profile.age < 35) return '正值壮年';
|
||||||
|
return '经验丰富';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7. 监听器 (Watchers)
|
||||||
|
```javascript
|
||||||
|
watch: {
|
||||||
|
isDarkTheme(newValue) {
|
||||||
|
localStorage.setItem('isDarkTheme', JSON.stringify(newValue));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🎨 自定义建议
|
||||||
|
|
||||||
|
### 1. 修改个人信息
|
||||||
|
在 `app.js` 中找到 `profile` 对象,修改为您自己的信息:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
profile: {
|
||||||
|
name: '您的姓名',
|
||||||
|
title: '您的职位',
|
||||||
|
age: 您的年龄,
|
||||||
|
city: '您的城市',
|
||||||
|
email: '您的邮箱',
|
||||||
|
avatar: '您的头像URL',
|
||||||
|
bio: '您的个人简介',
|
||||||
|
skills: ['技能1', '技能2', '技能3']
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 添加新功能
|
||||||
|
尝试添加以下功能来练习Vue:
|
||||||
|
- 添加更多个人信息字段
|
||||||
|
- 实现头像上传功能
|
||||||
|
- 添加社交媒体链接
|
||||||
|
- 创建多个主题选项
|
||||||
|
- 添加动画效果
|
||||||
|
|
||||||
|
### 3. 样式定制
|
||||||
|
修改 `style.css` 中的颜色、字体、布局等:
|
||||||
|
- 更改主色调(搜索 `#667eea` 替换为您喜欢的颜色)
|
||||||
|
- 调整卡片大小和间距
|
||||||
|
- 添加新的动画效果
|
||||||
|
|
||||||
|
## 🐛 常见问题
|
||||||
|
|
||||||
|
### Q: 为什么我的修改没有生效?
|
||||||
|
A: 确保您保存了文件,并刷新了浏览器页面。
|
||||||
|
|
||||||
|
### Q: 如何查看Vue的调试信息?
|
||||||
|
A: 打开浏览器开发者工具(F12),查看Console标签页。
|
||||||
|
|
||||||
|
### Q: 可以添加更多页面吗?
|
||||||
|
A: 当然可以!您可以创建更多HTML文件,或者学习Vue Router来实现单页应用。
|
||||||
|
|
||||||
|
## 📖 下一步学习
|
||||||
|
|
||||||
|
完成这个项目后,建议您:
|
||||||
|
1. 学习Vue组件化开发
|
||||||
|
2. 了解Vue CLI和现代开发工具
|
||||||
|
3. 学习Vue Router(路由)
|
||||||
|
4. 学习Vuex/Pinia(状态管理)
|
||||||
|
5. 尝试构建更复杂的项目
|
||||||
|
|
||||||
|
## 🤝 贡献
|
||||||
|
|
||||||
|
如果您有改进建议或发现了问题,欢迎:
|
||||||
|
- 提出Issue
|
||||||
|
- 提交Pull Request
|
||||||
|
- 分享您的学习心得
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**祝您学习愉快!** 🎉
|
||||||
132
profile-card-app/app.js
Normal file
132
profile-card-app/app.js
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
// Vue 3 应用配置
|
||||||
|
const { createApp } = Vue;
|
||||||
|
|
||||||
|
createApp({
|
||||||
|
// 数据定义
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
title: 'Vue个人资料卡片学习示例',
|
||||||
|
isDarkTheme: false,
|
||||||
|
isEditing: false,
|
||||||
|
|
||||||
|
// 个人资料数据
|
||||||
|
profile: {
|
||||||
|
name: '张小明',
|
||||||
|
title: '前端开发工程师',
|
||||||
|
age: 25,
|
||||||
|
city: '北京',
|
||||||
|
email: 'zhangxiaoming@example.com',
|
||||||
|
avatar: 'https://via.placeholder.com/120x120/4A90E2/FFFFFF?text=张小明',
|
||||||
|
bio: '热爱编程,专注于Vue.js和现代前端技术。喜欢学习新技术,分享技术心得。',
|
||||||
|
skills: ['Vue.js', 'JavaScript', 'HTML/CSS', 'Node.js', 'Git']
|
||||||
|
},
|
||||||
|
|
||||||
|
// 编辑时的临时数据
|
||||||
|
editProfile: {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 方法定义
|
||||||
|
methods: {
|
||||||
|
// 切换编辑模式
|
||||||
|
toggleEdit() {
|
||||||
|
if (this.isEditing) {
|
||||||
|
// 保存编辑的数据
|
||||||
|
this.saveProfile();
|
||||||
|
} else {
|
||||||
|
// 进入编辑模式,复制当前数据到编辑对象
|
||||||
|
this.editProfile = { ...this.profile };
|
||||||
|
}
|
||||||
|
this.isEditing = !this.isEditing;
|
||||||
|
},
|
||||||
|
|
||||||
|
// 保存个人资料
|
||||||
|
saveProfile() {
|
||||||
|
// 验证数据
|
||||||
|
if (this.editProfile.name && this.editProfile.title) {
|
||||||
|
this.profile = { ...this.editProfile };
|
||||||
|
this.showMessage('个人资料已更新!', 'success');
|
||||||
|
} else {
|
||||||
|
this.showMessage('请填写姓名和职位!', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 切换主题
|
||||||
|
toggleTheme() {
|
||||||
|
this.isDarkTheme = !this.isDarkTheme;
|
||||||
|
this.showMessage(
|
||||||
|
`已切换到${this.isDarkTheme ? '深色' : '浅色'}主题`,
|
||||||
|
'info'
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
// 显示消息提示
|
||||||
|
showMessage(message, type = 'info') {
|
||||||
|
// 创建消息元素
|
||||||
|
const messageEl = document.createElement('div');
|
||||||
|
messageEl.className = `message message-${type}`;
|
||||||
|
messageEl.textContent = message;
|
||||||
|
|
||||||
|
// 添加到页面
|
||||||
|
document.body.appendChild(messageEl);
|
||||||
|
|
||||||
|
// 3秒后自动移除
|
||||||
|
setTimeout(() => {
|
||||||
|
if (messageEl.parentNode) {
|
||||||
|
messageEl.parentNode.removeChild(messageEl);
|
||||||
|
}
|
||||||
|
}, 3000);
|
||||||
|
},
|
||||||
|
|
||||||
|
// 重置资料到默认值
|
||||||
|
resetProfile() {
|
||||||
|
this.profile = {
|
||||||
|
name: '张小明',
|
||||||
|
title: '前端开发工程师',
|
||||||
|
age: 25,
|
||||||
|
city: '北京',
|
||||||
|
email: 'zhangxiaoming@example.com',
|
||||||
|
avatar: 'https://via.placeholder.com/120x120/4A90E2/FFFFFF?text=张小明',
|
||||||
|
bio: '热爱编程,专注于Vue.js和现代前端技术。喜欢学习新技术,分享技术心得。',
|
||||||
|
skills: ['Vue.js', 'JavaScript', 'HTML/CSS', 'Node.js', 'Git']
|
||||||
|
};
|
||||||
|
this.showMessage('已重置为默认资料', 'info');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 计算属性
|
||||||
|
computed: {
|
||||||
|
// 计算年龄段
|
||||||
|
ageGroup() {
|
||||||
|
if (this.profile.age < 25) return '年轻有为';
|
||||||
|
if (this.profile.age < 35) return '正值壮年';
|
||||||
|
return '经验丰富';
|
||||||
|
},
|
||||||
|
|
||||||
|
// 技能数量
|
||||||
|
skillCount() {
|
||||||
|
return this.profile.skills.length;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 生命周期钩子
|
||||||
|
mounted() {
|
||||||
|
console.log('Vue应用已挂载!');
|
||||||
|
this.showMessage('欢迎使用Vue个人资料卡片!', 'success');
|
||||||
|
|
||||||
|
// 从localStorage加载保存的主题设置
|
||||||
|
const savedTheme = localStorage.getItem('isDarkTheme');
|
||||||
|
if (savedTheme !== null) {
|
||||||
|
this.isDarkTheme = JSON.parse(savedTheme);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 监听器
|
||||||
|
watch: {
|
||||||
|
// 监听主题变化,保存到localStorage
|
||||||
|
isDarkTheme(newValue) {
|
||||||
|
localStorage.setItem('isDarkTheme', JSON.stringify(newValue));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).mount('#app');
|
||||||
102
profile-card-app/index.html
Normal file
102
profile-card-app/index.html
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Vue个人资料卡片 - 学习示例</title>
|
||||||
|
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
|
||||||
|
<link rel="stylesheet" href="style.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app">
|
||||||
|
<div class="container">
|
||||||
|
<h1>{{ title }}</h1>
|
||||||
|
|
||||||
|
<!-- 个人资料卡片 -->
|
||||||
|
<div class="profile-card" :class="{ 'dark-theme': isDarkTheme }">
|
||||||
|
<div class="profile-header">
|
||||||
|
<img :src="profile.avatar" :alt="profile.name + '的头像'" class="avatar">
|
||||||
|
<h2>{{ profile.name }}</h2>
|
||||||
|
<p class="title">{{ profile.title }}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="profile-info">
|
||||||
|
<div class="info-item">
|
||||||
|
<strong>年龄:</strong> {{ profile.age }}岁
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<strong>城市:</strong> {{ profile.city }}
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<strong>邮箱:</strong> {{ profile.email }}
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<strong>技能:</strong>
|
||||||
|
<span v-for="skill in profile.skills" :key="skill" class="skill-tag">
|
||||||
|
{{ skill }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="profile-bio">
|
||||||
|
<h3>个人简介</h3>
|
||||||
|
<p>{{ profile.bio }}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="profile-actions">
|
||||||
|
<button @click="toggleEdit" class="btn btn-primary">
|
||||||
|
{{ isEditing ? '保存' : '编辑资料' }}
|
||||||
|
</button>
|
||||||
|
<button @click="toggleTheme" class="btn btn-secondary">
|
||||||
|
{{ isDarkTheme ? '浅色主题' : '深色主题' }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 编辑表单 -->
|
||||||
|
<div v-if="isEditing" class="edit-form" :class="{ 'dark-theme': isDarkTheme }">
|
||||||
|
<h3>编辑个人资料</h3>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>姓名:</label>
|
||||||
|
<input v-model="editProfile.name" type="text" class="form-input">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>职位:</label>
|
||||||
|
<input v-model="editProfile.title" type="text" class="form-input">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>年龄:</label>
|
||||||
|
<input v-model.number="editProfile.age" type="number" class="form-input">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>城市:</label>
|
||||||
|
<input v-model="editProfile.city" type="text" class="form-input">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>邮箱:</label>
|
||||||
|
<input v-model="editProfile.email" type="email" class="form-input">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>个人简介:</label>
|
||||||
|
<textarea v-model="editProfile.bio" class="form-textarea"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 学习提示 -->
|
||||||
|
<div class="learning-tips" :class="{ 'dark-theme': isDarkTheme }">
|
||||||
|
<h3>🎓 Vue学习要点</h3>
|
||||||
|
<ul>
|
||||||
|
<li><strong>数据绑定:</strong> 使用 {{ '{{ }}' }} 显示数据</li>
|
||||||
|
<li><strong>属性绑定:</strong> 使用 :src, :class 等绑定属性</li>
|
||||||
|
<li><strong>事件处理:</strong> 使用 @click 处理点击事件</li>
|
||||||
|
<li><strong>条件渲染:</strong> 使用 v-if 控制元素显示</li>
|
||||||
|
<li><strong>列表渲染:</strong> 使用 v-for 循环显示数组</li>
|
||||||
|
<li><strong>双向绑定:</strong> 使用 v-model 绑定表单输入</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="app.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
333
profile-card-app/style.css
Normal file
333
profile-card-app/style.css
Normal file
@ -0,0 +1,333 @@
|
|||||||
|
/* 基础样式重置 */
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
min-height: 100vh;
|
||||||
|
padding: 20px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
text-align: center;
|
||||||
|
color: white;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
font-size: 2.5rem;
|
||||||
|
text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 个人资料卡片样式 */
|
||||||
|
.profile-card {
|
||||||
|
background: white;
|
||||||
|
border-radius: 20px;
|
||||||
|
padding: 30px;
|
||||||
|
box-shadow: 0 15px 35px rgba(0,0,0,0.1);
|
||||||
|
margin-bottom: 30px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-card:hover {
|
||||||
|
transform: translateY(-5px);
|
||||||
|
box-shadow: 0 20px 40px rgba(0,0,0,0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-header {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar {
|
||||||
|
width: 120px;
|
||||||
|
height: 120px;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 5px solid #667eea;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar:hover {
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-header h2 {
|
||||||
|
color: #333;
|
||||||
|
font-size: 2rem;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-header .title {
|
||||||
|
color: #667eea;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 个人信息样式 */
|
||||||
|
.profile-info {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||||
|
gap: 15px;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-item {
|
||||||
|
padding: 15px;
|
||||||
|
background: #f8f9fa;
|
||||||
|
border-radius: 10px;
|
||||||
|
border-left: 4px solid #667eea;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-item strong {
|
||||||
|
color: #333;
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skill-tag {
|
||||||
|
display: inline-block;
|
||||||
|
background: #667eea;
|
||||||
|
color: white;
|
||||||
|
padding: 4px 12px;
|
||||||
|
border-radius: 20px;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
margin: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 个人简介样式 */
|
||||||
|
.profile-bio {
|
||||||
|
background: #f8f9fa;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 10px;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-bio h3 {
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-bio p {
|
||||||
|
color: #666;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 按钮样式 */
|
||||||
|
.profile-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 15px;
|
||||||
|
justify-content: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
padding: 12px 24px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 25px;
|
||||||
|
font-size: 1rem;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
background: #667eea;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:hover {
|
||||||
|
background: #5a6fd8;
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary {
|
||||||
|
background: #6c757d;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary:hover {
|
||||||
|
background: #5a6268;
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 编辑表单样式 */
|
||||||
|
.edit-form {
|
||||||
|
background: white;
|
||||||
|
border-radius: 20px;
|
||||||
|
padding: 30px;
|
||||||
|
box-shadow: 0 15px 35px rgba(0,0,0,0.1);
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-form h3 {
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group label {
|
||||||
|
display: block;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-input, .form-textarea {
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px;
|
||||||
|
border: 2px solid #e9ecef;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 1rem;
|
||||||
|
transition: border-color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-input:focus, .form-textarea:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #667eea;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-textarea {
|
||||||
|
height: 100px;
|
||||||
|
resize: vertical;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 学习提示样式 */
|
||||||
|
.learning-tips {
|
||||||
|
background: white;
|
||||||
|
border-radius: 20px;
|
||||||
|
padding: 30px;
|
||||||
|
box-shadow: 0 15px 35px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.learning-tips h3 {
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.learning-tips ul {
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.learning-tips li {
|
||||||
|
padding: 10px 0;
|
||||||
|
border-bottom: 1px solid #e9ecef;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.learning-tips li:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.learning-tips strong {
|
||||||
|
color: #667eea;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 深色主题样式 */
|
||||||
|
.dark-theme {
|
||||||
|
background: #2c3e50 !important;
|
||||||
|
color: white !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark-theme h2,
|
||||||
|
.dark-theme h3,
|
||||||
|
.dark-theme strong,
|
||||||
|
.dark-theme label {
|
||||||
|
color: white !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark-theme .info-item,
|
||||||
|
.dark-theme .profile-bio {
|
||||||
|
background: #34495e !important;
|
||||||
|
color: white !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark-theme .form-input,
|
||||||
|
.dark-theme .form-textarea {
|
||||||
|
background: #34495e !important;
|
||||||
|
color: white !important;
|
||||||
|
border-color: #4a5f7a !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark-theme .learning-tips li {
|
||||||
|
color: #bdc3c7 !important;
|
||||||
|
border-color: #4a5f7a !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 消息提示样式 */
|
||||||
|
.message {
|
||||||
|
position: fixed;
|
||||||
|
top: 20px;
|
||||||
|
right: 20px;
|
||||||
|
padding: 15px 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
color: white;
|
||||||
|
font-weight: 500;
|
||||||
|
z-index: 1000;
|
||||||
|
animation: slideIn 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-success {
|
||||||
|
background: #28a745;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-error {
|
||||||
|
background: #dc3545;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-info {
|
||||||
|
background: #17a2b8;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideIn {
|
||||||
|
from {
|
||||||
|
transform: translateX(100%);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: translateX(0);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 响应式设计 */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.container {
|
||||||
|
padding: 0 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-card,
|
||||||
|
.edit-form,
|
||||||
|
.learning-tips {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-info {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-actions {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
width: 200px;
|
||||||
|
}
|
||||||
|
}
|
||||||
161
test-weather.html
Normal file
161
test-weather.html
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>测试天气卡片</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
background: #1e1e2e;
|
||||||
|
color: white;
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.controls {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-btn {
|
||||||
|
padding: 10px 20px;
|
||||||
|
margin: 5px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 5px;
|
||||||
|
background: #667eea;
|
||||||
|
color: white;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-btn.active {
|
||||||
|
background: #ff6b6b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.weather-container {
|
||||||
|
display: flex;
|
||||||
|
gap: 20px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.weather-card {
|
||||||
|
width: 200px;
|
||||||
|
height: 250px;
|
||||||
|
border-radius: 10px;
|
||||||
|
position: relative;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.weather-card.hidden {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.weather-card.visible {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wind-card { background: linear-gradient(135deg, #74b9ff, #0984e3); }
|
||||||
|
.rain-card { background: linear-gradient(135deg, #636e72, #2d3436); }
|
||||||
|
.sun-card { background: linear-gradient(135deg, #fdcb6e, #e17055); }
|
||||||
|
.snow-card { background: linear-gradient(135deg, #a8e6cf, #74b9ff); }
|
||||||
|
|
||||||
|
.card-content {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 20px;
|
||||||
|
left: 20px;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>测试天气卡片切换功能</h1>
|
||||||
|
|
||||||
|
<div class="controls">
|
||||||
|
<button class="control-btn active" data-weather="all">显示全部</button>
|
||||||
|
<button class="control-btn" data-weather="wind">风</button>
|
||||||
|
<button class="control-btn" data-weather="rain">雨</button>
|
||||||
|
<button class="control-btn" data-weather="sun">太阳</button>
|
||||||
|
<button class="control-btn" data-weather="snow">雪</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="weather-container">
|
||||||
|
<div class="weather-card wind-card visible" data-type="wind">
|
||||||
|
<div class="card-content">
|
||||||
|
<div>风</div>
|
||||||
|
<div>微风轻拂</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="weather-card rain-card visible" data-type="rain">
|
||||||
|
<div class="card-content">
|
||||||
|
<div>雨</div>
|
||||||
|
<div>细雨绵绵</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="weather-card sun-card visible" data-type="sun">
|
||||||
|
<div class="card-content">
|
||||||
|
<div>太阳</div>
|
||||||
|
<div>阳光明媚</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="weather-card snow-card visible" data-type="snow">
|
||||||
|
<div class="card-content">
|
||||||
|
<div>雪</div>
|
||||||
|
<div>雪花飞舞</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
console.log('开始初始化...');
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
console.log('DOM加载完成');
|
||||||
|
|
||||||
|
const controlBtns = document.querySelectorAll('.control-btn');
|
||||||
|
console.log('找到按钮数量:', controlBtns.length);
|
||||||
|
|
||||||
|
controlBtns.forEach((btn, index) => {
|
||||||
|
console.log(`绑定按钮 ${index}:`, btn.textContent, btn.dataset.weather);
|
||||||
|
btn.addEventListener('click', (e) => {
|
||||||
|
console.log('按钮被点击:', e.target.textContent, e.target.dataset.weather);
|
||||||
|
|
||||||
|
const weather = e.target.dataset.weather;
|
||||||
|
showWeather(weather);
|
||||||
|
updateActiveButton(e.target);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function showWeather(weatherType) {
|
||||||
|
console.log('切换到天气类型:', weatherType);
|
||||||
|
|
||||||
|
const weatherCards = document.querySelectorAll('.weather-card');
|
||||||
|
|
||||||
|
weatherCards.forEach(card => {
|
||||||
|
const cardType = card.dataset.type;
|
||||||
|
console.log('处理卡片:', cardType);
|
||||||
|
|
||||||
|
if (weatherType === 'all') {
|
||||||
|
card.classList.remove('hidden');
|
||||||
|
card.classList.add('visible');
|
||||||
|
} else if (cardType === weatherType) {
|
||||||
|
card.classList.remove('hidden');
|
||||||
|
card.classList.add('visible');
|
||||||
|
} else {
|
||||||
|
card.classList.add('hidden');
|
||||||
|
card.classList.remove('visible');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateActiveButton(activeBtn) {
|
||||||
|
const controlBtns = document.querySelectorAll('.control-btn');
|
||||||
|
controlBtns.forEach(btn => btn.classList.remove('active'));
|
||||||
|
activeBtn.classList.add('active');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
27
test.py
Normal file
27
test.py
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import hashlib
|
||||||
|
import uuid
|
||||||
|
import base64
|
||||||
|
from urllib.parse import urlencode
|
||||||
|
|
||||||
|
def base64url_encode(data):
|
||||||
|
return base64.urlsafe_b64encode(data).decode('utf-8').rstrip('=')
|
||||||
|
|
||||||
|
# 生成PKCE参数
|
||||||
|
code_verifier = base64url_encode(hashlib.sha256(uuid.uuid4().bytes).digest())
|
||||||
|
code_challenge = base64url_encode(hashlib.sha256(code_verifier.encode()).digest())
|
||||||
|
state = str(uuid.uuid4())
|
||||||
|
|
||||||
|
# 构造授权URL
|
||||||
|
params = {
|
||||||
|
"response_type": "code",
|
||||||
|
"code_challenge": code_challenge,
|
||||||
|
"code_challenge_method": "S256",
|
||||||
|
"client_id": "augment-vscode-extension", # 🔧 可能需要修改
|
||||||
|
"redirect_uri": "vscode://augment.vscode-augment/auth/result", # 🔧 可能需要修改
|
||||||
|
"state": state,
|
||||||
|
"scope": "java@fastmail.cn", # 🔧 可能需要修改
|
||||||
|
"prompt": "login"
|
||||||
|
}
|
||||||
|
|
||||||
|
auth_url = f"https://auth.augmentcode.com/authorize?{urlencode(params)}"
|
||||||
|
print(f"授权URL: {auth_url}")
|
||||||
30
todo-app/.gitignore
vendored
Normal file
30
todo-app/.gitignore
vendored
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
.DS_Store
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
coverage
|
||||||
|
*.local
|
||||||
|
|
||||||
|
/cypress/videos/
|
||||||
|
/cypress/screenshots/
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
|
|
||||||
|
*.tsbuildinfo
|
||||||
3
todo-app/.vscode/extensions.json
vendored
Normal file
3
todo-app/.vscode/extensions.json
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"recommendations": ["Vue.volar"]
|
||||||
|
}
|
||||||
29
todo-app/README.md
Normal file
29
todo-app/README.md
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
# todo-app
|
||||||
|
|
||||||
|
This template should help get you started developing with Vue 3 in Vite.
|
||||||
|
|
||||||
|
## Recommended IDE Setup
|
||||||
|
|
||||||
|
[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur).
|
||||||
|
|
||||||
|
## Customize configuration
|
||||||
|
|
||||||
|
See [Vite Configuration Reference](https://vite.dev/config/).
|
||||||
|
|
||||||
|
## Project Setup
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
### Compile and Hot-Reload for Development
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
### Compile and Minify for Production
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
129
todo-app/Vue学习指南-项目1.md
Normal file
129
todo-app/Vue学习指南-项目1.md
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
# Vue 学习指南 - 项目1: 待办事项应用
|
||||||
|
|
||||||
|
## 🎯 学习目标
|
||||||
|
通过这个待办事项应用,您将学习到Vue的核心概念:
|
||||||
|
- 响应式数据 (Reactivity)
|
||||||
|
- 数据绑定 (Data Binding)
|
||||||
|
- 事件处理 (Event Handling)
|
||||||
|
- 条件渲染 (Conditional Rendering)
|
||||||
|
- 列表渲染 (List Rendering)
|
||||||
|
- 计算属性 (Computed Properties)
|
||||||
|
|
||||||
|
## 📚 核心概念详解
|
||||||
|
|
||||||
|
### 1. 响应式数据 (ref)
|
||||||
|
```javascript
|
||||||
|
const newTodo = ref('')
|
||||||
|
const todos = ref([...])
|
||||||
|
```
|
||||||
|
- `ref()` 创建响应式数据
|
||||||
|
- 当数据改变时,UI会自动更新
|
||||||
|
- 在模板中直接使用,在script中需要`.value`
|
||||||
|
|
||||||
|
### 2. 数据绑定 (v-model)
|
||||||
|
```html
|
||||||
|
<input v-model="newTodo" />
|
||||||
|
```
|
||||||
|
- 双向数据绑定
|
||||||
|
- 输入框的值与数据同步
|
||||||
|
- 用户输入会自动更新数据
|
||||||
|
|
||||||
|
### 3. 事件处理 (@click, @keyup)
|
||||||
|
```html
|
||||||
|
<button @click="addTodo">添加</button>
|
||||||
|
<input @keyup.enter="addTodo" />
|
||||||
|
```
|
||||||
|
- `@click` 处理点击事件
|
||||||
|
- `@keyup.enter` 处理回车键
|
||||||
|
- 事件修饰符简化常见操作
|
||||||
|
|
||||||
|
### 4. 条件渲染 (v-show, v-if)
|
||||||
|
```html
|
||||||
|
<main v-show="todos.length">
|
||||||
|
<div v-show="!todos.length">
|
||||||
|
```
|
||||||
|
- `v-show` 控制元素显示/隐藏
|
||||||
|
- `v-if` 条件性渲染元素
|
||||||
|
- 根据数据状态动态显示内容
|
||||||
|
|
||||||
|
### 5. 列表渲染 (v-for)
|
||||||
|
```html
|
||||||
|
<li v-for="todo in filteredTodos" :key="todo.id">
|
||||||
|
```
|
||||||
|
- `v-for` 循环渲染列表
|
||||||
|
- `:key` 提供唯一标识符
|
||||||
|
- 提高渲染性能
|
||||||
|
|
||||||
|
### 6. 计算属性 (computed)
|
||||||
|
```javascript
|
||||||
|
const filteredTodos = computed(() => {
|
||||||
|
// 根据filter状态过滤todos
|
||||||
|
})
|
||||||
|
```
|
||||||
|
- 基于其他数据计算得出
|
||||||
|
- 自动缓存,依赖不变时不重新计算
|
||||||
|
- 响应式更新
|
||||||
|
|
||||||
|
## 🔧 功能实现分析
|
||||||
|
|
||||||
|
### 添加待办事项
|
||||||
|
1. 用户在输入框输入内容
|
||||||
|
2. 按回车或点击添加按钮
|
||||||
|
3. 验证输入不为空
|
||||||
|
4. 创建新的todo对象
|
||||||
|
5. 添加到todos数组
|
||||||
|
6. 清空输入框
|
||||||
|
|
||||||
|
### 切换完成状态
|
||||||
|
1. 点击复选框
|
||||||
|
2. 触发toggleTodo方法
|
||||||
|
3. 找到对应的todo
|
||||||
|
4. 切换completed状态
|
||||||
|
5. UI自动更新样式
|
||||||
|
|
||||||
|
### 过滤显示
|
||||||
|
1. 点击过滤按钮
|
||||||
|
2. 更新filter状态
|
||||||
|
3. 计算属性重新计算
|
||||||
|
4. 列表自动更新显示
|
||||||
|
|
||||||
|
## 🎨 样式特点
|
||||||
|
- 使用scoped样式避免污染
|
||||||
|
- 响应式设计
|
||||||
|
- 悬停效果和过渡动画
|
||||||
|
- 清晰的视觉层次
|
||||||
|
|
||||||
|
## 🚀 实践练习
|
||||||
|
|
||||||
|
### 基础练习
|
||||||
|
1. 修改默认的待办事项
|
||||||
|
2. 改变应用的颜色主题
|
||||||
|
3. 添加更多的过滤选项
|
||||||
|
|
||||||
|
### 进阶练习
|
||||||
|
1. 添加编辑功能
|
||||||
|
2. 实现拖拽排序
|
||||||
|
3. 添加优先级标记
|
||||||
|
4. 实现本地存储
|
||||||
|
|
||||||
|
## 📖 Vue语法总结
|
||||||
|
|
||||||
|
| 语法 | 用途 | 示例 |
|
||||||
|
|------|------|------|
|
||||||
|
| `{{ }}` | 文本插值 | `{{ todo.text }}` |
|
||||||
|
| `v-model` | 双向绑定 | `v-model="newTodo"` |
|
||||||
|
| `@event` | 事件监听 | `@click="addTodo"` |
|
||||||
|
| `v-for` | 列表渲染 | `v-for="todo in todos"` |
|
||||||
|
| `v-show` | 条件显示 | `v-show="todos.length"` |
|
||||||
|
| `:class` | 动态类名 | `:class="{ active: filter === 'all' }"` |
|
||||||
|
| `ref()` | 响应式数据 | `const count = ref(0)` |
|
||||||
|
| `computed()` | 计算属性 | `const total = computed(() => ...)` |
|
||||||
|
|
||||||
|
## 🎯 下一步学习
|
||||||
|
完成这个项目后,您已经掌握了Vue的基础概念。接下来我们将学习:
|
||||||
|
- 组件化开发
|
||||||
|
- 组件通信 (props, emit)
|
||||||
|
- 插槽 (slots)
|
||||||
|
- 生命周期钩子
|
||||||
|
|
||||||
|
准备好继续学习项目2了吗?
|
||||||
13
todo-app/index.html
Normal file
13
todo-app/index.html
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<link rel="icon" href="/favicon.ico">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Vite App</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="module" src="/src/main.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
8
todo-app/jsconfig.json
Normal file
8
todo-app/jsconfig.json
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./src/*"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"exclude": ["node_modules", "dist"]
|
||||||
|
}
|
||||||
2611
todo-app/package-lock.json
generated
Normal file
2611
todo-app/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
19
todo-app/package.json
Normal file
19
todo-app/package.json
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"name": "todo-app",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "vite build",
|
||||||
|
"preview": "vite preview"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"vue": "^3.5.13"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@vitejs/plugin-vue": "^5.2.3",
|
||||||
|
"vite": "^6.2.4",
|
||||||
|
"vite-plugin-vue-devtools": "^7.7.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
todo-app/public/favicon.ico
Normal file
BIN
todo-app/public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
324
todo-app/src/App.vue
Normal file
324
todo-app/src/App.vue
Normal file
@ -0,0 +1,324 @@
|
|||||||
|
<script setup>
|
||||||
|
import { ref, computed } from 'vue'
|
||||||
|
|
||||||
|
// 响应式数据
|
||||||
|
const newTodo = ref('')
|
||||||
|
const todos = ref([
|
||||||
|
{ id: 1, text: '学习Vue基础语法', completed: false },
|
||||||
|
{ id: 2, text: '理解响应式数据', completed: true },
|
||||||
|
{ id: 3, text: '掌握事件处理', completed: false }
|
||||||
|
])
|
||||||
|
const filter = ref('all') // all, active, completed
|
||||||
|
|
||||||
|
// 计算属性
|
||||||
|
const filteredTodos = computed(() => {
|
||||||
|
switch (filter.value) {
|
||||||
|
case 'active':
|
||||||
|
return todos.value.filter(todo => !todo.completed)
|
||||||
|
case 'completed':
|
||||||
|
return todos.value.filter(todo => todo.completed)
|
||||||
|
default:
|
||||||
|
return todos.value
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const remainingCount = computed(() => {
|
||||||
|
return todos.value.filter(todo => !todo.completed).length
|
||||||
|
})
|
||||||
|
|
||||||
|
// 方法
|
||||||
|
const addTodo = () => {
|
||||||
|
if (newTodo.value.trim()) {
|
||||||
|
todos.value.push({
|
||||||
|
id: Date.now(),
|
||||||
|
text: newTodo.value.trim(),
|
||||||
|
completed: false
|
||||||
|
})
|
||||||
|
newTodo.value = ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const removeTodo = (id) => {
|
||||||
|
const index = todos.value.findIndex(todo => todo.id === id)
|
||||||
|
if (index > -1) {
|
||||||
|
todos.value.splice(index, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const toggleTodo = (id) => {
|
||||||
|
const todo = todos.value.find(todo => todo.id === id)
|
||||||
|
if (todo) {
|
||||||
|
todo.completed = !todo.completed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const clearCompleted = () => {
|
||||||
|
todos.value = todos.value.filter(todo => !todo.completed)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="todo-app">
|
||||||
|
<header class="header">
|
||||||
|
<h1>Vue 待办事项</h1>
|
||||||
|
<div class="input-container">
|
||||||
|
<input
|
||||||
|
v-model="newTodo"
|
||||||
|
@keyup.enter="addTodo"
|
||||||
|
class="new-todo"
|
||||||
|
placeholder="添加新的待办事项..."
|
||||||
|
autofocus
|
||||||
|
/>
|
||||||
|
<button @click="addTodo" class="add-btn">添加</button>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main class="main" v-show="todos.length">
|
||||||
|
<!-- 过滤器 -->
|
||||||
|
<div class="filters">
|
||||||
|
<button
|
||||||
|
@click="filter = 'all'"
|
||||||
|
:class="{ active: filter === 'all' }"
|
||||||
|
>
|
||||||
|
全部 ({{ todos.length }})
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
@click="filter = 'active'"
|
||||||
|
:class="{ active: filter === 'active' }"
|
||||||
|
>
|
||||||
|
未完成 ({{ remainingCount }})
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
@click="filter = 'completed'"
|
||||||
|
:class="{ active: filter === 'completed' }"
|
||||||
|
>
|
||||||
|
已完成 ({{ todos.length - remainingCount }})
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 待办事项列表 -->
|
||||||
|
<ul class="todo-list">
|
||||||
|
<li
|
||||||
|
v-for="todo in filteredTodos"
|
||||||
|
:key="todo.id"
|
||||||
|
:class="{ completed: todo.completed }"
|
||||||
|
class="todo-item"
|
||||||
|
>
|
||||||
|
<div class="todo-content">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
v-model="todo.completed"
|
||||||
|
@change="toggleTodo(todo.id)"
|
||||||
|
class="toggle"
|
||||||
|
/>
|
||||||
|
<span class="todo-text">{{ todo.text }}</span>
|
||||||
|
</div>
|
||||||
|
<button @click="removeTodo(todo.id)" class="destroy">删除</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<!-- 底部操作 -->
|
||||||
|
<footer class="footer">
|
||||||
|
<span class="todo-count">
|
||||||
|
还有 {{ remainingCount }} 项未完成
|
||||||
|
</span>
|
||||||
|
<button
|
||||||
|
v-show="todos.length > remainingCount"
|
||||||
|
@click="clearCompleted"
|
||||||
|
class="clear-completed"
|
||||||
|
>
|
||||||
|
清除已完成
|
||||||
|
</button>
|
||||||
|
</footer>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<!-- 空状态 -->
|
||||||
|
<div v-show="!todos.length" class="empty-state">
|
||||||
|
<p>还没有待办事项,添加一个开始吧!</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.todo-app {
|
||||||
|
max-width: 600px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 20px;
|
||||||
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header h1 {
|
||||||
|
color: #2c3e50;
|
||||||
|
font-size: 2.5rem;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-container {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
max-width: 400px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.new-todo {
|
||||||
|
flex: 1;
|
||||||
|
padding: 12px 16px;
|
||||||
|
font-size: 16px;
|
||||||
|
border: 2px solid #e0e0e0;
|
||||||
|
border-radius: 8px;
|
||||||
|
outline: none;
|
||||||
|
transition: border-color 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.new-todo:focus {
|
||||||
|
border-color: #42b883;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-btn {
|
||||||
|
padding: 12px 24px;
|
||||||
|
background-color: #42b883;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 16px;
|
||||||
|
transition: background-color 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-btn:hover {
|
||||||
|
background-color: #369870;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filters {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 10px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filters button {
|
||||||
|
padding: 8px 16px;
|
||||||
|
border: 2px solid #e0e0e0;
|
||||||
|
background: white;
|
||||||
|
border-radius: 20px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filters button.active {
|
||||||
|
background-color: #42b883;
|
||||||
|
color: white;
|
||||||
|
border-color: #42b883;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filters button:hover:not(.active) {
|
||||||
|
border-color: #42b883;
|
||||||
|
}
|
||||||
|
|
||||||
|
.todo-list {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.todo-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 15px;
|
||||||
|
border: 1px solid #e0e0e0;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
background: white;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.todo-item:hover {
|
||||||
|
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.todo-item.completed {
|
||||||
|
opacity: 0.6;
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.todo-content {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle {
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.todo-text {
|
||||||
|
font-size: 16px;
|
||||||
|
color: #2c3e50;
|
||||||
|
}
|
||||||
|
|
||||||
|
.completed .todo-text {
|
||||||
|
text-decoration: line-through;
|
||||||
|
color: #95a5a6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.destroy {
|
||||||
|
padding: 6px 12px;
|
||||||
|
background-color: #e74c3c;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
transition: background-color 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.destroy:hover {
|
||||||
|
background-color: #c0392b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: 20px;
|
||||||
|
padding: 15px;
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.todo-count {
|
||||||
|
color: #7f8c8d;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.clear-completed {
|
||||||
|
padding: 8px 16px;
|
||||||
|
background-color: #e74c3c;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
transition: background-color 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.clear-completed:hover {
|
||||||
|
background-color: #c0392b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-state {
|
||||||
|
text-align: center;
|
||||||
|
padding: 40px;
|
||||||
|
color: #7f8c8d;
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
86
todo-app/src/assets/base.css
Normal file
86
todo-app/src/assets/base.css
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
/* color palette from <https://github.com/vuejs/theme> */
|
||||||
|
:root {
|
||||||
|
--vt-c-white: #ffffff;
|
||||||
|
--vt-c-white-soft: #f8f8f8;
|
||||||
|
--vt-c-white-mute: #f2f2f2;
|
||||||
|
|
||||||
|
--vt-c-black: #181818;
|
||||||
|
--vt-c-black-soft: #222222;
|
||||||
|
--vt-c-black-mute: #282828;
|
||||||
|
|
||||||
|
--vt-c-indigo: #2c3e50;
|
||||||
|
|
||||||
|
--vt-c-divider-light-1: rgba(60, 60, 60, 0.29);
|
||||||
|
--vt-c-divider-light-2: rgba(60, 60, 60, 0.12);
|
||||||
|
--vt-c-divider-dark-1: rgba(84, 84, 84, 0.65);
|
||||||
|
--vt-c-divider-dark-2: rgba(84, 84, 84, 0.48);
|
||||||
|
|
||||||
|
--vt-c-text-light-1: var(--vt-c-indigo);
|
||||||
|
--vt-c-text-light-2: rgba(60, 60, 60, 0.66);
|
||||||
|
--vt-c-text-dark-1: var(--vt-c-white);
|
||||||
|
--vt-c-text-dark-2: rgba(235, 235, 235, 0.64);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* semantic color variables for this project */
|
||||||
|
:root {
|
||||||
|
--color-background: var(--vt-c-white);
|
||||||
|
--color-background-soft: var(--vt-c-white-soft);
|
||||||
|
--color-background-mute: var(--vt-c-white-mute);
|
||||||
|
|
||||||
|
--color-border: var(--vt-c-divider-light-2);
|
||||||
|
--color-border-hover: var(--vt-c-divider-light-1);
|
||||||
|
|
||||||
|
--color-heading: var(--vt-c-text-light-1);
|
||||||
|
--color-text: var(--vt-c-text-light-1);
|
||||||
|
|
||||||
|
--section-gap: 160px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
:root {
|
||||||
|
--color-background: var(--vt-c-black);
|
||||||
|
--color-background-soft: var(--vt-c-black-soft);
|
||||||
|
--color-background-mute: var(--vt-c-black-mute);
|
||||||
|
|
||||||
|
--color-border: var(--vt-c-divider-dark-2);
|
||||||
|
--color-border-hover: var(--vt-c-divider-dark-1);
|
||||||
|
|
||||||
|
--color-heading: var(--vt-c-text-dark-1);
|
||||||
|
--color-text: var(--vt-c-text-dark-2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*,
|
||||||
|
*::before,
|
||||||
|
*::after {
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin: 0;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
min-height: 100vh;
|
||||||
|
color: var(--color-text);
|
||||||
|
background: var(--color-background);
|
||||||
|
transition:
|
||||||
|
color 0.5s,
|
||||||
|
background-color 0.5s;
|
||||||
|
line-height: 1.6;
|
||||||
|
font-family:
|
||||||
|
Inter,
|
||||||
|
-apple-system,
|
||||||
|
BlinkMacSystemFont,
|
||||||
|
'Segoe UI',
|
||||||
|
Roboto,
|
||||||
|
Oxygen,
|
||||||
|
Ubuntu,
|
||||||
|
Cantarell,
|
||||||
|
'Fira Sans',
|
||||||
|
'Droid Sans',
|
||||||
|
'Helvetica Neue',
|
||||||
|
sans-serif;
|
||||||
|
font-size: 15px;
|
||||||
|
text-rendering: optimizeLegibility;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
}
|
||||||
1
todo-app/src/assets/logo.svg
Normal file
1
todo-app/src/assets/logo.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 261.76 226.69"><path d="M161.096.001l-30.225 52.351L100.647.001H-.005l130.877 226.688L261.749.001z" fill="#41b883"/><path d="M161.096.001l-30.225 52.351L100.647.001H52.346l78.526 136.01L209.398.001z" fill="#34495e"/></svg>
|
||||||
|
After Width: | Height: | Size: 276 B |
35
todo-app/src/assets/main.css
Normal file
35
todo-app/src/assets/main.css
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
@import './base.css';
|
||||||
|
|
||||||
|
#app {
|
||||||
|
max-width: 1280px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 2rem;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
a,
|
||||||
|
.green {
|
||||||
|
text-decoration: none;
|
||||||
|
color: hsla(160, 100%, 37%, 1);
|
||||||
|
transition: 0.4s;
|
||||||
|
padding: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (hover: hover) {
|
||||||
|
a:hover {
|
||||||
|
background-color: hsla(160, 100%, 37%, 0.2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 1024px) {
|
||||||
|
body {
|
||||||
|
display: flex;
|
||||||
|
place-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#app {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
padding: 0 2rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
44
todo-app/src/components/HelloWorld.vue
Normal file
44
todo-app/src/components/HelloWorld.vue
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
<script setup>
|
||||||
|
defineProps({
|
||||||
|
msg: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="greetings">
|
||||||
|
<h1 class="green">{{ msg }}</h1>
|
||||||
|
<h3>
|
||||||
|
You’ve successfully created a project with
|
||||||
|
<a href="https://vite.dev/" target="_blank" rel="noopener">Vite</a> +
|
||||||
|
<a href="https://vuejs.org/" target="_blank" rel="noopener">Vue 3</a>.
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
h1 {
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 2.6rem;
|
||||||
|
position: relative;
|
||||||
|
top: -10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.greetings h1,
|
||||||
|
.greetings h3 {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 1024px) {
|
||||||
|
.greetings h1,
|
||||||
|
.greetings h3 {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
94
todo-app/src/components/TheWelcome.vue
Normal file
94
todo-app/src/components/TheWelcome.vue
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
<script setup>
|
||||||
|
import WelcomeItem from './WelcomeItem.vue'
|
||||||
|
import DocumentationIcon from './icons/IconDocumentation.vue'
|
||||||
|
import ToolingIcon from './icons/IconTooling.vue'
|
||||||
|
import EcosystemIcon from './icons/IconEcosystem.vue'
|
||||||
|
import CommunityIcon from './icons/IconCommunity.vue'
|
||||||
|
import SupportIcon from './icons/IconSupport.vue'
|
||||||
|
|
||||||
|
const openReadmeInEditor = () => fetch('/__open-in-editor?file=README.md')
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<WelcomeItem>
|
||||||
|
<template #icon>
|
||||||
|
<DocumentationIcon />
|
||||||
|
</template>
|
||||||
|
<template #heading>Documentation</template>
|
||||||
|
|
||||||
|
Vue’s
|
||||||
|
<a href="https://vuejs.org/" target="_blank" rel="noopener">official documentation</a>
|
||||||
|
provides you with all information you need to get started.
|
||||||
|
</WelcomeItem>
|
||||||
|
|
||||||
|
<WelcomeItem>
|
||||||
|
<template #icon>
|
||||||
|
<ToolingIcon />
|
||||||
|
</template>
|
||||||
|
<template #heading>Tooling</template>
|
||||||
|
|
||||||
|
This project is served and bundled with
|
||||||
|
<a href="https://vite.dev/guide/features.html" target="_blank" rel="noopener">Vite</a>. The
|
||||||
|
recommended IDE setup is
|
||||||
|
<a href="https://code.visualstudio.com/" target="_blank" rel="noopener">VSCode</a>
|
||||||
|
+
|
||||||
|
<a href="https://github.com/vuejs/language-tools" target="_blank" rel="noopener">Vue - Official</a>. If
|
||||||
|
you need to test your components and web pages, check out
|
||||||
|
<a href="https://vitest.dev/" target="_blank" rel="noopener">Vitest</a>
|
||||||
|
and
|
||||||
|
<a href="https://www.cypress.io/" target="_blank" rel="noopener">Cypress</a>
|
||||||
|
/
|
||||||
|
<a href="https://playwright.dev/" target="_blank" rel="noopener">Playwright</a>.
|
||||||
|
|
||||||
|
<br />
|
||||||
|
|
||||||
|
More instructions are available in
|
||||||
|
<a href="javascript:void(0)" @click="openReadmeInEditor"><code>README.md</code></a
|
||||||
|
>.
|
||||||
|
</WelcomeItem>
|
||||||
|
|
||||||
|
<WelcomeItem>
|
||||||
|
<template #icon>
|
||||||
|
<EcosystemIcon />
|
||||||
|
</template>
|
||||||
|
<template #heading>Ecosystem</template>
|
||||||
|
|
||||||
|
Get official tools and libraries for your project:
|
||||||
|
<a href="https://pinia.vuejs.org/" target="_blank" rel="noopener">Pinia</a>,
|
||||||
|
<a href="https://router.vuejs.org/" target="_blank" rel="noopener">Vue Router</a>,
|
||||||
|
<a href="https://test-utils.vuejs.org/" target="_blank" rel="noopener">Vue Test Utils</a>, and
|
||||||
|
<a href="https://github.com/vuejs/devtools" target="_blank" rel="noopener">Vue Dev Tools</a>. If
|
||||||
|
you need more resources, we suggest paying
|
||||||
|
<a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">Awesome Vue</a>
|
||||||
|
a visit.
|
||||||
|
</WelcomeItem>
|
||||||
|
|
||||||
|
<WelcomeItem>
|
||||||
|
<template #icon>
|
||||||
|
<CommunityIcon />
|
||||||
|
</template>
|
||||||
|
<template #heading>Community</template>
|
||||||
|
|
||||||
|
Got stuck? Ask your question on
|
||||||
|
<a href="https://chat.vuejs.org" target="_blank" rel="noopener">Vue Land</a>
|
||||||
|
(our official Discord server), or
|
||||||
|
<a href="https://stackoverflow.com/questions/tagged/vue.js" target="_blank" rel="noopener"
|
||||||
|
>StackOverflow</a
|
||||||
|
>. You should also follow the official
|
||||||
|
<a href="https://bsky.app/profile/vuejs.org" target="_blank" rel="noopener">@vuejs.org</a>
|
||||||
|
Bluesky account or the
|
||||||
|
<a href="https://x.com/vuejs" target="_blank" rel="noopener">@vuejs</a>
|
||||||
|
X account for latest news in the Vue world.
|
||||||
|
</WelcomeItem>
|
||||||
|
|
||||||
|
<WelcomeItem>
|
||||||
|
<template #icon>
|
||||||
|
<SupportIcon />
|
||||||
|
</template>
|
||||||
|
<template #heading>Support Vue</template>
|
||||||
|
|
||||||
|
As an independent project, Vue relies on community backing for its sustainability. You can help
|
||||||
|
us by
|
||||||
|
<a href="https://vuejs.org/sponsor/" target="_blank" rel="noopener">becoming a sponsor</a>.
|
||||||
|
</WelcomeItem>
|
||||||
|
</template>
|
||||||
87
todo-app/src/components/WelcomeItem.vue
Normal file
87
todo-app/src/components/WelcomeItem.vue
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
<template>
|
||||||
|
<div class="item">
|
||||||
|
<i>
|
||||||
|
<slot name="icon"></slot>
|
||||||
|
</i>
|
||||||
|
<div class="details">
|
||||||
|
<h3>
|
||||||
|
<slot name="heading"></slot>
|
||||||
|
</h3>
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.item {
|
||||||
|
margin-top: 2rem;
|
||||||
|
display: flex;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.details {
|
||||||
|
flex: 1;
|
||||||
|
margin-left: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
i {
|
||||||
|
display: flex;
|
||||||
|
place-items: center;
|
||||||
|
place-content: center;
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
|
||||||
|
color: var(--color-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-bottom: 0.4rem;
|
||||||
|
color: var(--color-heading);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 1024px) {
|
||||||
|
.item {
|
||||||
|
margin-top: 0;
|
||||||
|
padding: 0.4rem 0 1rem calc(var(--section-gap) / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
i {
|
||||||
|
top: calc(50% - 25px);
|
||||||
|
left: -26px;
|
||||||
|
position: absolute;
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
background: var(--color-background);
|
||||||
|
border-radius: 8px;
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item:before {
|
||||||
|
content: ' ';
|
||||||
|
border-left: 1px solid var(--color-border);
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
bottom: calc(50% + 25px);
|
||||||
|
height: calc(50% - 25px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.item:after {
|
||||||
|
content: ' ';
|
||||||
|
border-left: 1px solid var(--color-border);
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: calc(50% + 25px);
|
||||||
|
height: calc(50% - 25px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.item:first-of-type:before {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item:last-of-type:after {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
7
todo-app/src/components/icons/IconCommunity.vue
Normal file
7
todo-app/src/components/icons/IconCommunity.vue
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<template>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor">
|
||||||
|
<path
|
||||||
|
d="M15 4a1 1 0 1 0 0 2V4zm0 11v-1a1 1 0 0 0-1 1h1zm0 4l-.707.707A1 1 0 0 0 16 19h-1zm-4-4l.707-.707A1 1 0 0 0 11 14v1zm-4.707-1.293a1 1 0 0 0-1.414 1.414l1.414-1.414zm-.707.707l-.707-.707.707.707zM9 11v-1a1 1 0 0 0-.707.293L9 11zm-4 0h1a1 1 0 0 0-1-1v1zm0 4H4a1 1 0 0 0 1.707.707L5 15zm10-9h2V4h-2v2zm2 0a1 1 0 0 1 1 1h2a3 3 0 0 0-3-3v2zm1 1v6h2V7h-2zm0 6a1 1 0 0 1-1 1v2a3 3 0 0 0 3-3h-2zm-1 1h-2v2h2v-2zm-3 1v4h2v-4h-2zm1.707 3.293l-4-4-1.414 1.414 4 4 1.414-1.414zM11 14H7v2h4v-2zm-4 0c-.276 0-.525-.111-.707-.293l-1.414 1.414C5.42 15.663 6.172 16 7 16v-2zm-.707 1.121l3.414-3.414-1.414-1.414-3.414 3.414 1.414 1.414zM9 12h4v-2H9v2zm4 0a3 3 0 0 0 3-3h-2a1 1 0 0 1-1 1v2zm3-3V3h-2v6h2zm0-6a3 3 0 0 0-3-3v2a1 1 0 0 1 1 1h2zm-3-3H3v2h10V0zM3 0a3 3 0 0 0-3 3h2a1 1 0 0 1 1-1V0zM0 3v6h2V3H0zm0 6a3 3 0 0 0 3 3v-2a1 1 0 0 1-1-1H0zm3 3h2v-2H3v2zm1-1v4h2v-4H4zm1.707 4.707l.586-.586-1.414-1.414-.586.586 1.414 1.414z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</template>
|
||||||
7
todo-app/src/components/icons/IconDocumentation.vue
Normal file
7
todo-app/src/components/icons/IconDocumentation.vue
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<template>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="17" fill="currentColor">
|
||||||
|
<path
|
||||||
|
d="M11 2.253a1 1 0 1 0-2 0h2zm-2 13a1 1 0 1 0 2 0H9zm.447-12.167a1 1 0 1 0 1.107-1.666L9.447 3.086zM1 2.253L.447 1.42A1 1 0 0 0 0 2.253h1zm0 13H0a1 1 0 0 0 1.553.833L1 15.253zm8.447.833a1 1 0 1 0 1.107-1.666l-1.107 1.666zm0-14.666a1 1 0 1 0 1.107 1.666L9.447 1.42zM19 2.253h1a1 1 0 0 0-.447-.833L19 2.253zm0 13l-.553.833A1 1 0 0 0 20 15.253h-1zm-9.553-.833a1 1 0 1 0 1.107 1.666L9.447 14.42zM9 2.253v13h2v-13H9zm1.553-.833C9.203.523 7.42 0 5.5 0v2c1.572 0 2.961.431 3.947 1.086l1.107-1.666zM5.5 0C3.58 0 1.797.523.447 1.42l1.107 1.666C2.539 2.431 3.928 2 5.5 2V0zM0 2.253v13h2v-13H0zm1.553 13.833C2.539 15.431 3.928 15 5.5 15v-2c-1.92 0-3.703.523-5.053 1.42l1.107 1.666zM5.5 15c1.572 0 2.961.431 3.947 1.086l1.107-1.666C9.203 13.523 7.42 13 5.5 13v2zm5.053-11.914C11.539 2.431 12.928 2 14.5 2V0c-1.92 0-3.703.523-5.053 1.42l1.107 1.666zM14.5 2c1.573 0 2.961.431 3.947 1.086l1.107-1.666C18.203.523 16.421 0 14.5 0v2zm3.5.253v13h2v-13h-2zm1.553 12.167C18.203 13.523 16.421 13 14.5 13v2c1.573 0 2.961.431 3.947 1.086l1.107-1.666zM14.5 13c-1.92 0-3.703.523-5.053 1.42l1.107 1.666C11.539 15.431 12.928 15 14.5 15v-2z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</template>
|
||||||
7
todo-app/src/components/icons/IconEcosystem.vue
Normal file
7
todo-app/src/components/icons/IconEcosystem.vue
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<template>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="20" fill="currentColor">
|
||||||
|
<path
|
||||||
|
d="M11.447 8.894a1 1 0 1 0-.894-1.789l.894 1.789zm-2.894-.789a1 1 0 1 0 .894 1.789l-.894-1.789zm0 1.789a1 1 0 1 0 .894-1.789l-.894 1.789zM7.447 7.106a1 1 0 1 0-.894 1.789l.894-1.789zM10 9a1 1 0 1 0-2 0h2zm-2 2.5a1 1 0 1 0 2 0H8zm9.447-5.606a1 1 0 1 0-.894-1.789l.894 1.789zm-2.894-.789a1 1 0 1 0 .894 1.789l-.894-1.789zm2 .789a1 1 0 1 0 .894-1.789l-.894 1.789zm-1.106-2.789a1 1 0 1 0-.894 1.789l.894-1.789zM18 5a1 1 0 1 0-2 0h2zm-2 2.5a1 1 0 1 0 2 0h-2zm-5.447-4.606a1 1 0 1 0 .894-1.789l-.894 1.789zM9 1l.447-.894a1 1 0 0 0-.894 0L9 1zm-2.447.106a1 1 0 1 0 .894 1.789l-.894-1.789zm-6 3a1 1 0 1 0 .894 1.789L.553 4.106zm2.894.789a1 1 0 1 0-.894-1.789l.894 1.789zm-2-.789a1 1 0 1 0-.894 1.789l.894-1.789zm1.106 2.789a1 1 0 1 0 .894-1.789l-.894 1.789zM2 5a1 1 0 1 0-2 0h2zM0 7.5a1 1 0 1 0 2 0H0zm8.553 12.394a1 1 0 1 0 .894-1.789l-.894 1.789zm-1.106-2.789a1 1 0 1 0-.894 1.789l.894-1.789zm1.106 1a1 1 0 1 0 .894 1.789l-.894-1.789zm2.894.789a1 1 0 1 0-.894-1.789l.894 1.789zM8 19a1 1 0 1 0 2 0H8zm2-2.5a1 1 0 1 0-2 0h2zm-7.447.394a1 1 0 1 0 .894-1.789l-.894 1.789zM1 15H0a1 1 0 0 0 .553.894L1 15zm1-2.5a1 1 0 1 0-2 0h2zm12.553 2.606a1 1 0 1 0 .894 1.789l-.894-1.789zM17 15l.447.894A1 1 0 0 0 18 15h-1zm1-2.5a1 1 0 1 0-2 0h2zm-7.447-5.394l-2 1 .894 1.789 2-1-.894-1.789zm-1.106 1l-2-1-.894 1.789 2 1 .894-1.789zM8 9v2.5h2V9H8zm8.553-4.894l-2 1 .894 1.789 2-1-.894-1.789zm.894 0l-2-1-.894 1.789 2 1 .894-1.789zM16 5v2.5h2V5h-2zm-4.553-3.894l-2-1-.894 1.789 2 1 .894-1.789zm-2.894-1l-2 1 .894 1.789 2-1L8.553.106zM1.447 5.894l2-1-.894-1.789-2 1 .894 1.789zm-.894 0l2 1 .894-1.789-2-1-.894 1.789zM0 5v2.5h2V5H0zm9.447 13.106l-2-1-.894 1.789 2 1 .894-1.789zm0 1.789l2-1-.894-1.789-2 1 .894 1.789zM10 19v-2.5H8V19h2zm-6.553-3.894l-2-1-.894 1.789 2 1 .894-1.789zM2 15v-2.5H0V15h2zm13.447 1.894l2-1-.894-1.789-2 1 .894 1.789zM18 15v-2.5h-2V15h2z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</template>
|
||||||
7
todo-app/src/components/icons/IconSupport.vue
Normal file
7
todo-app/src/components/icons/IconSupport.vue
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<template>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor">
|
||||||
|
<path
|
||||||
|
d="M10 3.22l-.61-.6a5.5 5.5 0 0 0-7.666.105 5.5 5.5 0 0 0-.114 7.665L10 18.78l8.39-8.4a5.5 5.5 0 0 0-.114-7.665 5.5 5.5 0 0 0-7.666-.105l-.61.61z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</template>
|
||||||
19
todo-app/src/components/icons/IconTooling.vue
Normal file
19
todo-app/src/components/icons/IconTooling.vue
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<!-- This icon is from <https://github.com/Templarian/MaterialDesign>, distributed under Apache 2.0 (https://www.apache.org/licenses/LICENSE-2.0) license-->
|
||||||
|
<template>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
aria-hidden="true"
|
||||||
|
role="img"
|
||||||
|
class="iconify iconify--mdi"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
preserveAspectRatio="xMidYMid meet"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M20 18v-4h-3v1h-2v-1H9v1H7v-1H4v4h16M6.33 8l-1.74 4H7v-1h2v1h6v-1h2v1h2.41l-1.74-4H6.33M9 5v1h6V5H9m12.84 7.61c.1.22.16.48.16.8V18c0 .53-.21 1-.6 1.41c-.4.4-.85.59-1.4.59H4c-.55 0-1-.19-1.4-.59C2.21 19 2 18.53 2 18v-4.59c0-.32.06-.58.16-.8L4.5 7.22C4.84 6.41 5.45 6 6.33 6H7V5c0-.55.18-1 .57-1.41C7.96 3.2 8.44 3 9 3h6c.56 0 1.04.2 1.43.59c.39.41.57.86.57 1.41v1h.67c.88 0 1.49.41 1.83 1.22l2.34 5.39z"
|
||||||
|
fill="currentColor"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
</template>
|
||||||
6
todo-app/src/main.js
Normal file
6
todo-app/src/main.js
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import './assets/main.css'
|
||||||
|
|
||||||
|
import { createApp } from 'vue'
|
||||||
|
import App from './App.vue'
|
||||||
|
|
||||||
|
createApp(App).mount('#app')
|
||||||
18
todo-app/vite.config.js
Normal file
18
todo-app/vite.config.js
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { fileURLToPath, URL } from 'node:url'
|
||||||
|
|
||||||
|
import { defineConfig } from 'vite'
|
||||||
|
import vue from '@vitejs/plugin-vue'
|
||||||
|
import vueDevTools from 'vite-plugin-vue-devtools'
|
||||||
|
|
||||||
|
// https://vite.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [
|
||||||
|
vue(),
|
||||||
|
vueDevTools(),
|
||||||
|
],
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@': fileURLToPath(new URL('./src', import.meta.url))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
114
todo-app/使用指南.md
Normal file
114
todo-app/使用指南.md
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
# Vue 待办事项应用 - 使用指南
|
||||||
|
|
||||||
|
## 项目概述
|
||||||
|
这是一个基于 Vue 3 和 Vite 构建的现代化待办事项管理应用,使用了 Composition API 和响应式系统。
|
||||||
|
|
||||||
|
## 快速开始
|
||||||
|
|
||||||
|
### 1. 环境要求
|
||||||
|
- Node.js 版本 16.0 或更高
|
||||||
|
- npm 或 yarn 包管理器
|
||||||
|
|
||||||
|
### 2. 安装与运行
|
||||||
|
```bash
|
||||||
|
# 进入项目目录
|
||||||
|
cd todo-app
|
||||||
|
|
||||||
|
# 安装依赖
|
||||||
|
npm install
|
||||||
|
|
||||||
|
# 启动开发服务器
|
||||||
|
npm run dev
|
||||||
|
|
||||||
|
# 构建生产版本
|
||||||
|
npm run build
|
||||||
|
|
||||||
|
# 预览生产版本
|
||||||
|
npm run preview
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 访问应用
|
||||||
|
开发服务器启动后,在浏览器中访问 `http://localhost:5173`
|
||||||
|
|
||||||
|
## 功能使用说明
|
||||||
|
|
||||||
|
### 📝 添加待办事项
|
||||||
|
1. 在顶部输入框中输入待办内容
|
||||||
|
2. 点击"添加"按钮或按回车键确认添加
|
||||||
|
3. 新事项将出现在列表中
|
||||||
|
|
||||||
|
### ✅ 管理待办事项
|
||||||
|
- **标记完成**: 点击事项前的复选框
|
||||||
|
- **修改状态**: 再次点击复选框可取消完成状态
|
||||||
|
- **删除事项**: 点击右侧的"删除"按钮
|
||||||
|
|
||||||
|
### 🔍 筛选查看
|
||||||
|
应用提供三种查看模式:
|
||||||
|
- **全部**: 显示所有待办事项
|
||||||
|
- **未完成**: 只显示未完成的事项
|
||||||
|
- **已完成**: 只显示已完成的事项
|
||||||
|
|
||||||
|
每个筛选按钮显示对应类型的事项数量。
|
||||||
|
|
||||||
|
### 🧹 批量操作
|
||||||
|
- **清除已完成**: 点击底部"清除已完成"按钮,删除所有已完成的事项
|
||||||
|
- **统计显示**: 底部显示剩余未完成事项的数量
|
||||||
|
|
||||||
|
## 界面特性
|
||||||
|
|
||||||
|
### 🎨 视觉反馈
|
||||||
|
- **悬停效果**: 鼠标悬停时显示阴影效果
|
||||||
|
- **状态变化**: 已完成事项显示删除线和半透明效果
|
||||||
|
- **按钮状态**: 当前筛选条件高亮显示
|
||||||
|
|
||||||
|
### 📱 响应式设计
|
||||||
|
- 最大宽度 600px,在各种屏幕尺寸下都有良好体验
|
||||||
|
- 灵活的布局适配不同设备
|
||||||
|
|
||||||
|
### 🎯 用户体验
|
||||||
|
- **自动对焦**: 页面加载时输入框自动获得焦点
|
||||||
|
- **快捷键**: 支持回车键快速添加
|
||||||
|
- **空状态**: 无待办事项时显示友好提示
|
||||||
|
|
||||||
|
## 技术特点
|
||||||
|
|
||||||
|
### Vue 3 Composition API
|
||||||
|
- 使用 `<script setup>` 语法
|
||||||
|
- 响应式数据管理 (`ref`, `computed`)
|
||||||
|
- 现代化的组件开发模式
|
||||||
|
|
||||||
|
### 核心功能实现
|
||||||
|
- **数据持久化**: 使用内存存储(可扩展为 localStorage)
|
||||||
|
- **状态管理**: 响应式数据自动更新视图
|
||||||
|
- **事件处理**: 完整的用户交互支持
|
||||||
|
|
||||||
|
## 项目结构
|
||||||
|
```
|
||||||
|
todo-app/
|
||||||
|
├── src/
|
||||||
|
│ ├── App.vue # 主组件
|
||||||
|
│ ├── main.js # 应用入口
|
||||||
|
│ └── assets/ # 静态资源
|
||||||
|
├── public/ # 公共资源
|
||||||
|
├── package.json # 项目配置
|
||||||
|
└── vite.config.js # 构建配置
|
||||||
|
```
|
||||||
|
|
||||||
|
## 扩展建议
|
||||||
|
1. **数据持久化**: 添加 localStorage 支持
|
||||||
|
2. **编辑功能**: 支持双击编辑待办内容
|
||||||
|
3. **分类管理**: 添加标签或分类功能
|
||||||
|
4. **优先级**: 为待办事项添加优先级标记
|
||||||
|
5. **到期时间**: 支持设置截止日期
|
||||||
|
|
||||||
|
## 学习价值
|
||||||
|
这个项目适合Vue初学者,涵盖了:
|
||||||
|
- Vue 3 基础语法
|
||||||
|
- 响应式数据管理
|
||||||
|
- 事件处理
|
||||||
|
- 条件渲染
|
||||||
|
- 列表渲染
|
||||||
|
- 计算属性
|
||||||
|
- CSS 样式绑定
|
||||||
|
|
||||||
|
通过实践这个项目,可以掌握Vue现代开发的核心概念和最佳实践。
|
||||||
1302
weather-cards.html
Normal file
1302
weather-cards.html
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user