Compare commits

...

2 Commits

Author SHA1 Message Date
HP
c2027afe8a Merge branch 'main' of ssh://git.orinme.com:222/camellia/notes 2025-06-25 19:30:04 +08:00
HP
4c7451db25 Initial commit 2025-06-25 19:21:02 +08:00
43 changed files with 17352 additions and 0 deletions

View File

@ -0,0 +1,8 @@
{
"permissions": {
"allow": [
"mcp__zen__chat"
],
"deny": []
}
}

0
CLAUDE.md Normal file
View File

2807
Java语法完整指南.md Normal file

File diff suppressed because it is too large Load Diff

1997
Linux常用命令详解.md Normal file

File diff suppressed because it is too large Load Diff

View 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值的处理

View 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 条件
- 结合实际业务场景灵活运用

View 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;
```
### 面试题3Top-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 使用可以优化大数据量查询
- 理解不同排名函数的差异和适用场景

View 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;
-- ❌ 陷阱3LIKE查询的误用
-- 错误示例
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 查询优化,可以显著提升数据库查询性能,改善用户体验。

View 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操作

File diff suppressed because it is too large Load Diff

View 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
**难度等级**:⭐⭐⭐⭐☆

View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View File

@ -0,0 +1,3 @@
{
"recommendations": ["Vue.volar"]
}

29
todo-app/README.md Normal file
View 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
```

View 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
View 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
View File

@ -0,0 +1,8 @@
{
"compilerOptions": {
"paths": {
"@/*": ["./src/*"]
}
},
"exclude": ["node_modules", "dist"]
}

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
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

324
todo-app/src/App.vue Normal file
View 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>

View 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;
}

View 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

View 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;
}
}

View File

@ -0,0 +1,44 @@
<script setup>
defineProps({
msg: {
type: String,
required: true,
},
})
</script>
<template>
<div class="greetings">
<h1 class="green">{{ msg }}</h1>
<h3>
Youve 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>

View 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>
Vues
<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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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
View 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
View 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
View 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

File diff suppressed because it is too large Load Diff