Compare commits
No commits in common. "c2027afe8a4e9765e34e3587dfd3fa1873d9ee4b" and "dc02c97dc53bbc7d7857725b3a9e165847267265" have entirely different histories.
c2027afe8a
...
dc02c97dc5
@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"permissions": {
|
|
||||||
"allow": [
|
|
||||||
"mcp__zen__chat"
|
|
||||||
],
|
|
||||||
"deny": []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
2807
Java语法完整指南.md
2807
Java语法完整指南.md
File diff suppressed because it is too large
Load Diff
1997
Linux常用命令详解.md
1997
Linux常用命令详解.md
File diff suppressed because it is too large
Load Diff
@ -1,954 +0,0 @@
|
|||||||
# 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值的处理
|
|
||||||
@ -1,951 +0,0 @@
|
|||||||
# 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 条件
|
|
||||||
- 结合实际业务场景灵活运用
|
|
||||||
@ -1,769 +0,0 @@
|
|||||||
# MySQL ORDER BY 语法使用文档
|
|
||||||
|
|
||||||
## 目录
|
|
||||||
1. [ORDER BY 基础语法](#order-by-基础语法)
|
|
||||||
2. [ORDER BY 规则和特性](#order-by-规则和特性)
|
|
||||||
3. [示例数据准备](#示例数据准备)
|
|
||||||
4. [基础排序示例](#基础排序示例)
|
|
||||||
5. [多列排序](#多列排序)
|
|
||||||
6. [特殊排序场景](#特殊排序场景)
|
|
||||||
7. [复杂排序查询](#复杂排序查询)
|
|
||||||
8. [面试题和实际案例](#面试题和实际案例)
|
|
||||||
9. [性能优化和最佳实践](#性能优化和最佳实践)
|
|
||||||
|
|
||||||
## ORDER BY 基础语法
|
|
||||||
|
|
||||||
ORDER BY 用于对查询结果进行排序。
|
|
||||||
|
|
||||||
```sql
|
|
||||||
SELECT column1, column2, ...
|
|
||||||
FROM table_name
|
|
||||||
[WHERE condition]
|
|
||||||
ORDER BY column1 [ASC|DESC], column2 [ASC|DESC], ...
|
|
||||||
[LIMIT number];
|
|
||||||
```
|
|
||||||
|
|
||||||
**关键字说明:**
|
|
||||||
- `ASC`:升序排列(默认)
|
|
||||||
- `DESC`:降序排列
|
|
||||||
- 可以指定多个排序列
|
|
||||||
- NULL 值默认排在最前面(ASC)或最后面(DESC)
|
|
||||||
|
|
||||||
## ORDER BY 规则和特性
|
|
||||||
|
|
||||||
1. **排序优先级**:从左到右依次排序
|
|
||||||
2. **数据类型排序**:
|
|
||||||
- 数字:按数值大小
|
|
||||||
- 字符串:按字典序(字母顺序)
|
|
||||||
- 日期:按时间先后
|
|
||||||
- NULL:默认最小值处理
|
|
||||||
3. **性能影响**:ORDER BY 可能触发文件排序,影响查询性能
|
|
||||||
4. **与 LIMIT 结合**:获取排序后的前 N 条记录
|
|
||||||
|
|
||||||
## 示例数据准备
|
|
||||||
|
|
||||||
### 创建示例表
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- 学生表
|
|
||||||
CREATE TABLE students (
|
|
||||||
id INT PRIMARY KEY AUTO_INCREMENT,
|
|
||||||
name VARCHAR(50) NOT NULL,
|
|
||||||
age INT,
|
|
||||||
gender ENUM('男', '女'),
|
|
||||||
class_id INT,
|
|
||||||
score DECIMAL(5,2),
|
|
||||||
enrollment_date DATE,
|
|
||||||
city VARCHAR(30)
|
|
||||||
);
|
|
||||||
|
|
||||||
-- 订单表
|
|
||||||
CREATE TABLE orders (
|
|
||||||
order_id INT PRIMARY KEY AUTO_INCREMENT,
|
|
||||||
customer_name VARCHAR(50),
|
|
||||||
product_name VARCHAR(100),
|
|
||||||
order_date DATETIME,
|
|
||||||
amount DECIMAL(10,2),
|
|
||||||
status ENUM('pending', 'processing', 'completed', 'cancelled'),
|
|
||||||
priority INT,
|
|
||||||
region VARCHAR(30)
|
|
||||||
);
|
|
||||||
|
|
||||||
-- 员工表
|
|
||||||
CREATE TABLE staff (
|
|
||||||
emp_id INT PRIMARY KEY,
|
|
||||||
name VARCHAR(50),
|
|
||||||
department VARCHAR(30),
|
|
||||||
salary DECIMAL(10,2),
|
|
||||||
hire_date DATE,
|
|
||||||
manager_id INT,
|
|
||||||
performance_score DECIMAL(3,1),
|
|
||||||
bonus DECIMAL(8,2)
|
|
||||||
);
|
|
||||||
|
|
||||||
-- 产品销售表
|
|
||||||
CREATE TABLE product_sales (
|
|
||||||
id INT PRIMARY KEY AUTO_INCREMENT,
|
|
||||||
product_id INT,
|
|
||||||
product_name VARCHAR(100),
|
|
||||||
category VARCHAR(50),
|
|
||||||
sale_date DATE,
|
|
||||||
quantity INT,
|
|
||||||
unit_price DECIMAL(8,2),
|
|
||||||
discount_rate DECIMAL(3,2),
|
|
||||||
sales_rep VARCHAR(50)
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
### 插入示例数据
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- 插入学生数据
|
|
||||||
INSERT INTO students (name, age, gender, class_id, score, enrollment_date, city) VALUES
|
|
||||||
('张三', 20, '男', 1, 85.5, '2023-09-01', '北京'),
|
|
||||||
('李四', 19, '女', 1, 92.0, '2023-09-01', '上海'),
|
|
||||||
('王五', 21, '男', 2, 78.5, '2023-09-01', '深圳'),
|
|
||||||
('赵六', 20, '女', 2, 88.0, '2023-09-01', '广州'),
|
|
||||||
('钱七', 22, '男', 1, 76.0, '2022-09-01', '北京'),
|
|
||||||
('孙八', 19, '女', 3, 95.5, '2023-09-01', '杭州'),
|
|
||||||
('周九', 20, '男', 3, 82.0, '2023-09-01', '成都'),
|
|
||||||
('吴十', 21, '女', 2, 89.5, '2022-09-01', '南京'),
|
|
||||||
('郑一', 19, '男', 1, 91.0, '2023-09-01', '西安'),
|
|
||||||
('王二', NULL, '女', 3, 87.0, '2023-09-01', '重庆');
|
|
||||||
|
|
||||||
-- 插入订单数据
|
|
||||||
INSERT INTO orders (customer_name, product_name, order_date, amount, status, priority, region) VALUES
|
|
||||||
('客户A', '笔记本电脑', '2024-01-15 10:30:00', 5999.00, 'completed', 1, '华北'),
|
|
||||||
('客户B', '手机', '2024-01-16 14:20:00', 3999.00, 'processing', 2, '华东'),
|
|
||||||
('客户C', '平板电脑', '2024-01-16 16:45:00', 2999.00, 'pending', 3, '华南'),
|
|
||||||
('客户A', '耳机', '2024-01-17 09:15:00', 299.00, 'completed', 2, '华北'),
|
|
||||||
('客户D', '键盘', '2024-01-17 11:30:00', 199.00, 'cancelled', 3, '华西'),
|
|
||||||
('客户E', '鼠标', '2024-01-18 13:40:00', 99.00, 'completed', 1, '华东'),
|
|
||||||
('客户B', '显示器', '2024-01-18 15:20:00', 1299.00, 'processing', 1, '华东'),
|
|
||||||
('客户F', '音响', '2024-01-19 08:50:00', 899.00, 'pending', 2, '华南'),
|
|
||||||
('客户C', '摄像头', '2024-01-19 12:10:00', 399.00, 'completed', 3, '华南'),
|
|
||||||
('客户G', '打印机', '2024-01-20 16:30:00', 1599.00, 'processing', 1, '华北');
|
|
||||||
|
|
||||||
-- 插入员工数据
|
|
||||||
INSERT INTO staff (emp_id, name, department, salary, hire_date, manager_id, performance_score, bonus) VALUES
|
|
||||||
(1001, '张经理', '技术部', 15000.00, '2020-03-15', NULL, 9.2, 5000.00),
|
|
||||||
(1002, '李工程师', '技术部', 12000.00, '2021-05-20', 1001, 8.8, 3000.00),
|
|
||||||
(1003, '王设计师', '设计部', 10000.00, '2021-08-10', NULL, 8.5, 2500.00),
|
|
||||||
(1004, '赵分析师', '数据部', 11000.00, '2022-01-15', NULL, 9.0, 3500.00),
|
|
||||||
(1005, '钱开发', '技术部', 9000.00, '2022-04-20', 1001, 7.8, 2000.00),
|
|
||||||
(1006, '孙测试', '技术部', 8500.00, '2022-07-01', 1001, 8.2, 1500.00),
|
|
||||||
(1007, '周产品', '产品部', 13000.00, '2021-12-05', NULL, 8.9, 4000.00),
|
|
||||||
(1008, '吴运营', '运营部', 8000.00, '2023-02-10', NULL, 7.5, 1000.00),
|
|
||||||
(1009, '郑销售', '销售部', 7500.00, '2023-03-20', NULL, 8.1, 1800.00),
|
|
||||||
(1010, '刘助理', '技术部', 6000.00, '2023-09-01', 1002, NULL, 500.00);
|
|
||||||
|
|
||||||
-- 插入产品销售数据
|
|
||||||
INSERT INTO product_sales (product_id, product_name, category, sale_date, quantity, unit_price, discount_rate, sales_rep) VALUES
|
|
||||||
(101, 'iPhone 15', '手机', '2024-01-10', 50, 5999.00, 0.05, '张销售'),
|
|
||||||
(102, '华为Mate60', '手机', '2024-01-10', 30, 4999.00, 0.10, '李销售'),
|
|
||||||
(103, 'MacBook Pro', '笔记本', '2024-01-11', 20, 12999.00, 0.03, '张销售'),
|
|
||||||
(104, '联想ThinkPad', '笔记本', '2024-01-11', 40, 6999.00, 0.08, '王销售'),
|
|
||||||
(105, 'iPad Air', '平板', '2024-01-12', 25, 3999.00, 0.06, '李销售'),
|
|
||||||
(106, '小米平板', '平板', '2024-01-12', 35, 1999.00, 0.12, '赵销售'),
|
|
||||||
(107, 'AirPods Pro', '耳机', '2024-01-13', 100, 1999.00, 0.08, '张销售'),
|
|
||||||
(108, '索尼耳机', '耳机', '2024-01-13', 60, 2999.00, 0.15, '王销售'),
|
|
||||||
(109, 'Dell显示器', '显示器', '2024-01-14', 80, 1599.00, 0.10, '李销售'),
|
|
||||||
(110, '三星显示器', '显示器', '2024-01-14', 45, 2299.00, 0.07, '赵销售');
|
|
||||||
```
|
|
||||||
|
|
||||||
## 基础排序示例
|
|
||||||
|
|
||||||
### 1. 单列升序排序(默认)
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- 按年龄升序排列学生
|
|
||||||
SELECT name, age FROM students ORDER BY age;
|
|
||||||
```
|
|
||||||
|
|
||||||
**结果:**
|
|
||||||
```
|
|
||||||
+------+------+
|
|
||||||
| name | age |
|
|
||||||
+------+------+
|
|
||||||
| 王二 | NULL |
|
|
||||||
| 李四 | 19 |
|
|
||||||
| 孙八 | 19 |
|
|
||||||
| 郑一 | 19 |
|
|
||||||
| 张三 | 20 |
|
|
||||||
| 赵六 | 20 |
|
|
||||||
| 周九 | 20 |
|
|
||||||
| 王五 | 21 |
|
|
||||||
| 吴十 | 21 |
|
|
||||||
| 钱七 | 22 |
|
|
||||||
+------+------+
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 单列降序排序
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- 按分数降序排列学生
|
|
||||||
SELECT name, score FROM students ORDER BY score DESC;
|
|
||||||
```
|
|
||||||
|
|
||||||
**结果:**
|
|
||||||
```
|
|
||||||
+------+-------+
|
|
||||||
| name | score |
|
|
||||||
+------+-------+
|
|
||||||
| 孙八 | 95.50 |
|
|
||||||
| 李四 | 92.00 |
|
|
||||||
| 郑一 | 91.00 |
|
|
||||||
| 吴十 | 89.50 |
|
|
||||||
| 赵六 | 88.00 |
|
|
||||||
| 王二 | 87.00 |
|
|
||||||
| 张三 | 85.50 |
|
|
||||||
| 周九 | 82.00 |
|
|
||||||
| 王五 | 78.50 |
|
|
||||||
| 钱七 | 76.00 |
|
|
||||||
+------+-------+
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. 字符串排序
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- 按姓名字母顺序排列
|
|
||||||
SELECT name, age FROM students ORDER BY name;
|
|
||||||
```
|
|
||||||
|
|
||||||
**结果:**
|
|
||||||
```
|
|
||||||
+------+------+
|
|
||||||
| name | age |
|
|
||||||
+------+------+
|
|
||||||
| 钱七 | 22 |
|
|
||||||
| 孙八 | 19 |
|
|
||||||
| 王二 | NULL |
|
|
||||||
| 王五 | 21 |
|
|
||||||
| 吴十 | 21 |
|
|
||||||
| 张三 | 20 |
|
|
||||||
| 赵六 | 20 |
|
|
||||||
| 郑一 | 19 |
|
|
||||||
| 周九 | 20 |
|
|
||||||
| 李四 | 19 |
|
|
||||||
+------+-------+
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. 日期排序
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- 按入学日期排序
|
|
||||||
SELECT name, enrollment_date FROM students ORDER BY enrollment_date;
|
|
||||||
```
|
|
||||||
|
|
||||||
**结果:**
|
|
||||||
```
|
|
||||||
+------+-----------------+
|
|
||||||
| name | enrollment_date |
|
|
||||||
+------+-----------------+
|
|
||||||
| 钱七 | 2022-09-01 |
|
|
||||||
| 吴十 | 2022-09-01 |
|
|
||||||
| 张三 | 2023-09-01 |
|
|
||||||
| 李四 | 2023-09-01 |
|
|
||||||
| 王五 | 2023-09-01 |
|
|
||||||
| 赵六 | 2023-09-01 |
|
|
||||||
| 孙八 | 2023-09-01 |
|
|
||||||
| 周九 | 2023-09-01 |
|
|
||||||
| 郑一 | 2023-09-01 |
|
|
||||||
| 王二 | 2023-09-01 |
|
|
||||||
+------+-----------------+
|
|
||||||
```
|
|
||||||
|
|
||||||
## 多列排序
|
|
||||||
|
|
||||||
### 1. 多列升序排序
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- 先按班级排序,再按分数排序
|
|
||||||
SELECT name, class_id, score
|
|
||||||
FROM students
|
|
||||||
ORDER BY class_id, score;
|
|
||||||
```
|
|
||||||
|
|
||||||
**结果:**
|
|
||||||
```
|
|
||||||
+------+----------+-------+
|
|
||||||
| name | class_id | score |
|
|
||||||
+------+----------+-------+
|
|
||||||
| 钱七 | 1 | 76.00 |
|
|
||||||
| 张三 | 1 | 85.50 |
|
|
||||||
| 郑一 | 1 | 91.00 |
|
|
||||||
| 李四 | 1 | 92.00 |
|
|
||||||
| 王五 | 2 | 78.50 |
|
|
||||||
| 赵六 | 2 | 88.00 |
|
|
||||||
| 吴十 | 2 | 89.50 |
|
|
||||||
| 周九 | 3 | 82.00 |
|
|
||||||
| 王二 | 3 | 87.00 |
|
|
||||||
| 孙八 | 3 | 95.50 |
|
|
||||||
+------+----------+-------+
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 混合排序(升序+降序)
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- 按班级升序,分数降序
|
|
||||||
SELECT name, class_id, score
|
|
||||||
FROM students
|
|
||||||
ORDER BY class_id ASC, score DESC;
|
|
||||||
```
|
|
||||||
|
|
||||||
**结果:**
|
|
||||||
```
|
|
||||||
+------+----------+-------+
|
|
||||||
| name | class_id | score |
|
|
||||||
+------+----------+-------+
|
|
||||||
| 李四 | 1 | 92.00 |
|
|
||||||
| 郑一 | 1 | 91.00 |
|
|
||||||
| 张三 | 1 | 85.50 |
|
|
||||||
| 钱七 | 1 | 76.00 |
|
|
||||||
| 吴十 | 2 | 89.50 |
|
|
||||||
| 赵六 | 2 | 88.00 |
|
|
||||||
| 王五 | 2 | 78.50 |
|
|
||||||
| 孙八 | 3 | 95.50 |
|
|
||||||
| 王二 | 3 | 87.00 |
|
|
||||||
| 周九 | 3 | 82.00 |
|
|
||||||
+------+----------+-------+
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. 三列排序
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- 按部门、薪资、绩效排序
|
|
||||||
SELECT name, department, salary, performance_score
|
|
||||||
FROM staff
|
|
||||||
ORDER BY department, salary DESC, performance_score DESC;
|
|
||||||
```
|
|
||||||
|
|
||||||
**结果:**
|
|
||||||
```
|
|
||||||
+----------+----------+----------+------------------+
|
|
||||||
| name | department | salary | performance_score |
|
|
||||||
+----------+----------+----------+------------------+
|
|
||||||
| 王设计师 | 设计部 | 10000.00 | 8.50 |
|
|
||||||
| 赵分析师 | 数据部 | 11000.00 | 9.00 |
|
|
||||||
| 周产品 | 产品部 | 13000.00 | 8.90 |
|
|
||||||
| 张经理 | 技术部 | 15000.00 | 9.20 |
|
|
||||||
| 李工程师 | 技术部 | 12000.00 | 8.80 |
|
|
||||||
| 钱开发 | 技术部 | 9000.00 | 7.80 |
|
|
||||||
| 孙测试 | 技术部 | 8500.00 | 8.20 |
|
|
||||||
| 刘助理 | 技术部 | 6000.00 | NULL |
|
|
||||||
| 吴运营 | 运营部 | 8000.00 | 7.50 |
|
|
||||||
| 郑销售 | 销售部 | 7500.00 | 8.10 |
|
|
||||||
+----------+----------+----------+------------------+
|
|
||||||
```
|
|
||||||
|
|
||||||
## 特殊排序场景
|
|
||||||
|
|
||||||
### 1. NULL 值处理
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- 查看NULL值的排序位置
|
|
||||||
SELECT name, age FROM students ORDER BY age;
|
|
||||||
|
|
||||||
-- 使用 ISNULL() 函数控制NULL值排序
|
|
||||||
SELECT name, age
|
|
||||||
FROM students
|
|
||||||
ORDER BY ISNULL(age), age; -- NULL值排在最后
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 自定义排序(FIELD函数)
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- 按自定义状态优先级排序
|
|
||||||
SELECT customer_name, status, amount
|
|
||||||
FROM orders
|
|
||||||
ORDER BY FIELD(status, 'pending', 'processing', 'completed', 'cancelled'), amount DESC;
|
|
||||||
```
|
|
||||||
|
|
||||||
**结果:**
|
|
||||||
```
|
|
||||||
+------------+------------+---------+
|
|
||||||
| customer_name | status | amount |
|
|
||||||
+------------+------------+---------+
|
|
||||||
| 客户C | pending | 2999.00 |
|
|
||||||
| 客户F | pending | 899.00 |
|
|
||||||
| 客户B | processing | 3999.00 |
|
|
||||||
| 客户G | processing | 1599.00 |
|
|
||||||
| 客户B | processing | 1299.00 |
|
|
||||||
| 客户A | completed | 5999.00 |
|
|
||||||
| 客户F | completed | 899.00 |
|
|
||||||
| 客户C | completed | 399.00 |
|
|
||||||
| 客户A | completed | 299.00 |
|
|
||||||
| 客户E | completed | 99.00 |
|
|
||||||
| 客户D | cancelled | 199.00 |
|
|
||||||
+------------+------------+---------+
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. 条件排序(CASE WHEN)
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- 根据不同条件进行排序
|
|
||||||
SELECT name, department, salary,
|
|
||||||
CASE
|
|
||||||
WHEN department = '技术部' THEN 1
|
|
||||||
WHEN department = '产品部' THEN 2
|
|
||||||
WHEN department = '设计部' THEN 3
|
|
||||||
ELSE 4
|
|
||||||
END AS dept_priority
|
|
||||||
FROM staff
|
|
||||||
ORDER BY dept_priority, salary DESC;
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. 计算字段排序
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- 按销售额排序(数量×单价×(1-折扣率))
|
|
||||||
SELECT product_name, quantity, unit_price, discount_rate,
|
|
||||||
ROUND(quantity * unit_price * (1 - discount_rate), 2) AS total_sales
|
|
||||||
FROM product_sales
|
|
||||||
ORDER BY total_sales DESC;
|
|
||||||
```
|
|
||||||
|
|
||||||
**结果:**
|
|
||||||
```
|
|
||||||
+-------------+----------+------------+---------------+-------------+
|
|
||||||
| product_name | quantity | unit_price | discount_rate | total_sales |
|
|
||||||
+-------------+----------+------------+---------------+-------------+
|
|
||||||
| iPhone 15 | 50 | 5999.00 | 0.05 | 284952.50 |
|
|
||||||
| MacBook Pro | 20 | 12999.00 | 0.03 | 251980.60 |
|
|
||||||
| 联想ThinkPad | 40 | 6999.00 | 0.08 | 257569.60 |
|
|
||||||
| AirPods Pro | 100 | 1999.00 | 0.08 | 183908.00 |
|
|
||||||
| 索尼耳机 | 60 | 2999.00 | 0.15 | 152949.00 |
|
|
||||||
| 华为Mate60 | 30 | 4999.00 | 0.10 | 134973.00 |
|
|
||||||
| Dell显示器 | 80 | 1599.00 | 0.10 | 115128.00 |
|
|
||||||
| iPad Air | 25 | 3999.00 | 0.06 | 93975.00 |
|
|
||||||
| 三星显示器 | 45 | 2299.00 | 0.07 | 96218.55 |
|
|
||||||
| 小米平板 | 35 | 1999.00 | 0.12 | 61652.40 |
|
|
||||||
+-------------+----------+------------+---------------+-------------+
|
|
||||||
```
|
|
||||||
|
|
||||||
## 复杂排序查询
|
|
||||||
|
|
||||||
### 1. 分组后排序
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- 各部门平均薪资,按平均薪资降序
|
|
||||||
SELECT department,
|
|
||||||
COUNT(*) AS emp_count,
|
|
||||||
ROUND(AVG(salary), 2) AS avg_salary
|
|
||||||
FROM staff
|
|
||||||
GROUP BY department
|
|
||||||
ORDER BY avg_salary DESC;
|
|
||||||
```
|
|
||||||
|
|
||||||
**结果:**
|
|
||||||
```
|
|
||||||
+----------+-----------+------------+
|
|
||||||
| department | emp_count | avg_salary |
|
|
||||||
+----------+-----------+------------+
|
|
||||||
| 技术部 | 5 | 10100.00 |
|
|
||||||
| 产品部 | 1 | 13000.00 |
|
|
||||||
| 数据部 | 1 | 11000.00 |
|
|
||||||
| 设计部 | 1 | 10000.00 |
|
|
||||||
| 运营部 | 1 | 8000.00 |
|
|
||||||
| 销售部 | 1 | 7500.00 |
|
|
||||||
+----------+-----------+------------+
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 子查询结果排序
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- 查找每个班级分数最高的学生
|
|
||||||
SELECT s1.name, s1.class_id, s1.score
|
|
||||||
FROM students s1
|
|
||||||
WHERE s1.score = (
|
|
||||||
SELECT MAX(s2.score)
|
|
||||||
FROM students s2
|
|
||||||
WHERE s2.class_id = s1.class_id
|
|
||||||
)
|
|
||||||
ORDER BY s1.class_id, s1.score DESC;
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. 窗口函数排序
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- 使用ROW_NUMBER()进行排名
|
|
||||||
SELECT name, department, salary,
|
|
||||||
ROW_NUMBER() OVER (PARTITION BY department ORDER BY salary DESC) AS dept_rank,
|
|
||||||
ROW_NUMBER() OVER (ORDER BY salary DESC) AS overall_rank
|
|
||||||
FROM staff
|
|
||||||
ORDER BY department, dept_rank;
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. 复杂条件排序
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- 优先显示高优先级且金额大的订单
|
|
||||||
SELECT customer_name, product_name, amount, priority, status,
|
|
||||||
CASE
|
|
||||||
WHEN priority = 1 AND amount > 1000 THEN 1
|
|
||||||
WHEN priority = 1 THEN 2
|
|
||||||
WHEN priority = 2 AND amount > 1000 THEN 3
|
|
||||||
WHEN priority = 2 THEN 4
|
|
||||||
ELSE 5
|
|
||||||
END AS sort_priority
|
|
||||||
FROM orders
|
|
||||||
ORDER BY sort_priority, amount DESC;
|
|
||||||
```
|
|
||||||
|
|
||||||
## 面试题和实际案例
|
|
||||||
|
|
||||||
### 面试题1:销售排名问题
|
|
||||||
|
|
||||||
**题目**:查询每个销售代表的销售总额,并按销售额降序排列,同时显示排名。
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- 解答
|
|
||||||
SELECT sales_rep,
|
|
||||||
COUNT(*) AS product_count,
|
|
||||||
SUM(quantity * unit_price * (1 - discount_rate)) AS total_sales,
|
|
||||||
RANK() OVER (ORDER BY SUM(quantity * unit_price * (1 - discount_rate)) DESC) AS sales_rank
|
|
||||||
FROM product_sales
|
|
||||||
GROUP BY sales_rep
|
|
||||||
ORDER BY total_sales DESC;
|
|
||||||
```
|
|
||||||
|
|
||||||
**结果:**
|
|
||||||
```
|
|
||||||
+----------+---------------+-------------+------------+
|
|
||||||
| sales_rep | product_count | total_sales | sales_rank |
|
|
||||||
+----------+---------------+-------------+------------+
|
|
||||||
| 张销售 | 3 | 720841.10 | 1 |
|
|
||||||
| 李销售 | 3 | 344076.00 | 2 |
|
|
||||||
| 王销售 | 2 | 410518.20 | 3 |
|
|
||||||
| 赵销售 | 2 | 157870.95 | 4 |
|
|
||||||
+----------+---------------+-------------+------------+
|
|
||||||
```
|
|
||||||
|
|
||||||
### 面试题2:分页查询优化
|
|
||||||
|
|
||||||
**题目**:实现高效的分页查询,按ID排序。
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- 传统分页(性能较差)
|
|
||||||
SELECT * FROM orders ORDER BY order_id LIMIT 1000000, 10;
|
|
||||||
|
|
||||||
-- 优化后的分页(使用索引)
|
|
||||||
SELECT o.* FROM orders o
|
|
||||||
WHERE o.order_id > (
|
|
||||||
SELECT order_id FROM orders ORDER BY order_id LIMIT 999999, 1
|
|
||||||
)
|
|
||||||
ORDER BY o.order_id LIMIT 10;
|
|
||||||
|
|
||||||
-- 更好的分页方案(记住上次的最后一条记录ID)
|
|
||||||
SELECT * FROM orders
|
|
||||||
WHERE order_id > 1000000 -- 上次查询的最后一个ID
|
|
||||||
ORDER BY order_id LIMIT 10;
|
|
||||||
```
|
|
||||||
|
|
||||||
### 面试题3:Top-N问题
|
|
||||||
|
|
||||||
**题目**:查询每个类别销售额前2名的产品。
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- 使用窗口函数解决
|
|
||||||
SELECT category, product_name, total_sales, category_rank
|
|
||||||
FROM (
|
|
||||||
SELECT category, product_name,
|
|
||||||
quantity * unit_price * (1 - discount_rate) AS total_sales,
|
|
||||||
ROW_NUMBER() OVER (PARTITION BY category ORDER BY quantity * unit_price * (1 - discount_rate) DESC) AS category_rank
|
|
||||||
FROM product_sales
|
|
||||||
) ranked_sales
|
|
||||||
WHERE category_rank <= 2
|
|
||||||
ORDER BY category, category_rank;
|
|
||||||
```
|
|
||||||
|
|
||||||
### 实际案例1:电商订单分析
|
|
||||||
|
|
||||||
**场景**:电商平台需要分析订单数据,按多个维度排序。
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- 复合排序:紧急订单优先,然后按金额降序,最后按时间升序
|
|
||||||
SELECT customer_name, product_name, amount, status, priority, order_date,
|
|
||||||
CASE
|
|
||||||
WHEN status = 'pending' AND priority = 1 THEN '紧急待处理'
|
|
||||||
WHEN status = 'processing' AND priority <= 2 THEN '优先处理中'
|
|
||||||
WHEN status = 'completed' THEN '已完成'
|
|
||||||
ELSE '普通订单'
|
|
||||||
END AS order_category
|
|
||||||
FROM orders
|
|
||||||
ORDER BY
|
|
||||||
CASE WHEN status = 'pending' AND priority = 1 THEN 1
|
|
||||||
WHEN status = 'processing' AND priority <= 2 THEN 2
|
|
||||||
WHEN status = 'pending' THEN 3
|
|
||||||
WHEN status = 'processing' THEN 4
|
|
||||||
WHEN status = 'completed' THEN 5
|
|
||||||
ELSE 6 END,
|
|
||||||
amount DESC,
|
|
||||||
order_date ASC;
|
|
||||||
```
|
|
||||||
|
|
||||||
### 实际案例2:学生成绩分析
|
|
||||||
|
|
||||||
**场景**:学校需要按多个条件对学生排序。
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- 综合排序:优秀学生优先(分数>90),然后按班级、分数排序
|
|
||||||
SELECT name, class_id, score, age,
|
|
||||||
CASE
|
|
||||||
WHEN score >= 90 THEN '优秀'
|
|
||||||
WHEN score >= 80 THEN '良好'
|
|
||||||
WHEN score >= 70 THEN '中等'
|
|
||||||
ELSE '待提高'
|
|
||||||
END AS grade_level
|
|
||||||
FROM students
|
|
||||||
ORDER BY
|
|
||||||
CASE WHEN score >= 90 THEN 1 ELSE 2 END, -- 优秀学生优先
|
|
||||||
class_id, -- 再按班级
|
|
||||||
score DESC, -- 最后按分数降序
|
|
||||||
age; -- 分数相同时按年龄升序
|
|
||||||
```
|
|
||||||
|
|
||||||
### 实际案例3:员工绩效排序
|
|
||||||
|
|
||||||
**场景**:HR部门需要按绩效和多个因素对员工排序。
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- 复杂绩效排序
|
|
||||||
SELECT name, department, salary, performance_score, bonus, hire_date,
|
|
||||||
CASE
|
|
||||||
WHEN performance_score >= 9.0 THEN 'A'
|
|
||||||
WHEN performance_score >= 8.0 THEN 'B'
|
|
||||||
WHEN performance_score >= 7.0 THEN 'C'
|
|
||||||
ELSE 'D'
|
|
||||||
END AS performance_grade
|
|
||||||
FROM staff
|
|
||||||
WHERE performance_score IS NOT NULL
|
|
||||||
ORDER BY
|
|
||||||
performance_score DESC, -- 绩效评分降序
|
|
||||||
CASE WHEN department = '技术部' THEN 1 -- 技术部优先
|
|
||||||
WHEN department = '产品部' THEN 2
|
|
||||||
ELSE 3 END,
|
|
||||||
salary DESC, -- 薪资降序
|
|
||||||
hire_date; -- 入职时间升序(资历)
|
|
||||||
```
|
|
||||||
|
|
||||||
### 面试题4:连续排名问题
|
|
||||||
|
|
||||||
**题目**:为员工按薪资排序,要求排名连续(即使薪资相同)。
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- 使用不同的排名函数
|
|
||||||
SELECT name, salary,
|
|
||||||
ROW_NUMBER() OVER (ORDER BY salary DESC) AS row_num, -- 连续排名
|
|
||||||
RANK() OVER (ORDER BY salary DESC) AS rank_num, -- 跳跃排名
|
|
||||||
DENSE_RANK() OVER (ORDER BY salary DESC) AS dense_rank -- 密集排名
|
|
||||||
FROM staff
|
|
||||||
ORDER BY salary DESC, name;
|
|
||||||
```
|
|
||||||
|
|
||||||
### 面试题5:移动平均排序
|
|
||||||
|
|
||||||
**题目**:计算每个学生的班级排名和移动平均分。
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- 窗口函数实现移动平均
|
|
||||||
SELECT name, class_id, score,
|
|
||||||
ROW_NUMBER() OVER (PARTITION BY class_id ORDER BY score DESC) AS class_rank,
|
|
||||||
ROUND(AVG(score) OVER (
|
|
||||||
PARTITION BY class_id
|
|
||||||
ORDER BY score DESC
|
|
||||||
ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING
|
|
||||||
), 2) AS moving_avg
|
|
||||||
FROM students
|
|
||||||
WHERE score IS NOT NULL
|
|
||||||
ORDER BY class_id, class_rank;
|
|
||||||
```
|
|
||||||
|
|
||||||
## 性能优化和最佳实践
|
|
||||||
|
|
||||||
### 1. 索引优化
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- 为经常排序的列创建索引
|
|
||||||
CREATE INDEX idx_students_score ON students(score);
|
|
||||||
CREATE INDEX idx_orders_date_amount ON orders(order_date, amount);
|
|
||||||
CREATE INDEX idx_staff_dept_salary ON staff(department, salary DESC);
|
|
||||||
|
|
||||||
-- 复合索引的排序优化
|
|
||||||
CREATE INDEX idx_orders_status_priority_amount ON orders(status, priority, amount DESC);
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. LIMIT 与 ORDER BY 结合使用
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- ✅ 推荐:只获取需要的记录数
|
|
||||||
SELECT name, score FROM students ORDER BY score DESC LIMIT 10;
|
|
||||||
|
|
||||||
-- ❌ 避免:不必要的全表排序
|
|
||||||
SELECT name, score FROM students ORDER BY score DESC; -- 然后在应用层取前10条
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. 避免文件排序
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- ✅ 使用索引排序(Using index)
|
|
||||||
EXPLAIN SELECT name, score FROM students ORDER BY score DESC;
|
|
||||||
|
|
||||||
-- 查看执行计划中是否出现 "Using filesort"
|
|
||||||
-- 出现则表示需要文件排序,性能较差
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. 排序字段选择
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- ✅ 推荐:使用数值型字段排序
|
|
||||||
SELECT * FROM orders ORDER BY amount DESC;
|
|
||||||
|
|
||||||
-- ⚠️ 注意:字符串排序相对较慢
|
|
||||||
SELECT * FROM orders ORDER BY customer_name;
|
|
||||||
|
|
||||||
-- ✅ 优化:为常用字符串排序创建专门索引
|
|
||||||
CREATE INDEX idx_orders_customer_name ON orders(customer_name);
|
|
||||||
```
|
|
||||||
|
|
||||||
### 5. 内存使用优化
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- 调整排序缓冲区大小(根据实际情况)
|
|
||||||
SET SESSION sort_buffer_size = 2097152; -- 2MB
|
|
||||||
|
|
||||||
-- 监控排序性能
|
|
||||||
SHOW STATUS LIKE 'Sort%';
|
|
||||||
```
|
|
||||||
|
|
||||||
### 6. 常见性能陷阱
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- ❌ 避免:在ORDER BY中使用函数
|
|
||||||
SELECT * FROM students ORDER BY UPPER(name);
|
|
||||||
|
|
||||||
-- ✅ 推荐:创建函数索引或使用计算列
|
|
||||||
ALTER TABLE students ADD COLUMN name_upper VARCHAR(50) GENERATED ALWAYS AS (UPPER(name));
|
|
||||||
CREATE INDEX idx_students_name_upper ON students(name_upper);
|
|
||||||
|
|
||||||
-- ❌ 避免:复杂的CASE WHEN排序
|
|
||||||
SELECT * FROM orders
|
|
||||||
ORDER BY CASE WHEN status = 'pending' THEN 1
|
|
||||||
WHEN status = 'processing' THEN 2
|
|
||||||
ELSE 3 END, amount DESC;
|
|
||||||
|
|
||||||
-- ✅ 推荐:添加排序辅助列
|
|
||||||
ALTER TABLE orders ADD COLUMN status_sort_order INT;
|
|
||||||
UPDATE orders SET status_sort_order = CASE
|
|
||||||
WHEN status = 'pending' THEN 1
|
|
||||||
WHEN status = 'processing' THEN 2
|
|
||||||
ELSE 3 END;
|
|
||||||
CREATE INDEX idx_orders_status_sort ON orders(status_sort_order, amount DESC);
|
|
||||||
```
|
|
||||||
|
|
||||||
### 7. 监控和调优
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- 查看慢查询日志中的排序相关查询
|
|
||||||
SHOW VARIABLES LIKE 'slow_query_log%';
|
|
||||||
|
|
||||||
-- 分析排序操作的性能
|
|
||||||
SELECT
|
|
||||||
sql_text,
|
|
||||||
exec_count,
|
|
||||||
avg_timer_wait/1000000000000 as 'avg_time_sec'
|
|
||||||
FROM performance_schema.events_statements_summary_by_digest
|
|
||||||
WHERE sql_text LIKE '%ORDER BY%'
|
|
||||||
ORDER BY avg_timer_wait DESC
|
|
||||||
LIMIT 10;
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**总结:**
|
|
||||||
- ORDER BY 是数据排序的核心工具
|
|
||||||
- 合理使用索引可以显著提升排序性能
|
|
||||||
- 多列排序时注意优先级和索引设计
|
|
||||||
- 避免在排序字段上使用函数计算
|
|
||||||
- 结合 LIMIT 使用可以优化大数据量查询
|
|
||||||
- 理解不同排名函数的差异和适用场景
|
|
||||||
@ -1,956 +0,0 @@
|
|||||||
# MySQL RANGE 查询优化详解
|
|
||||||
|
|
||||||
## 目录
|
|
||||||
1. [RANGE 查询基本概念](#range-查询基本概念)
|
|
||||||
2. [RANGE 查询原理和类型](#range-查询原理和类型)
|
|
||||||
3. [示例数据准备](#示例数据准备)
|
|
||||||
4. [RANGE 查询优化技巧](#range-查询优化技巧)
|
|
||||||
5. [索引策略和执行计划分析](#索引策略和执行计划分析)
|
|
||||||
6. [实际开发场景案例](#实际开发场景案例)
|
|
||||||
7. [分区表与 RANGE 优化](#分区表与-range-优化)
|
|
||||||
8. [性能监控和调优](#性能监控和调优)
|
|
||||||
9. [常见问题和最佳实践](#常见问题和最佳实践)
|
|
||||||
|
|
||||||
## RANGE 查询基本概念
|
|
||||||
|
|
||||||
### 什么是 RANGE 查询
|
|
||||||
|
|
||||||
RANGE 查询是指查询某个字段在指定范围内的数据,在 MySQL 执行计划中表现为 `type=range`。这类查询在实际开发中非常常见,包括:
|
|
||||||
|
|
||||||
- 时间范围查询:`WHERE create_time BETWEEN '2024-01-01' AND '2024-12-31'`
|
|
||||||
- 数值范围查询:`WHERE price BETWEEN 100 AND 500`
|
|
||||||
- 大于小于查询:`WHERE id > 1000 AND id < 2000`
|
|
||||||
- IN 查询:`WHERE status IN ('active', 'pending')`
|
|
||||||
|
|
||||||
### RANGE 查询的重要性
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- 典型的业务场景
|
|
||||||
-- 1. 订单查询:查询某个时间段的订单
|
|
||||||
SELECT * FROM orders WHERE order_date BETWEEN '2024-01-01' AND '2024-01-31';
|
|
||||||
|
|
||||||
-- 2. 商品筛选:查询某个价格区间的商品
|
|
||||||
SELECT * FROM products WHERE price >= 100 AND price <= 500;
|
|
||||||
|
|
||||||
-- 3. 日志分析:查询某个ID范围的日志
|
|
||||||
SELECT * FROM logs WHERE id > 10000 AND id <= 20000;
|
|
||||||
|
|
||||||
-- 4. 状态筛选:查询多个状态的记录
|
|
||||||
SELECT * FROM users WHERE status IN ('active', 'premium', 'vip');
|
|
||||||
```
|
|
||||||
|
|
||||||
## RANGE 查询原理和类型
|
|
||||||
|
|
||||||
### 1. MySQL 中 RANGE 查询的类型
|
|
||||||
|
|
||||||
| 类型 | 描述 | 示例 |
|
|
||||||
|------|------|------|
|
|
||||||
| **range** | 索引范围扫描 | `WHERE id BETWEEN 1 AND 100` |
|
|
||||||
| **index_merge** | 多个索引合并 | `WHERE id > 100 OR name = 'test'` |
|
|
||||||
| **ref_or_null** | 引用查询包含NULL | `WHERE id = 1 OR id IS NULL` |
|
|
||||||
|
|
||||||
### 2. RANGE 查询的执行流程
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- 执行流程示意
|
|
||||||
1. 解析 SQL 语句和条件
|
|
||||||
2. 选择最优索引
|
|
||||||
3. 定位起始位置(起始键值)
|
|
||||||
4. 扫描到结束位置(结束键值)
|
|
||||||
5. 返回符合条件的记录
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. 索引选择性和基数
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- 查看索引选择性
|
|
||||||
SELECT
|
|
||||||
COUNT(DISTINCT column_name) / COUNT(*) as selectivity,
|
|
||||||
COUNT(DISTINCT column_name) as cardinality,
|
|
||||||
COUNT(*) as total_rows
|
|
||||||
FROM table_name;
|
|
||||||
|
|
||||||
-- 选择性越高,索引效果越好
|
|
||||||
-- 选择性 = 不重复值数量 / 总行数
|
|
||||||
-- 选择性接近 1 表示索引效果最好
|
|
||||||
```
|
|
||||||
|
|
||||||
## 示例数据准备
|
|
||||||
|
|
||||||
### 创建测试表和数据
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- 创建订单表
|
|
||||||
CREATE TABLE orders (
|
|
||||||
order_id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
|
||||||
user_id INT NOT NULL,
|
|
||||||
product_id INT NOT NULL,
|
|
||||||
order_date DATE NOT NULL,
|
|
||||||
order_time DATETIME NOT NULL,
|
|
||||||
amount DECIMAL(10,2) NOT NULL,
|
|
||||||
quantity INT NOT NULL,
|
|
||||||
status ENUM('pending', 'paid', 'shipped', 'delivered', 'cancelled') NOT NULL,
|
|
||||||
region VARCHAR(50) NOT NULL,
|
|
||||||
channel VARCHAR(30) NOT NULL,
|
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
||||||
|
|
||||||
KEY idx_user_id (user_id),
|
|
||||||
KEY idx_product_id (product_id),
|
|
||||||
KEY idx_order_date (order_date),
|
|
||||||
KEY idx_order_time (order_time),
|
|
||||||
KEY idx_amount (amount),
|
|
||||||
KEY idx_status (status),
|
|
||||||
KEY idx_region (region),
|
|
||||||
KEY idx_user_date (user_id, order_date),
|
|
||||||
KEY idx_status_date (status, order_date),
|
|
||||||
KEY idx_amount_date (amount, order_date)
|
|
||||||
) ENGINE=InnoDB;
|
|
||||||
|
|
||||||
-- 创建商品表
|
|
||||||
CREATE TABLE products (
|
|
||||||
product_id INT PRIMARY KEY AUTO_INCREMENT,
|
|
||||||
product_name VARCHAR(200) NOT NULL,
|
|
||||||
category_id INT NOT NULL,
|
|
||||||
price DECIMAL(8,2) NOT NULL,
|
|
||||||
cost DECIMAL(8,2) NOT NULL,
|
|
||||||
stock_quantity INT NOT NULL,
|
|
||||||
weight DECIMAL(6,3),
|
|
||||||
created_date DATE NOT NULL,
|
|
||||||
last_update_time DATETIME NOT NULL,
|
|
||||||
status TINYINT NOT NULL DEFAULT 1,
|
|
||||||
|
|
||||||
KEY idx_category (category_id),
|
|
||||||
KEY idx_price (price),
|
|
||||||
KEY idx_stock (stock_quantity),
|
|
||||||
KEY idx_created_date (created_date),
|
|
||||||
KEY idx_status (status),
|
|
||||||
KEY idx_category_price (category_id, price),
|
|
||||||
KEY idx_status_price (status, price)
|
|
||||||
) ENGINE=InnoDB;
|
|
||||||
|
|
||||||
-- 创建用户访问日志表
|
|
||||||
CREATE TABLE access_logs (
|
|
||||||
log_id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
|
||||||
user_id INT,
|
|
||||||
ip_address VARCHAR(45),
|
|
||||||
request_url VARCHAR(500),
|
|
||||||
request_method VARCHAR(10),
|
|
||||||
response_code INT,
|
|
||||||
response_time_ms INT,
|
|
||||||
user_agent TEXT,
|
|
||||||
access_time DATETIME NOT NULL,
|
|
||||||
session_id VARCHAR(64),
|
|
||||||
|
|
||||||
KEY idx_user_id (user_id),
|
|
||||||
KEY idx_access_time (access_time),
|
|
||||||
KEY idx_response_code (response_code),
|
|
||||||
KEY idx_response_time (response_time_ms),
|
|
||||||
KEY idx_user_time (user_id, access_time),
|
|
||||||
KEY idx_code_time (response_code, access_time)
|
|
||||||
) ENGINE=InnoDB;
|
|
||||||
|
|
||||||
-- 插入测试数据(使用存储过程快速生成大量数据)
|
|
||||||
DELIMITER //
|
|
||||||
CREATE PROCEDURE GenerateTestData()
|
|
||||||
BEGIN
|
|
||||||
DECLARE i INT DEFAULT 1;
|
|
||||||
DECLARE max_records INT DEFAULT 100000;
|
|
||||||
|
|
||||||
-- 清空表
|
|
||||||
TRUNCATE TABLE orders;
|
|
||||||
TRUNCATE TABLE products;
|
|
||||||
TRUNCATE TABLE access_logs;
|
|
||||||
|
|
||||||
-- 生成商品数据
|
|
||||||
WHILE i <= 1000 DO
|
|
||||||
INSERT INTO products (
|
|
||||||
product_name, category_id, price, cost, stock_quantity,
|
|
||||||
weight, created_date, last_update_time, status
|
|
||||||
) VALUES (
|
|
||||||
CONCAT('产品_', i),
|
|
||||||
(i % 50) + 1,
|
|
||||||
ROUND(RAND() * 1000 + 10, 2),
|
|
||||||
ROUND(RAND() * 500 + 5, 2),
|
|
||||||
FLOOR(RAND() * 1000),
|
|
||||||
ROUND(RAND() * 10, 3),
|
|
||||||
DATE_SUB(CURDATE(), INTERVAL FLOOR(RAND() * 365) DAY),
|
|
||||||
DATE_SUB(NOW(), INTERVAL FLOOR(RAND() * 30) DAY),
|
|
||||||
IF(RAND() > 0.1, 1, 0)
|
|
||||||
);
|
|
||||||
SET i = i + 1;
|
|
||||||
END WHILE;
|
|
||||||
|
|
||||||
-- 生成订单数据
|
|
||||||
SET i = 1;
|
|
||||||
WHILE i <= max_records DO
|
|
||||||
INSERT INTO orders (
|
|
||||||
user_id, product_id, order_date, order_time, amount, quantity,
|
|
||||||
status, region, channel
|
|
||||||
) VALUES (
|
|
||||||
FLOOR(RAND() * 10000) + 1,
|
|
||||||
FLOOR(RAND() * 1000) + 1,
|
|
||||||
DATE_SUB(CURDATE(), INTERVAL FLOOR(RAND() * 365) DAY),
|
|
||||||
DATE_SUB(NOW(), INTERVAL FLOOR(RAND() * 365 * 24 * 60) MINUTE),
|
|
||||||
ROUND(RAND() * 1000 + 10, 2),
|
|
||||||
FLOOR(RAND() * 5) + 1,
|
|
||||||
ELT(FLOOR(RAND() * 5) + 1, 'pending', 'paid', 'shipped', 'delivered', 'cancelled'),
|
|
||||||
ELT(FLOOR(RAND() * 6) + 1, '北京', '上海', '广州', '深圳', '杭州', '成都'),
|
|
||||||
ELT(FLOOR(RAND() * 4) + 1, 'web', 'mobile', 'api', 'admin')
|
|
||||||
);
|
|
||||||
|
|
||||||
IF i % 10000 = 0 THEN
|
|
||||||
COMMIT;
|
|
||||||
END IF;
|
|
||||||
SET i = i + 1;
|
|
||||||
END WHILE;
|
|
||||||
|
|
||||||
-- 生成访问日志数据
|
|
||||||
SET i = 1;
|
|
||||||
WHILE i <= max_records DO
|
|
||||||
INSERT INTO access_logs (
|
|
||||||
user_id, ip_address, request_url, request_method,
|
|
||||||
response_code, response_time_ms, access_time, session_id
|
|
||||||
) VALUES (
|
|
||||||
IF(RAND() > 0.3, FLOOR(RAND() * 10000) + 1, NULL),
|
|
||||||
CONCAT(
|
|
||||||
FLOOR(RAND() * 255), '.',
|
|
||||||
FLOOR(RAND() * 255), '.',
|
|
||||||
FLOOR(RAND() * 255), '.',
|
|
||||||
FLOOR(RAND() * 255)
|
|
||||||
),
|
|
||||||
CONCAT('/api/v1/', ELT(FLOOR(RAND() * 5) + 1, 'users', 'orders', 'products', 'search', 'cart')),
|
|
||||||
ELT(FLOOR(RAND() * 4) + 1, 'GET', 'POST', 'PUT', 'DELETE'),
|
|
||||||
ELT(FLOOR(RAND() * 10) + 1, 200, 200, 200, 200, 200, 404, 500, 403, 401, 302),
|
|
||||||
FLOOR(RAND() * 5000) + 10,
|
|
||||||
DATE_SUB(NOW(), INTERVAL FLOOR(RAND() * 30 * 24 * 60) MINUTE),
|
|
||||||
MD5(CONCAT(i, RAND()))
|
|
||||||
);
|
|
||||||
|
|
||||||
IF i % 10000 = 0 THEN
|
|
||||||
COMMIT;
|
|
||||||
END IF;
|
|
||||||
SET i = i + 1;
|
|
||||||
END WHILE;
|
|
||||||
|
|
||||||
COMMIT;
|
|
||||||
END //
|
|
||||||
DELIMITER ;
|
|
||||||
|
|
||||||
-- 执行数据生成(注意:这会花费一些时间)
|
|
||||||
CALL GenerateTestData();
|
|
||||||
|
|
||||||
-- 更新表统计信息
|
|
||||||
ANALYZE TABLE orders, products, access_logs;
|
|
||||||
```
|
|
||||||
|
|
||||||
## RANGE 查询优化技巧
|
|
||||||
|
|
||||||
### 1. 日期范围查询优化
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- ❌ 低效的日期查询(无法使用索引)
|
|
||||||
SELECT * FROM orders
|
|
||||||
WHERE YEAR(order_date) = 2024 AND MONTH(order_date) = 1;
|
|
||||||
|
|
||||||
-- ❌ 函数导致全表扫描
|
|
||||||
SELECT * FROM orders
|
|
||||||
WHERE DATE_FORMAT(order_date, '%Y-%m') = '2024-01';
|
|
||||||
|
|
||||||
-- ✅ 高效的日期范围查询
|
|
||||||
SELECT * FROM orders
|
|
||||||
WHERE order_date >= '2024-01-01' AND order_date < '2024-02-01';
|
|
||||||
|
|
||||||
-- ✅ 使用 BETWEEN(包含边界)
|
|
||||||
SELECT * FROM orders
|
|
||||||
WHERE order_date BETWEEN '2024-01-01' AND '2024-01-31';
|
|
||||||
|
|
||||||
-- 执行计划对比
|
|
||||||
EXPLAIN FORMAT=JSON
|
|
||||||
SELECT * FROM orders
|
|
||||||
WHERE order_date >= '2024-01-01' AND order_date < '2024-02-01';
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 数值范围查询优化
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- ✅ 基本数值范围查询
|
|
||||||
SELECT * FROM products
|
|
||||||
WHERE price BETWEEN 100 AND 500;
|
|
||||||
|
|
||||||
-- ✅ 组合条件优化
|
|
||||||
SELECT * FROM products
|
|
||||||
WHERE price >= 100 AND price <= 500
|
|
||||||
AND status = 1;
|
|
||||||
|
|
||||||
-- 🔍 查看执行计划
|
|
||||||
EXPLAIN FORMAT=JSON
|
|
||||||
SELECT * FROM products
|
|
||||||
WHERE price >= 100 AND price <= 500 AND status = 1;
|
|
||||||
|
|
||||||
-- ✅ 多条件范围查询优化
|
|
||||||
SELECT * FROM orders
|
|
||||||
WHERE amount >= 100 AND amount <= 1000
|
|
||||||
AND order_date >= '2024-01-01' AND order_date <= '2024-12-31';
|
|
||||||
|
|
||||||
-- 🔍 分析索引选择
|
|
||||||
EXPLAIN FORMAT=JSON
|
|
||||||
SELECT * FROM orders
|
|
||||||
WHERE amount >= 100 AND amount <= 1000
|
|
||||||
AND order_date >= '2024-01-01' AND order_date <= '2024-12-31';
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. IN 查询优化
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- ✅ 基本 IN 查询
|
|
||||||
SELECT * FROM orders WHERE status IN ('paid', 'shipped', 'delivered');
|
|
||||||
|
|
||||||
-- ✅ 大量 IN 值的优化
|
|
||||||
SELECT * FROM orders
|
|
||||||
WHERE user_id IN (1, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50);
|
|
||||||
|
|
||||||
-- ❌ 避免过多的 IN 值(可能导致性能问题)
|
|
||||||
-- SELECT * FROM orders WHERE user_id IN (1,2,3,...,1000); -- 太多值
|
|
||||||
|
|
||||||
-- ✅ 使用 EXISTS 替代大量 IN 值
|
|
||||||
CREATE TEMPORARY TABLE temp_users (user_id INT PRIMARY KEY);
|
|
||||||
INSERT INTO temp_users VALUES (1), (5), (10), (15), (20);
|
|
||||||
|
|
||||||
SELECT o.* FROM orders o
|
|
||||||
WHERE EXISTS (SELECT 1 FROM temp_users t WHERE t.user_id = o.user_id);
|
|
||||||
|
|
||||||
-- ✅ 使用 JOIN 替代大量 IN 值
|
|
||||||
SELECT o.* FROM orders o
|
|
||||||
INNER JOIN temp_users t ON o.user_id = t.user_id;
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. 复合索引的 RANGE 查询优化
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- 创建复合索引
|
|
||||||
CREATE INDEX idx_user_status_date ON orders(user_id, status, order_date);
|
|
||||||
|
|
||||||
-- ✅ 充分利用复合索引(遵循最左前缀原则)
|
|
||||||
SELECT * FROM orders
|
|
||||||
WHERE user_id = 1001
|
|
||||||
AND status IN ('paid', 'shipped')
|
|
||||||
AND order_date >= '2024-01-01';
|
|
||||||
|
|
||||||
-- ✅ 部分利用复合索引
|
|
||||||
SELECT * FROM orders
|
|
||||||
WHERE user_id = 1001
|
|
||||||
AND order_date >= '2024-01-01';
|
|
||||||
|
|
||||||
-- ❌ 无法利用复合索引(跳过了前缀列)
|
|
||||||
SELECT * FROM orders
|
|
||||||
WHERE status = 'paid'
|
|
||||||
AND order_date >= '2024-01-01';
|
|
||||||
|
|
||||||
-- 🔍 验证索引使用情况
|
|
||||||
EXPLAIN FORMAT=JSON
|
|
||||||
SELECT * FROM orders
|
|
||||||
WHERE user_id = 1001
|
|
||||||
AND status IN ('paid', 'shipped')
|
|
||||||
AND order_date >= '2024-01-01';
|
|
||||||
```
|
|
||||||
|
|
||||||
## 索引策略和执行计划分析
|
|
||||||
|
|
||||||
### 1. 执行计划解读
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- 创建测试查询并分析执行计划
|
|
||||||
EXPLAIN FORMAT=JSON
|
|
||||||
SELECT * FROM orders
|
|
||||||
WHERE order_date BETWEEN '2024-01-01' AND '2024-01-31'
|
|
||||||
AND amount >= 100;
|
|
||||||
|
|
||||||
-- 关键信息解读:
|
|
||||||
-- "access_type": "range" - 表示使用了范围扫描
|
|
||||||
-- "key": "idx_order_date" - 使用的索引名称
|
|
||||||
-- "rows_examined_per_scan": 预估扫描行数
|
|
||||||
-- "cost_info": 查询成本信息
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 索引选择性分析
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- 分析不同字段的选择性
|
|
||||||
SELECT
|
|
||||||
'order_date' as column_name,
|
|
||||||
COUNT(DISTINCT order_date) as distinct_values,
|
|
||||||
COUNT(*) as total_rows,
|
|
||||||
COUNT(DISTINCT order_date) / COUNT(*) as selectivity
|
|
||||||
FROM orders
|
|
||||||
|
|
||||||
UNION ALL
|
|
||||||
|
|
||||||
SELECT
|
|
||||||
'amount' as column_name,
|
|
||||||
COUNT(DISTINCT amount) as distinct_values,
|
|
||||||
COUNT(*) as total_rows,
|
|
||||||
COUNT(DISTINCT amount) / COUNT(*) as selectivity
|
|
||||||
FROM orders
|
|
||||||
|
|
||||||
UNION ALL
|
|
||||||
|
|
||||||
SELECT
|
|
||||||
'status' as column_name,
|
|
||||||
COUNT(DISTINCT status) as distinct_values,
|
|
||||||
COUNT(*) as total_rows,
|
|
||||||
COUNT(DISTINCT status) / COUNT(*) as selectivity
|
|
||||||
FROM orders;
|
|
||||||
|
|
||||||
-- 根据选择性结果调整索引策略
|
|
||||||
-- 高选择性字段适合做索引前缀
|
|
||||||
-- 低选择性字段适合放在组合索引后面
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. 成本分析和索引选择
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- 强制使用不同索引对比性能
|
|
||||||
-- 使用日期索引
|
|
||||||
SELECT * FROM orders USE INDEX (idx_order_date)
|
|
||||||
WHERE order_date BETWEEN '2024-01-01' AND '2024-01-31'
|
|
||||||
AND amount >= 100;
|
|
||||||
|
|
||||||
-- 使用金额索引
|
|
||||||
SELECT * FROM orders USE INDEX (idx_amount)
|
|
||||||
WHERE order_date BETWEEN '2024-01-01' AND '2024-01-31'
|
|
||||||
AND amount >= 100;
|
|
||||||
|
|
||||||
-- 使用复合索引
|
|
||||||
SELECT * FROM orders USE INDEX (idx_amount_date)
|
|
||||||
WHERE order_date BETWEEN '2024-01-01' AND '2024-01-31'
|
|
||||||
AND amount >= 100;
|
|
||||||
|
|
||||||
-- 让优化器自动选择
|
|
||||||
SELECT * FROM orders
|
|
||||||
WHERE order_date BETWEEN '2024-01-01' AND '2024-01-31'
|
|
||||||
AND amount >= 100;
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. 索引提示的使用
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- 强制使用特定索引
|
|
||||||
SELECT * FROM orders FORCE INDEX (idx_order_date)
|
|
||||||
WHERE order_date >= '2024-01-01' AND amount > 100;
|
|
||||||
|
|
||||||
-- 忽略某个索引
|
|
||||||
SELECT * FROM orders IGNORE INDEX (idx_amount)
|
|
||||||
WHERE order_date >= '2024-01-01' AND amount > 100;
|
|
||||||
|
|
||||||
-- 建议使用某个索引
|
|
||||||
SELECT * FROM orders USE INDEX (idx_amount_date)
|
|
||||||
WHERE order_date >= '2024-01-01' AND amount > 100;
|
|
||||||
```
|
|
||||||
|
|
||||||
## 实际开发场景案例
|
|
||||||
|
|
||||||
### 案例1:电商订单查询系统
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- 需求:查询某用户在指定时间段内的订单,按金额排序
|
|
||||||
-- 原始查询(可能性能较差)
|
|
||||||
SELECT * FROM orders
|
|
||||||
WHERE user_id = 1001
|
|
||||||
AND order_date BETWEEN '2024-01-01' AND '2024-01-31'
|
|
||||||
ORDER BY amount DESC;
|
|
||||||
|
|
||||||
-- 优化方案1:创建专门的复合索引
|
|
||||||
CREATE INDEX idx_user_date_amount ON orders(user_id, order_date, amount DESC);
|
|
||||||
|
|
||||||
-- 优化后的查询
|
|
||||||
SELECT * FROM orders
|
|
||||||
WHERE user_id = 1001
|
|
||||||
AND order_date BETWEEN '2024-01-01' AND '2024-01-31'
|
|
||||||
ORDER BY amount DESC;
|
|
||||||
|
|
||||||
-- 验证优化效果
|
|
||||||
EXPLAIN FORMAT=JSON
|
|
||||||
SELECT * FROM orders
|
|
||||||
WHERE user_id = 1001
|
|
||||||
AND order_date BETWEEN '2024-01-01' AND '2024-01-31'
|
|
||||||
ORDER BY amount DESC;
|
|
||||||
|
|
||||||
-- 如果需要分页
|
|
||||||
SELECT * FROM orders
|
|
||||||
WHERE user_id = 1001
|
|
||||||
AND order_date BETWEEN '2024-01-01' AND '2024-01-31'
|
|
||||||
ORDER BY amount DESC
|
|
||||||
LIMIT 20 OFFSET 0;
|
|
||||||
```
|
|
||||||
|
|
||||||
### 案例2:实时数据分析查询
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- 需求:按小时统计最近24小时的订单数量和金额
|
|
||||||
-- 原始查询
|
|
||||||
SELECT
|
|
||||||
DATE_FORMAT(order_time, '%Y-%m-%d %H:00:00') as hour_bucket,
|
|
||||||
COUNT(*) as order_count,
|
|
||||||
SUM(amount) as total_amount
|
|
||||||
FROM orders
|
|
||||||
WHERE order_time >= DATE_SUB(NOW(), INTERVAL 24 HOUR)
|
|
||||||
GROUP BY DATE_FORMAT(order_time, '%Y-%m-%d %H:00:00')
|
|
||||||
ORDER BY hour_bucket;
|
|
||||||
|
|
||||||
-- 优化方案:创建时间索引并使用覆盖索引
|
|
||||||
CREATE INDEX idx_order_time_amount ON orders(order_time, amount);
|
|
||||||
|
|
||||||
-- 优化查询(避免函数计算)
|
|
||||||
SELECT
|
|
||||||
FROM_UNIXTIME(UNIX_TIMESTAMP(order_time) - UNIX_TIMESTAMP(order_time) % 3600) as hour_bucket,
|
|
||||||
COUNT(*) as order_count,
|
|
||||||
SUM(amount) as total_amount
|
|
||||||
FROM orders
|
|
||||||
WHERE order_time >= DATE_SUB(NOW(), INTERVAL 24 HOUR)
|
|
||||||
GROUP BY FROM_UNIXTIME(UNIX_TIMESTAMP(order_time) - UNIX_TIMESTAMP(order_time) % 3600)
|
|
||||||
ORDER BY hour_bucket;
|
|
||||||
|
|
||||||
-- 进一步优化:预计算小时桶
|
|
||||||
ALTER TABLE orders ADD COLUMN hour_bucket DATETIME GENERATED ALWAYS AS
|
|
||||||
(FROM_UNIXTIME(UNIX_TIMESTAMP(order_time) - UNIX_TIMESTAMP(order_time) % 3600)) STORED;
|
|
||||||
|
|
||||||
CREATE INDEX idx_hour_bucket_amount ON orders(hour_bucket, amount);
|
|
||||||
|
|
||||||
-- 最优化查询
|
|
||||||
SELECT
|
|
||||||
hour_bucket,
|
|
||||||
COUNT(*) as order_count,
|
|
||||||
SUM(amount) as total_amount
|
|
||||||
FROM orders
|
|
||||||
WHERE hour_bucket >= DATE_SUB(DATE_SUB(NOW(), INTERVAL MINUTE(NOW()) MINUTE), INTERVAL 23 HOUR)
|
|
||||||
GROUP BY hour_bucket
|
|
||||||
ORDER BY hour_bucket;
|
|
||||||
```
|
|
||||||
|
|
||||||
### 案例3:用户行为分析
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- 需求:分析响应时间在不同区间的请求分布
|
|
||||||
-- 创建响应时间分析的优化索引
|
|
||||||
CREATE INDEX idx_response_time_code ON access_logs(response_time_ms, response_code);
|
|
||||||
|
|
||||||
-- 分析查询
|
|
||||||
SELECT
|
|
||||||
CASE
|
|
||||||
WHEN response_time_ms < 100 THEN '< 100ms'
|
|
||||||
WHEN response_time_ms < 500 THEN '100-500ms'
|
|
||||||
WHEN response_time_ms < 1000 THEN '500ms-1s'
|
|
||||||
WHEN response_time_ms < 3000 THEN '1-3s'
|
|
||||||
ELSE '> 3s'
|
|
||||||
END as response_time_bucket,
|
|
||||||
COUNT(*) as request_count,
|
|
||||||
COUNT(CASE WHEN response_code = 200 THEN 1 END) as success_count,
|
|
||||||
ROUND(AVG(response_time_ms), 2) as avg_response_time
|
|
||||||
FROM access_logs
|
|
||||||
WHERE access_time >= DATE_SUB(NOW(), INTERVAL 7 DAY)
|
|
||||||
GROUP BY
|
|
||||||
CASE
|
|
||||||
WHEN response_time_ms < 100 THEN '< 100ms'
|
|
||||||
WHEN response_time_ms < 500 THEN '100-500ms'
|
|
||||||
WHEN response_time_ms < 1000 THEN '500ms-1s'
|
|
||||||
WHEN response_time_ms < 3000 THEN '1-3s'
|
|
||||||
ELSE '> 3s'
|
|
||||||
END
|
|
||||||
ORDER BY
|
|
||||||
CASE
|
|
||||||
WHEN response_time_ms < 100 THEN 1
|
|
||||||
WHEN response_time_ms < 500 THEN 2
|
|
||||||
WHEN response_time_ms < 1000 THEN 3
|
|
||||||
WHEN response_time_ms < 3000 THEN 4
|
|
||||||
ELSE 5
|
|
||||||
END;
|
|
||||||
|
|
||||||
-- 优化版本:使用多个简单的RANGE查询代替复杂CASE
|
|
||||||
SELECT '< 100ms' as bucket, COUNT(*) as count FROM access_logs
|
|
||||||
WHERE access_time >= DATE_SUB(NOW(), INTERVAL 7 DAY) AND response_time_ms < 100
|
|
||||||
UNION ALL
|
|
||||||
SELECT '100-500ms', COUNT(*) FROM access_logs
|
|
||||||
WHERE access_time >= DATE_SUB(NOW(), INTERVAL 7 DAY) AND response_time_ms >= 100 AND response_time_ms < 500
|
|
||||||
UNION ALL
|
|
||||||
SELECT '500ms-1s', COUNT(*) FROM access_logs
|
|
||||||
WHERE access_time >= DATE_SUB(NOW(), INTERVAL 7 DAY) AND response_time_ms >= 500 AND response_time_ms < 1000
|
|
||||||
UNION ALL
|
|
||||||
SELECT '1-3s', COUNT(*) FROM access_logs
|
|
||||||
WHERE access_time >= DATE_SUB(NOW(), INTERVAL 7 DAY) AND response_time_ms >= 1000 AND response_time_ms < 3000
|
|
||||||
UNION ALL
|
|
||||||
SELECT '> 3s', COUNT(*) FROM access_logs
|
|
||||||
WHERE access_time >= DATE_SUB(NOW(), INTERVAL 7 DAY) AND response_time_ms >= 3000;
|
|
||||||
```
|
|
||||||
|
|
||||||
## 分区表与 RANGE 优化
|
|
||||||
|
|
||||||
### 1. 基于日期的范围分区
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- 创建按月份分区的订单表
|
|
||||||
CREATE TABLE orders_partitioned (
|
|
||||||
order_id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
|
||||||
user_id INT NOT NULL,
|
|
||||||
product_id INT NOT NULL,
|
|
||||||
order_date DATE NOT NULL,
|
|
||||||
order_time DATETIME NOT NULL,
|
|
||||||
amount DECIMAL(10,2) NOT NULL,
|
|
||||||
status ENUM('pending', 'paid', 'shipped', 'delivered', 'cancelled') NOT NULL,
|
|
||||||
region VARCHAR(50) NOT NULL,
|
|
||||||
|
|
||||||
KEY idx_user_id (user_id),
|
|
||||||
KEY idx_order_date (order_date),
|
|
||||||
KEY idx_amount (amount),
|
|
||||||
KEY idx_user_date (user_id, order_date)
|
|
||||||
) ENGINE=InnoDB
|
|
||||||
PARTITION BY RANGE (YEAR(order_date) * 100 + MONTH(order_date)) (
|
|
||||||
PARTITION p202401 VALUES LESS THAN (202402),
|
|
||||||
PARTITION p202402 VALUES LESS THAN (202403),
|
|
||||||
PARTITION p202403 VALUES LESS THAN (202404),
|
|
||||||
PARTITION p202404 VALUES LESS THAN (202405),
|
|
||||||
PARTITION p202405 VALUES LESS THAN (202406),
|
|
||||||
PARTITION p202406 VALUES LESS THAN (202407),
|
|
||||||
PARTITION p202407 VALUES LESS THAN (202408),
|
|
||||||
PARTITION p202408 VALUES LESS THAN (202409),
|
|
||||||
PARTITION p202409 VALUES LESS THAN (202410),
|
|
||||||
PARTITION p202410 VALUES LESS THAN (202411),
|
|
||||||
PARTITION p202411 VALUES LESS THAN (202412),
|
|
||||||
PARTITION p202412 VALUES LESS THAN (202501),
|
|
||||||
PARTITION p_future VALUES LESS THAN MAXVALUE
|
|
||||||
);
|
|
||||||
|
|
||||||
-- 分区表的RANGE查询优化
|
|
||||||
-- ✅ 可以利用分区剪枝的查询
|
|
||||||
SELECT * FROM orders_partitioned
|
|
||||||
WHERE order_date BETWEEN '2024-03-01' AND '2024-03-31';
|
|
||||||
|
|
||||||
-- 查看分区剪枝效果
|
|
||||||
EXPLAIN PARTITIONS
|
|
||||||
SELECT * FROM orders_partitioned
|
|
||||||
WHERE order_date BETWEEN '2024-03-01' AND '2024-03-31';
|
|
||||||
|
|
||||||
-- ❌ 无法利用分区剪枝的查询(使用函数)
|
|
||||||
SELECT * FROM orders_partitioned
|
|
||||||
WHERE YEAR(order_date) = 2024 AND MONTH(order_date) = 3;
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 基于数值的范围分区
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- 创建按用户ID范围分区的表
|
|
||||||
CREATE TABLE user_activities_partitioned (
|
|
||||||
activity_id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
|
||||||
user_id INT NOT NULL,
|
|
||||||
activity_type VARCHAR(50) NOT NULL,
|
|
||||||
activity_time DATETIME NOT NULL,
|
|
||||||
activity_data JSON,
|
|
||||||
|
|
||||||
KEY idx_user_time (user_id, activity_time),
|
|
||||||
KEY idx_activity_time (activity_time)
|
|
||||||
) ENGINE=InnoDB
|
|
||||||
PARTITION BY RANGE (user_id) (
|
|
||||||
PARTITION p0 VALUES LESS THAN (1000),
|
|
||||||
PARTITION p1 VALUES LESS THAN (5000),
|
|
||||||
PARTITION p2 VALUES LESS THAN (10000),
|
|
||||||
PARTITION p3 VALUES LESS THAN (50000),
|
|
||||||
PARTITION p4 VALUES LESS THAN MAXVALUE
|
|
||||||
);
|
|
||||||
|
|
||||||
-- 针对特定用户范围的查询优化
|
|
||||||
SELECT * FROM user_activities_partitioned
|
|
||||||
WHERE user_id BETWEEN 1000 AND 4999
|
|
||||||
AND activity_time >= '2024-01-01';
|
|
||||||
|
|
||||||
-- 查看执行计划
|
|
||||||
EXPLAIN PARTITIONS
|
|
||||||
SELECT * FROM user_activities_partitioned
|
|
||||||
WHERE user_id BETWEEN 1000 AND 4999
|
|
||||||
AND activity_time >= '2024-01-01';
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. 分区表维护
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- 添加新分区
|
|
||||||
ALTER TABLE orders_partitioned
|
|
||||||
ADD PARTITION (PARTITION p202501 VALUES LESS THAN (202502));
|
|
||||||
|
|
||||||
-- 删除旧分区(删除数据)
|
|
||||||
ALTER TABLE orders_partitioned DROP PARTITION p202401;
|
|
||||||
|
|
||||||
-- 重组分区
|
|
||||||
ALTER TABLE orders_partitioned
|
|
||||||
REORGANIZE PARTITION p_future INTO (
|
|
||||||
PARTITION p202502 VALUES LESS THAN (202503),
|
|
||||||
PARTITION p202503 VALUES LESS THAN (202504),
|
|
||||||
PARTITION p_future VALUES LESS THAN MAXVALUE
|
|
||||||
);
|
|
||||||
|
|
||||||
-- 查看分区信息
|
|
||||||
SELECT
|
|
||||||
PARTITION_NAME,
|
|
||||||
TABLE_ROWS,
|
|
||||||
DATA_LENGTH,
|
|
||||||
PARTITION_DESCRIPTION
|
|
||||||
FROM information_schema.PARTITIONS
|
|
||||||
WHERE TABLE_SCHEMA = DATABASE()
|
|
||||||
AND TABLE_NAME = 'orders_partitioned';
|
|
||||||
```
|
|
||||||
|
|
||||||
## 性能监控和调优
|
|
||||||
|
|
||||||
### 1. 监控 RANGE 查询性能
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- 查看慢查询中的RANGE查询
|
|
||||||
SELECT
|
|
||||||
sql_text,
|
|
||||||
exec_count,
|
|
||||||
avg_timer_wait/1000000000000 as avg_time_sec,
|
|
||||||
sum_rows_examined/exec_count as avg_rows_examined,
|
|
||||||
sum_rows_sent/exec_count as avg_rows_sent,
|
|
||||||
(sum_rows_examined/exec_count) / (sum_rows_sent/exec_count) as examine_ratio
|
|
||||||
FROM performance_schema.events_statements_summary_by_digest
|
|
||||||
WHERE sql_text LIKE '%BETWEEN%'
|
|
||||||
OR sql_text LIKE '%>%'
|
|
||||||
OR sql_text LIKE '%<%'
|
|
||||||
ORDER BY avg_timer_wait DESC
|
|
||||||
LIMIT 10;
|
|
||||||
|
|
||||||
-- 监控索引使用情况
|
|
||||||
SELECT
|
|
||||||
object_schema,
|
|
||||||
object_name,
|
|
||||||
index_name,
|
|
||||||
count_star as usage_count,
|
|
||||||
sum_timer_wait/1000000000000 as total_time_sec
|
|
||||||
FROM performance_schema.table_io_waits_summary_by_index_usage
|
|
||||||
WHERE object_schema = DATABASE()
|
|
||||||
AND count_star > 0
|
|
||||||
ORDER BY sum_timer_wait DESC;
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 性能测试脚本
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- 创建性能测试存储过程
|
|
||||||
DELIMITER //
|
|
||||||
CREATE PROCEDURE TestRangeQueryPerformance()
|
|
||||||
BEGIN
|
|
||||||
DECLARE start_time DATETIME;
|
|
||||||
DECLARE end_time DATETIME;
|
|
||||||
DECLARE duration_ms INT;
|
|
||||||
|
|
||||||
-- 测试1:日期范围查询
|
|
||||||
SET start_time = NOW(6);
|
|
||||||
SELECT COUNT(*) FROM orders
|
|
||||||
WHERE order_date BETWEEN '2024-01-01' AND '2024-01-31';
|
|
||||||
SET end_time = NOW(6);
|
|
||||||
SET duration_ms = TIMESTAMPDIFF(MICROSECOND, start_time, end_time) / 1000;
|
|
||||||
SELECT 'Date Range Query' as test_name, duration_ms as duration_ms;
|
|
||||||
|
|
||||||
-- 测试2:数值范围查询
|
|
||||||
SET start_time = NOW(6);
|
|
||||||
SELECT COUNT(*) FROM orders
|
|
||||||
WHERE amount BETWEEN 100 AND 500;
|
|
||||||
SET end_time = NOW(6);
|
|
||||||
SET duration_ms = TIMESTAMPDIFF(MICROSECOND, start_time, end_time) / 1000;
|
|
||||||
SELECT 'Amount Range Query' as test_name, duration_ms as duration_ms;
|
|
||||||
|
|
||||||
-- 测试3:复合条件查询
|
|
||||||
SET start_time = NOW(6);
|
|
||||||
SELECT COUNT(*) FROM orders
|
|
||||||
WHERE order_date BETWEEN '2024-01-01' AND '2024-01-31'
|
|
||||||
AND amount BETWEEN 100 AND 500;
|
|
||||||
SET end_time = NOW(6);
|
|
||||||
SET duration_ms = TIMESTAMPDIFF(MICROSECOND, start_time, end_time) / 1000;
|
|
||||||
SELECT 'Combined Range Query' as test_name, duration_ms as duration_ms;
|
|
||||||
|
|
||||||
END //
|
|
||||||
DELIMITER ;
|
|
||||||
|
|
||||||
-- 执行性能测试
|
|
||||||
CALL TestRangeQueryPerformance();
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. 自动化索引建议
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- 分析表的查询模式,建议索引
|
|
||||||
CREATE VIEW range_query_analysis AS
|
|
||||||
SELECT
|
|
||||||
'orders' as table_name,
|
|
||||||
'order_date' as column_name,
|
|
||||||
COUNT(DISTINCT order_date) as distinct_values,
|
|
||||||
COUNT(*) as total_rows,
|
|
||||||
COUNT(DISTINCT order_date) / COUNT(*) as selectivity,
|
|
||||||
CASE
|
|
||||||
WHEN COUNT(DISTINCT order_date) / COUNT(*) > 0.1 THEN '高选择性,建议单独建索引'
|
|
||||||
WHEN COUNT(DISTINCT order_date) / COUNT(*) > 0.01 THEN '中等选择性,建议组合索引'
|
|
||||||
ELSE '低选择性,不建议建索引'
|
|
||||||
END as index_recommendation
|
|
||||||
FROM orders
|
|
||||||
|
|
||||||
UNION ALL
|
|
||||||
|
|
||||||
SELECT
|
|
||||||
'orders' as table_name,
|
|
||||||
'amount' as column_name,
|
|
||||||
COUNT(DISTINCT amount) as distinct_values,
|
|
||||||
COUNT(*) as total_rows,
|
|
||||||
COUNT(DISTINCT amount) / COUNT(*) as selectivity,
|
|
||||||
CASE
|
|
||||||
WHEN COUNT(DISTINCT amount) / COUNT(*) > 0.1 THEN '高选择性,建议单独建索引'
|
|
||||||
WHEN COUNT(DISTINCT amount) / COUNT(*) > 0.01 THEN '中等选择性,建议组合索引'
|
|
||||||
ELSE '低选择性,不建议建索引'
|
|
||||||
END as index_recommendation
|
|
||||||
FROM orders;
|
|
||||||
|
|
||||||
-- 查看分析结果
|
|
||||||
SELECT * FROM range_query_analysis;
|
|
||||||
```
|
|
||||||
|
|
||||||
## 常见问题和最佳实践
|
|
||||||
|
|
||||||
### 1. 常见性能陷阱
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- ❌ 陷阱1:在WHERE条件中使用函数
|
|
||||||
-- 错误示例
|
|
||||||
SELECT * FROM orders WHERE YEAR(order_date) = 2024;
|
|
||||||
SELECT * FROM orders WHERE DATE_ADD(order_date, INTERVAL 1 DAY) = '2024-01-02';
|
|
||||||
|
|
||||||
-- ✅ 正确做法
|
|
||||||
SELECT * FROM orders WHERE order_date >= '2024-01-01' AND order_date < '2025-01-01';
|
|
||||||
SELECT * FROM orders WHERE order_date = '2024-01-01';
|
|
||||||
|
|
||||||
-- ❌ 陷阱2:数据类型不匹配导致的隐式转换
|
|
||||||
-- 错误示例(假设user_id是INT类型)
|
|
||||||
SELECT * FROM orders WHERE user_id = '1001'; -- 字符串比较整数
|
|
||||||
|
|
||||||
-- ✅ 正确做法
|
|
||||||
SELECT * FROM orders WHERE user_id = 1001;
|
|
||||||
|
|
||||||
-- ❌ 陷阱3:LIKE查询的误用
|
|
||||||
-- 错误示例
|
|
||||||
SELECT * FROM orders WHERE order_id LIKE '1001%'; -- 应该用数值比较
|
|
||||||
|
|
||||||
-- ✅ 正确做法
|
|
||||||
SELECT * FROM orders WHERE order_id >= 1001 AND order_id < 1002;
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 索引设计最佳实践
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- ✅ 最佳实践1:根据查询频率设计索引
|
|
||||||
-- 高频查询的索引设计
|
|
||||||
CREATE INDEX idx_user_status_date ON orders(user_id, status, order_date);
|
|
||||||
CREATE INDEX idx_date_amount ON orders(order_date, amount);
|
|
||||||
|
|
||||||
-- ✅ 最佳实践2:考虑排序需求的索引设计
|
|
||||||
-- 如果经常需要按金额降序排序
|
|
||||||
CREATE INDEX idx_date_amount_desc ON orders(order_date, amount DESC);
|
|
||||||
|
|
||||||
-- ✅ 最佳实践3:覆盖索引的设计
|
|
||||||
-- 如果只查询特定列,可以设计覆盖索引
|
|
||||||
CREATE INDEX idx_covering_order_summary ON orders(order_date, user_id, amount, status);
|
|
||||||
|
|
||||||
-- 该查询只需访问索引,无需回表
|
|
||||||
SELECT user_id, amount, status FROM orders
|
|
||||||
WHERE order_date BETWEEN '2024-01-01' AND '2024-01-31';
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. 查询优化检查清单
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- 优化检查清单
|
|
||||||
/*
|
|
||||||
1. ✅ 避免在WHERE条件中使用函数
|
|
||||||
2. ✅ 确保数据类型匹配,避免隐式转换
|
|
||||||
3. ✅ 合理使用复合索引,遵循最左前缀原则
|
|
||||||
4. ✅ 考虑使用覆盖索引减少回表操作
|
|
||||||
5. ✅ 对于大量IN值,考虑使用JOIN或EXISTS
|
|
||||||
6. ✅ 利用分区表的分区剪枝特性
|
|
||||||
7. ✅ 定期分析表统计信息
|
|
||||||
8. ✅ 监控慢查询日志和执行计划
|
|
||||||
9. ✅ 考虑查询结果的数据量,适当使用LIMIT
|
|
||||||
10. ✅ 根据业务特点选择合适的索引策略
|
|
||||||
*/
|
|
||||||
|
|
||||||
-- 索引效果验证模板
|
|
||||||
EXPLAIN FORMAT=JSON SELECT * FROM table_name WHERE conditions;
|
|
||||||
|
|
||||||
-- 性能对比模板
|
|
||||||
SELECT
|
|
||||||
BENCHMARK(10000, (SELECT COUNT(*) FROM table_name WHERE conditions)) as execution_time;
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. 实际生产环境建议
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- 生产环境优化建议
|
|
||||||
|
|
||||||
-- 1. 定期收集表统计信息
|
|
||||||
ANALYZE TABLE orders, products, access_logs;
|
|
||||||
|
|
||||||
-- 2. 监控索引使用情况
|
|
||||||
CREATE EVENT check_index_usage
|
|
||||||
ON SCHEDULE EVERY 1 DAY
|
|
||||||
DO
|
|
||||||
INSERT INTO index_usage_log
|
|
||||||
SELECT
|
|
||||||
NOW() as check_time,
|
|
||||||
object_name,
|
|
||||||
index_name,
|
|
||||||
count_star as usage_count
|
|
||||||
FROM performance_schema.table_io_waits_summary_by_index_usage
|
|
||||||
WHERE object_schema = DATABASE()
|
|
||||||
AND count_star = 0; -- 未使用的索引
|
|
||||||
|
|
||||||
-- 3. 自动优化建议
|
|
||||||
DELIMITER //
|
|
||||||
CREATE PROCEDURE GenerateOptimizationSuggestions()
|
|
||||||
BEGIN
|
|
||||||
-- 查找可能需要索引的列
|
|
||||||
SELECT
|
|
||||||
'Consider adding index' as suggestion,
|
|
||||||
CONCAT('CREATE INDEX idx_', table_name, '_', column_name,
|
|
||||||
' ON ', table_name, '(', column_name, ');') as sql_statement
|
|
||||||
FROM (
|
|
||||||
SELECT 'orders' as table_name, 'region' as column_name
|
|
||||||
UNION ALL
|
|
||||||
SELECT 'products', 'category_id'
|
|
||||||
UNION ALL
|
|
||||||
SELECT 'access_logs', 'user_agent'
|
|
||||||
) potential_indexes;
|
|
||||||
|
|
||||||
-- 查找可能需要删除的索引
|
|
||||||
SELECT
|
|
||||||
'Consider dropping unused index' as suggestion,
|
|
||||||
CONCAT('DROP INDEX ', index_name, ' ON ', object_name, ';') as sql_statement
|
|
||||||
FROM performance_schema.table_io_waits_summary_by_index_usage
|
|
||||||
WHERE object_schema = DATABASE()
|
|
||||||
AND count_star = 0
|
|
||||||
AND index_name != 'PRIMARY';
|
|
||||||
END //
|
|
||||||
DELIMITER ;
|
|
||||||
|
|
||||||
-- 调用优化建议
|
|
||||||
CALL GenerateOptimizationSuggestions();
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**总结:**
|
|
||||||
|
|
||||||
RANGE 查询优化是数据库性能优化的重要组成部分,关键要点包括:
|
|
||||||
|
|
||||||
1. **理解原理**:掌握 RANGE 查询的执行机制和索引选择逻辑
|
|
||||||
2. **索引设计**:根据查询模式设计合适的单列和复合索引
|
|
||||||
3. **查询优化**:避免函数使用,保证数据类型匹配,合理使用索引提示
|
|
||||||
4. **分区策略**:对于大表,考虑使用分区表提高查询效率
|
|
||||||
5. **性能监控**:建立完善的监控体系,持续优化查询性能
|
|
||||||
6. **最佳实践**:遵循数据库设计和查询优化的最佳实践
|
|
||||||
|
|
||||||
通过系统的 RANGE 查询优化,可以显著提升数据库查询性能,改善用户体验。
|
|
||||||
@ -1,532 +0,0 @@
|
|||||||
# MySQL UNION 语法使用文档
|
|
||||||
|
|
||||||
## 目录
|
|
||||||
1. [UNION 基础语法](#union-基础语法)
|
|
||||||
2. [UNION 规则和限制](#union-规则和限制)
|
|
||||||
3. [示例数据准备](#示例数据准备)
|
|
||||||
4. [UNION 基础使用示例](#union-基础使用示例)
|
|
||||||
5. [UNION vs UNION ALL](#union-vs-union-all)
|
|
||||||
6. [复杂查询示例](#复杂查询示例)
|
|
||||||
7. [最佳实践和注意事项](#最佳实践和注意事项)
|
|
||||||
|
|
||||||
## UNION 基础语法
|
|
||||||
|
|
||||||
UNION 用于合并两个或多个 SELECT 语句的结果集。
|
|
||||||
|
|
||||||
```sql
|
|
||||||
SELECT column1, column2, ... FROM table1
|
|
||||||
UNION [ALL]
|
|
||||||
SELECT column1, column2, ... FROM table2
|
|
||||||
[UNION [ALL]
|
|
||||||
SELECT column1, column2, ... FROM table3]
|
|
||||||
...
|
|
||||||
[ORDER BY column_name]
|
|
||||||
```
|
|
||||||
|
|
||||||
## UNION 规则和限制
|
|
||||||
|
|
||||||
1. **列数相同**:每个 SELECT 语句必须拥有相同数量的列
|
|
||||||
2. **数据类型兼容**:对应位置的列必须具有兼容的数据类型
|
|
||||||
3. **列名**:结果集使用第一个 SELECT 语句的列名
|
|
||||||
4. **去重**:UNION 默认去除重复记录,UNION ALL 保留所有记录
|
|
||||||
5. **ORDER BY**:只能在最后使用,对整个结果集排序
|
|
||||||
|
|
||||||
## 示例数据准备
|
|
||||||
|
|
||||||
### 创建示例表
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- 创建员工表
|
|
||||||
CREATE TABLE employees (
|
|
||||||
id INT PRIMARY KEY,
|
|
||||||
name VARCHAR(50),
|
|
||||||
department VARCHAR(30),
|
|
||||||
salary DECIMAL(10,2),
|
|
||||||
city VARCHAR(30)
|
|
||||||
);
|
|
||||||
|
|
||||||
-- 创建前员工表
|
|
||||||
CREATE TABLE former_employees (
|
|
||||||
id INT PRIMARY KEY,
|
|
||||||
name VARCHAR(50),
|
|
||||||
department VARCHAR(30),
|
|
||||||
last_salary DECIMAL(10,2),
|
|
||||||
city VARCHAR(30),
|
|
||||||
leave_date DATE
|
|
||||||
);
|
|
||||||
|
|
||||||
-- 创建管理层表
|
|
||||||
CREATE TABLE managers (
|
|
||||||
id INT PRIMARY KEY,
|
|
||||||
name VARCHAR(50),
|
|
||||||
level VARCHAR(20),
|
|
||||||
salary DECIMAL(10,2),
|
|
||||||
region VARCHAR(30)
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
### 插入示例数据
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- 插入员工数据
|
|
||||||
INSERT INTO employees (id, name, department, salary, city) VALUES
|
|
||||||
(1, '张三', '技术部', 8000.00, '北京'),
|
|
||||||
(2, '李四', '销售部', 6000.00, '上海'),
|
|
||||||
(3, '王五', '技术部', 9000.00, '深圳'),
|
|
||||||
(4, '赵六', '人事部', 5500.00, '北京'),
|
|
||||||
(5, '钱七', '财务部', 7000.00, '广州'),
|
|
||||||
(6, '孙八', '技术部', 8500.00, '杭州'),
|
|
||||||
(7, '周九', '市场部', 6500.00, '成都');
|
|
||||||
|
|
||||||
-- 插入前员工数据
|
|
||||||
INSERT INTO former_employees (id, name, department, last_salary, city, leave_date) VALUES
|
|
||||||
(8, '吴十', '技术部', 7500.00, '北京', '2023-08-15'),
|
|
||||||
(9, '郑一', '销售部', 5800.00, '上海', '2023-09-20'),
|
|
||||||
(10, '王五', '技术部', 8000.00, '深圳', '2023-07-10'),
|
|
||||||
(11, '刘二', '人事部', 5200.00, '广州', '2023-10-05');
|
|
||||||
|
|
||||||
-- 插入管理层数据
|
|
||||||
INSERT INTO managers (id, name, level, salary, region) VALUES
|
|
||||||
(12, '陈总', '高级经理', 15000.00, '华北'),
|
|
||||||
(13, '林总', '部门经理', 12000.00, '华东'),
|
|
||||||
(14, '张三', '项目经理', 10000.00, '华南'),
|
|
||||||
(15, '黄总', '区域经理', 11000.00, '华西');
|
|
||||||
```
|
|
||||||
|
|
||||||
## UNION 基础使用示例
|
|
||||||
|
|
||||||
### 1. 简单合并查询
|
|
||||||
|
|
||||||
合并当前员工和前员工的姓名:
|
|
||||||
|
|
||||||
```sql
|
|
||||||
SELECT name FROM employees
|
|
||||||
UNION
|
|
||||||
SELECT name FROM former_employees;
|
|
||||||
```
|
|
||||||
|
|
||||||
**结果:**
|
|
||||||
```
|
|
||||||
+------+
|
|
||||||
| name |
|
|
||||||
+------+
|
|
||||||
| 张三 |
|
|
||||||
| 李四 |
|
|
||||||
| 王五 |
|
|
||||||
| 赵六 |
|
|
||||||
| 钱七 |
|
|
||||||
| 孙八 |
|
|
||||||
| 周九 |
|
|
||||||
| 吴十 |
|
|
||||||
| 郑一 |
|
|
||||||
| 刘二 |
|
|
||||||
+------+
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 多列合并查询
|
|
||||||
|
|
||||||
合并员工和管理层的姓名和薪资:
|
|
||||||
|
|
||||||
```sql
|
|
||||||
SELECT name, salary FROM employees
|
|
||||||
UNION
|
|
||||||
SELECT name, salary FROM managers
|
|
||||||
ORDER BY salary DESC;
|
|
||||||
```
|
|
||||||
|
|
||||||
**结果:**
|
|
||||||
```
|
|
||||||
+------+----------+
|
|
||||||
| name | salary |
|
|
||||||
+------+----------+
|
|
||||||
| 陈总 | 15000.00 |
|
|
||||||
| 林总 | 12000.00 |
|
|
||||||
| 黄总 | 11000.00 |
|
|
||||||
| 张三 | 10000.00 |
|
|
||||||
| 王五 | 9000.00 |
|
|
||||||
| 孙八 | 8500.00 |
|
|
||||||
| 张三 | 8000.00 |
|
|
||||||
| 钱七 | 7000.00 |
|
|
||||||
| 周九 | 6500.00 |
|
|
||||||
| 李四 | 6000.00 |
|
|
||||||
| 赵六 | 5500.00 |
|
|
||||||
+------+----------+
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. 使用别名统一列名
|
|
||||||
|
|
||||||
```sql
|
|
||||||
SELECT name, department AS work_area, salary FROM employees
|
|
||||||
UNION
|
|
||||||
SELECT name, region AS work_area, salary FROM managers
|
|
||||||
ORDER BY work_area;
|
|
||||||
```
|
|
||||||
|
|
||||||
**结果:**
|
|
||||||
```
|
|
||||||
+------+-----------+----------+
|
|
||||||
| name | work_area | salary |
|
|
||||||
+------+-----------+----------+
|
|
||||||
| 钱七 | 财务部 | 7000.00 |
|
|
||||||
| 陈总 | 华北 | 15000.00 |
|
|
||||||
| 林总 | 华东 | 12000.00 |
|
|
||||||
| 张三 | 华南 | 10000.00 |
|
|
||||||
| 黄总 | 华西 | 11000.00 |
|
|
||||||
| 赵六 | 人事部 | 5500.00 |
|
|
||||||
| 李四 | 销售部 | 6000.00 |
|
|
||||||
| 周九 | 市场部 | 6500.00 |
|
|
||||||
| 张三 | 技术部 | 8000.00 |
|
|
||||||
| 王五 | 技术部 | 9000.00 |
|
|
||||||
| 孙八 | 技术部 | 8500.00 |
|
|
||||||
+------+-----------+----------+
|
|
||||||
```
|
|
||||||
|
|
||||||
## UNION vs UNION ALL
|
|
||||||
|
|
||||||
### UNION(去重)
|
|
||||||
|
|
||||||
```sql
|
|
||||||
SELECT city FROM employees
|
|
||||||
UNION
|
|
||||||
SELECT city FROM former_employees;
|
|
||||||
```
|
|
||||||
|
|
||||||
**结果:**
|
|
||||||
```
|
|
||||||
+------+
|
|
||||||
| city |
|
|
||||||
+------+
|
|
||||||
| 北京 |
|
|
||||||
| 上海 |
|
|
||||||
| 深圳 |
|
|
||||||
| 广州 |
|
|
||||||
| 杭州 |
|
|
||||||
| 成都 |
|
|
||||||
+------+
|
|
||||||
```
|
|
||||||
|
|
||||||
### UNION ALL(保留重复)
|
|
||||||
|
|
||||||
```sql
|
|
||||||
SELECT city FROM employees
|
|
||||||
UNION ALL
|
|
||||||
SELECT city FROM former_employees;
|
|
||||||
```
|
|
||||||
|
|
||||||
**结果:**
|
|
||||||
```
|
|
||||||
+------+
|
|
||||||
| city |
|
|
||||||
+------+
|
|
||||||
| 北京 |
|
|
||||||
| 上海 |
|
|
||||||
| 深圳 |
|
|
||||||
| 北京 |
|
|
||||||
| 广州 |
|
|
||||||
| 杭州 |
|
|
||||||
| 成都 |
|
|
||||||
| 北京 |
|
|
||||||
| 上海 |
|
|
||||||
| 深圳 |
|
|
||||||
| 广州 |
|
|
||||||
+------+
|
|
||||||
```
|
|
||||||
|
|
||||||
### 性能对比示例
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- 统计重复记录数量
|
|
||||||
SELECT
|
|
||||||
'UNION' AS query_type,
|
|
||||||
COUNT(*) AS record_count
|
|
||||||
FROM (
|
|
||||||
SELECT city FROM employees
|
|
||||||
UNION
|
|
||||||
SELECT city FROM former_employees
|
|
||||||
) AS union_result
|
|
||||||
|
|
||||||
UNION ALL
|
|
||||||
|
|
||||||
SELECT
|
|
||||||
'UNION ALL' AS query_type,
|
|
||||||
COUNT(*) AS record_count
|
|
||||||
FROM (
|
|
||||||
SELECT city FROM employees
|
|
||||||
UNION ALL
|
|
||||||
SELECT city FROM former_employees
|
|
||||||
) AS union_all_result;
|
|
||||||
```
|
|
||||||
|
|
||||||
**结果:**
|
|
||||||
```
|
|
||||||
+------------+--------------+
|
|
||||||
| query_type | record_count |
|
|
||||||
+------------+--------------+
|
|
||||||
| UNION | 6 |
|
|
||||||
| UNION ALL | 11 |
|
|
||||||
+------------+--------------+
|
|
||||||
```
|
|
||||||
|
|
||||||
## 复杂查询示例
|
|
||||||
|
|
||||||
### 1. 条件过滤与UNION
|
|
||||||
|
|
||||||
查询高薪员工和所有管理层:
|
|
||||||
|
|
||||||
```sql
|
|
||||||
SELECT name, salary, '高薪员工' AS category
|
|
||||||
FROM employees
|
|
||||||
WHERE salary > 8000
|
|
||||||
|
|
||||||
UNION
|
|
||||||
|
|
||||||
SELECT name, salary, '管理层' AS category
|
|
||||||
FROM managers
|
|
||||||
|
|
||||||
ORDER BY salary DESC;
|
|
||||||
```
|
|
||||||
|
|
||||||
**结果:**
|
|
||||||
```
|
|
||||||
+------+----------+----------+
|
|
||||||
| name | salary | category |
|
|
||||||
+------+----------+----------+
|
|
||||||
| 陈总 | 15000.00 | 管理层 |
|
|
||||||
| 林总 | 12000.00 | 管理层 |
|
|
||||||
| 黄总 | 11000.00 | 管理层 |
|
|
||||||
| 张三 | 10000.00 | 管理层 |
|
|
||||||
| 王五 | 9000.00 | 高薪员工 |
|
|
||||||
| 孙八 | 8500.00 | 高薪员工 |
|
|
||||||
+------+----------+----------+
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 聚合查询与UNION
|
|
||||||
|
|
||||||
各部门薪资统计:
|
|
||||||
|
|
||||||
```sql
|
|
||||||
SELECT department AS name, AVG(salary) AS avg_salary, '部门平均' AS type
|
|
||||||
FROM employees
|
|
||||||
GROUP BY department
|
|
||||||
|
|
||||||
UNION ALL
|
|
||||||
|
|
||||||
SELECT '公司总体' AS name, AVG(salary) AS avg_salary, '总体平均' AS type
|
|
||||||
FROM employees
|
|
||||||
|
|
||||||
UNION ALL
|
|
||||||
|
|
||||||
SELECT '管理层' AS name, AVG(salary) AS avg_salary, '管理平均' AS type
|
|
||||||
FROM managers
|
|
||||||
|
|
||||||
ORDER BY avg_salary DESC;
|
|
||||||
```
|
|
||||||
|
|
||||||
**结果:**
|
|
||||||
```
|
|
||||||
+----------+-------------+----------+
|
|
||||||
| name | avg_salary | type |
|
|
||||||
+----------+-------------+----------+
|
|
||||||
| 管理层 | 12000.00000 | 管理平均 |
|
|
||||||
| 技术部 | 8500.00000 | 部门平均 |
|
|
||||||
| 公司总体 | 7142.85714 | 总体平均 |
|
|
||||||
| 财务部 | 7000.00000 | 部门平均 |
|
|
||||||
| 市场部 | 6500.00000 | 部门平均 |
|
|
||||||
| 销售部 | 6000.00000 | 部门平均 |
|
|
||||||
| 人事部 | 5500.00000 | 部门平均 |
|
|
||||||
+----------+-------------+----------+
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. 多表复杂联合查询
|
|
||||||
|
|
||||||
创建完整的人员名册:
|
|
||||||
|
|
||||||
```sql
|
|
||||||
SELECT
|
|
||||||
id,
|
|
||||||
name,
|
|
||||||
department,
|
|
||||||
salary,
|
|
||||||
city,
|
|
||||||
'在职' AS status
|
|
||||||
FROM employees
|
|
||||||
|
|
||||||
UNION ALL
|
|
||||||
|
|
||||||
SELECT
|
|
||||||
id,
|
|
||||||
name,
|
|
||||||
department,
|
|
||||||
last_salary AS salary,
|
|
||||||
city,
|
|
||||||
'离职' AS status
|
|
||||||
FROM former_employees
|
|
||||||
|
|
||||||
UNION ALL
|
|
||||||
|
|
||||||
SELECT
|
|
||||||
id,
|
|
||||||
name,
|
|
||||||
'管理层' AS department,
|
|
||||||
salary,
|
|
||||||
region AS city,
|
|
||||||
'管理' AS status
|
|
||||||
FROM managers
|
|
||||||
|
|
||||||
ORDER BY status, salary DESC;
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. 使用子查询的UNION
|
|
||||||
|
|
||||||
查询每个城市的最高薪资员工:
|
|
||||||
|
|
||||||
```sql
|
|
||||||
SELECT name, city, salary, '当前最高薪' AS note
|
|
||||||
FROM employees e1
|
|
||||||
WHERE salary = (
|
|
||||||
SELECT MAX(salary)
|
|
||||||
FROM employees e2
|
|
||||||
WHERE e2.city = e1.city
|
|
||||||
)
|
|
||||||
|
|
||||||
UNION
|
|
||||||
|
|
||||||
SELECT name, city, last_salary AS salary, '前员工最高薪' AS note
|
|
||||||
FROM former_employees f1
|
|
||||||
WHERE last_salary = (
|
|
||||||
SELECT MAX(last_salary)
|
|
||||||
FROM former_employees f2
|
|
||||||
WHERE f2.city = f1.city
|
|
||||||
)
|
|
||||||
|
|
||||||
ORDER BY salary DESC;
|
|
||||||
```
|
|
||||||
|
|
||||||
## 最佳实践和注意事项
|
|
||||||
|
|
||||||
### 1. 性能优化建议
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- ❌ 避免:不必要的UNION,可以用OR替代
|
|
||||||
SELECT * FROM employees WHERE department = '技术部'
|
|
||||||
UNION
|
|
||||||
SELECT * FROM employees WHERE department = '销售部';
|
|
||||||
|
|
||||||
-- ✅ 推荐:使用OR条件
|
|
||||||
SELECT * FROM employees
|
|
||||||
WHERE department IN ('技术部', '销售部');
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 数据类型兼容性
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- ✅ 正确:确保数据类型兼容
|
|
||||||
SELECT id, name, salary FROM employees
|
|
||||||
UNION
|
|
||||||
SELECT id, name, CAST(last_salary AS DECIMAL(10,2)) FROM former_employees;
|
|
||||||
|
|
||||||
-- ❌ 错误:数据类型不兼容可能导致错误
|
|
||||||
SELECT id, name, salary FROM employees
|
|
||||||
UNION
|
|
||||||
SELECT id, name, leave_date FROM former_employees; -- leave_date是DATE类型
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. 使用索引优化
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- 为UNION查询中的过滤条件创建索引
|
|
||||||
CREATE INDEX idx_employees_dept ON employees(department);
|
|
||||||
CREATE INDEX idx_employees_salary ON employees(salary);
|
|
||||||
CREATE INDEX idx_former_employees_dept ON former_employees(department);
|
|
||||||
|
|
||||||
-- 优化后的查询
|
|
||||||
SELECT name, department FROM employees WHERE department = '技术部'
|
|
||||||
UNION
|
|
||||||
SELECT name, department FROM former_employees WHERE department = '技术部';
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. 内存使用注意事项
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- 对于大型结果集,考虑使用LIMIT
|
|
||||||
SELECT name, salary FROM employees
|
|
||||||
UNION ALL
|
|
||||||
SELECT name, salary FROM managers
|
|
||||||
ORDER BY salary DESC
|
|
||||||
LIMIT 10;
|
|
||||||
```
|
|
||||||
|
|
||||||
### 5. 常见错误和解决方案
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- ❌ 错误:列数不匹配
|
|
||||||
SELECT name, department FROM employees
|
|
||||||
UNION
|
|
||||||
SELECT name FROM managers; -- 缺少一列
|
|
||||||
|
|
||||||
-- ✅ 修正:补齐列数
|
|
||||||
SELECT name, department FROM employees
|
|
||||||
UNION
|
|
||||||
SELECT name, level AS department FROM managers;
|
|
||||||
|
|
||||||
-- ❌ 错误:ORDER BY位置错误
|
|
||||||
SELECT name FROM employees ORDER BY name
|
|
||||||
UNION
|
|
||||||
SELECT name FROM managers ORDER BY name;
|
|
||||||
|
|
||||||
-- ✅ 修正:ORDER BY只能在最后
|
|
||||||
SELECT name FROM employees
|
|
||||||
UNION
|
|
||||||
SELECT name FROM managers
|
|
||||||
ORDER BY name;
|
|
||||||
```
|
|
||||||
|
|
||||||
### 6. 实用查询模式
|
|
||||||
|
|
||||||
#### 数据完整性检查
|
|
||||||
```sql
|
|
||||||
-- 检查是否有重复的员工记录
|
|
||||||
SELECT name, COUNT(*) as count
|
|
||||||
FROM (
|
|
||||||
SELECT name FROM employees
|
|
||||||
UNION ALL
|
|
||||||
SELECT name FROM former_employees
|
|
||||||
UNION ALL
|
|
||||||
SELECT name FROM managers
|
|
||||||
) AS all_people
|
|
||||||
GROUP BY name
|
|
||||||
HAVING COUNT(*) > 1;
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 数据对比分析
|
|
||||||
```sql
|
|
||||||
-- 对比不同表中的数据分布
|
|
||||||
SELECT
|
|
||||||
'当前员工' AS source,
|
|
||||||
department,
|
|
||||||
COUNT(*) AS count,
|
|
||||||
AVG(salary) AS avg_salary
|
|
||||||
FROM employees
|
|
||||||
GROUP BY department
|
|
||||||
|
|
||||||
UNION ALL
|
|
||||||
|
|
||||||
SELECT
|
|
||||||
'前员工' AS source,
|
|
||||||
department,
|
|
||||||
COUNT(*) AS count,
|
|
||||||
AVG(last_salary) AS avg_salary
|
|
||||||
FROM former_employees
|
|
||||||
GROUP BY department
|
|
||||||
|
|
||||||
ORDER BY source, department;
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**总结:**
|
|
||||||
- UNION 是合并查询结果的强大工具
|
|
||||||
- 注意列数、数据类型的匹配
|
|
||||||
- 根据需求选择 UNION 或 UNION ALL
|
|
||||||
- 合理使用索引和LIMIT提升性能
|
|
||||||
- 避免不必要的复杂UNION操作
|
|
||||||
1301
MySQL常用SQL语法使用文档.md
1301
MySQL常用SQL语法使用文档.md
File diff suppressed because it is too large
Load Diff
@ -1,327 +0,0 @@
|
|||||||
# 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
|
|
||||||
**难度等级**:⭐⭐⭐⭐☆
|
|
||||||
@ -1,808 +0,0 @@
|
|||||||
# 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的世界还有很多精彩等你探索!🌟
|
|
||||||
@ -1,187 +0,0 @@
|
|||||||
# 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
|
|
||||||
- 分享您的学习心得
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**祝您学习愉快!** 🎉
|
|
||||||
@ -1,132 +0,0 @@
|
|||||||
// 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');
|
|
||||||
@ -1,102 +0,0 @@
|
|||||||
<!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>
|
|
||||||
@ -1,333 +0,0 @@
|
|||||||
/* 基础样式重置 */
|
|
||||||
* {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,161 +0,0 @@
|
|||||||
<!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
27
test.py
@ -1,27 +0,0 @@
|
|||||||
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
30
todo-app/.gitignore
vendored
@ -1,30 +0,0 @@
|
|||||||
# 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
3
todo-app/.vscode/extensions.json
vendored
@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
"recommendations": ["Vue.volar"]
|
|
||||||
}
|
|
||||||
@ -1,29 +0,0 @@
|
|||||||
# 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
|
|
||||||
```
|
|
||||||
@ -1,129 +0,0 @@
|
|||||||
# 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了吗?
|
|
||||||
@ -1,13 +0,0 @@
|
|||||||
<!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>
|
|
||||||
@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"paths": {
|
|
||||||
"@/*": ["./src/*"]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"exclude": ["node_modules", "dist"]
|
|
||||||
}
|
|
||||||
2611
todo-app/package-lock.json
generated
2611
todo-app/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,19 +0,0 @@
|
|||||||
{
|
|
||||||
"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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 4.2 KiB |
@ -1,324 +0,0 @@
|
|||||||
<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>
|
|
||||||
@ -1,86 +0,0 @@
|
|||||||
/* color palette from <https://github.com/vuejs/theme> */
|
|
||||||
:root {
|
|
||||||
--vt-c-white: #ffffff;
|
|
||||||
--vt-c-white-soft: #f8f8f8;
|
|
||||||
--vt-c-white-mute: #f2f2f2;
|
|
||||||
|
|
||||||
--vt-c-black: #181818;
|
|
||||||
--vt-c-black-soft: #222222;
|
|
||||||
--vt-c-black-mute: #282828;
|
|
||||||
|
|
||||||
--vt-c-indigo: #2c3e50;
|
|
||||||
|
|
||||||
--vt-c-divider-light-1: rgba(60, 60, 60, 0.29);
|
|
||||||
--vt-c-divider-light-2: rgba(60, 60, 60, 0.12);
|
|
||||||
--vt-c-divider-dark-1: rgba(84, 84, 84, 0.65);
|
|
||||||
--vt-c-divider-dark-2: rgba(84, 84, 84, 0.48);
|
|
||||||
|
|
||||||
--vt-c-text-light-1: var(--vt-c-indigo);
|
|
||||||
--vt-c-text-light-2: rgba(60, 60, 60, 0.66);
|
|
||||||
--vt-c-text-dark-1: var(--vt-c-white);
|
|
||||||
--vt-c-text-dark-2: rgba(235, 235, 235, 0.64);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* semantic color variables for this project */
|
|
||||||
:root {
|
|
||||||
--color-background: var(--vt-c-white);
|
|
||||||
--color-background-soft: var(--vt-c-white-soft);
|
|
||||||
--color-background-mute: var(--vt-c-white-mute);
|
|
||||||
|
|
||||||
--color-border: var(--vt-c-divider-light-2);
|
|
||||||
--color-border-hover: var(--vt-c-divider-light-1);
|
|
||||||
|
|
||||||
--color-heading: var(--vt-c-text-light-1);
|
|
||||||
--color-text: var(--vt-c-text-light-1);
|
|
||||||
|
|
||||||
--section-gap: 160px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
|
||||||
:root {
|
|
||||||
--color-background: var(--vt-c-black);
|
|
||||||
--color-background-soft: var(--vt-c-black-soft);
|
|
||||||
--color-background-mute: var(--vt-c-black-mute);
|
|
||||||
|
|
||||||
--color-border: var(--vt-c-divider-dark-2);
|
|
||||||
--color-border-hover: var(--vt-c-divider-dark-1);
|
|
||||||
|
|
||||||
--color-heading: var(--vt-c-text-dark-1);
|
|
||||||
--color-text: var(--vt-c-text-dark-2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
*,
|
|
||||||
*::before,
|
|
||||||
*::after {
|
|
||||||
box-sizing: border-box;
|
|
||||||
margin: 0;
|
|
||||||
font-weight: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
min-height: 100vh;
|
|
||||||
color: var(--color-text);
|
|
||||||
background: var(--color-background);
|
|
||||||
transition:
|
|
||||||
color 0.5s,
|
|
||||||
background-color 0.5s;
|
|
||||||
line-height: 1.6;
|
|
||||||
font-family:
|
|
||||||
Inter,
|
|
||||||
-apple-system,
|
|
||||||
BlinkMacSystemFont,
|
|
||||||
'Segoe UI',
|
|
||||||
Roboto,
|
|
||||||
Oxygen,
|
|
||||||
Ubuntu,
|
|
||||||
Cantarell,
|
|
||||||
'Fira Sans',
|
|
||||||
'Droid Sans',
|
|
||||||
'Helvetica Neue',
|
|
||||||
sans-serif;
|
|
||||||
font-size: 15px;
|
|
||||||
text-rendering: optimizeLegibility;
|
|
||||||
-webkit-font-smoothing: antialiased;
|
|
||||||
-moz-osx-font-smoothing: grayscale;
|
|
||||||
}
|
|
||||||
@ -1 +0,0 @@
|
|||||||
<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>
|
|
||||||
|
Before Width: | Height: | Size: 276 B |
@ -1,35 +0,0 @@
|
|||||||
@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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,44 +0,0 @@
|
|||||||
<script setup>
|
|
||||||
defineProps({
|
|
||||||
msg: {
|
|
||||||
type: String,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="greetings">
|
|
||||||
<h1 class="green">{{ msg }}</h1>
|
|
||||||
<h3>
|
|
||||||
You’ve successfully created a project with
|
|
||||||
<a href="https://vite.dev/" target="_blank" rel="noopener">Vite</a> +
|
|
||||||
<a href="https://vuejs.org/" target="_blank" rel="noopener">Vue 3</a>.
|
|
||||||
</h3>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
h1 {
|
|
||||||
font-weight: 500;
|
|
||||||
font-size: 2.6rem;
|
|
||||||
position: relative;
|
|
||||||
top: -10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
font-size: 1.2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.greetings h1,
|
|
||||||
.greetings h3 {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 1024px) {
|
|
||||||
.greetings h1,
|
|
||||||
.greetings h3 {
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,94 +0,0 @@
|
|||||||
<script setup>
|
|
||||||
import WelcomeItem from './WelcomeItem.vue'
|
|
||||||
import DocumentationIcon from './icons/IconDocumentation.vue'
|
|
||||||
import ToolingIcon from './icons/IconTooling.vue'
|
|
||||||
import EcosystemIcon from './icons/IconEcosystem.vue'
|
|
||||||
import CommunityIcon from './icons/IconCommunity.vue'
|
|
||||||
import SupportIcon from './icons/IconSupport.vue'
|
|
||||||
|
|
||||||
const openReadmeInEditor = () => fetch('/__open-in-editor?file=README.md')
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<WelcomeItem>
|
|
||||||
<template #icon>
|
|
||||||
<DocumentationIcon />
|
|
||||||
</template>
|
|
||||||
<template #heading>Documentation</template>
|
|
||||||
|
|
||||||
Vue’s
|
|
||||||
<a href="https://vuejs.org/" target="_blank" rel="noopener">official documentation</a>
|
|
||||||
provides you with all information you need to get started.
|
|
||||||
</WelcomeItem>
|
|
||||||
|
|
||||||
<WelcomeItem>
|
|
||||||
<template #icon>
|
|
||||||
<ToolingIcon />
|
|
||||||
</template>
|
|
||||||
<template #heading>Tooling</template>
|
|
||||||
|
|
||||||
This project is served and bundled with
|
|
||||||
<a href="https://vite.dev/guide/features.html" target="_blank" rel="noopener">Vite</a>. The
|
|
||||||
recommended IDE setup is
|
|
||||||
<a href="https://code.visualstudio.com/" target="_blank" rel="noopener">VSCode</a>
|
|
||||||
+
|
|
||||||
<a href="https://github.com/vuejs/language-tools" target="_blank" rel="noopener">Vue - Official</a>. If
|
|
||||||
you need to test your components and web pages, check out
|
|
||||||
<a href="https://vitest.dev/" target="_blank" rel="noopener">Vitest</a>
|
|
||||||
and
|
|
||||||
<a href="https://www.cypress.io/" target="_blank" rel="noopener">Cypress</a>
|
|
||||||
/
|
|
||||||
<a href="https://playwright.dev/" target="_blank" rel="noopener">Playwright</a>.
|
|
||||||
|
|
||||||
<br />
|
|
||||||
|
|
||||||
More instructions are available in
|
|
||||||
<a href="javascript:void(0)" @click="openReadmeInEditor"><code>README.md</code></a
|
|
||||||
>.
|
|
||||||
</WelcomeItem>
|
|
||||||
|
|
||||||
<WelcomeItem>
|
|
||||||
<template #icon>
|
|
||||||
<EcosystemIcon />
|
|
||||||
</template>
|
|
||||||
<template #heading>Ecosystem</template>
|
|
||||||
|
|
||||||
Get official tools and libraries for your project:
|
|
||||||
<a href="https://pinia.vuejs.org/" target="_blank" rel="noopener">Pinia</a>,
|
|
||||||
<a href="https://router.vuejs.org/" target="_blank" rel="noopener">Vue Router</a>,
|
|
||||||
<a href="https://test-utils.vuejs.org/" target="_blank" rel="noopener">Vue Test Utils</a>, and
|
|
||||||
<a href="https://github.com/vuejs/devtools" target="_blank" rel="noopener">Vue Dev Tools</a>. If
|
|
||||||
you need more resources, we suggest paying
|
|
||||||
<a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">Awesome Vue</a>
|
|
||||||
a visit.
|
|
||||||
</WelcomeItem>
|
|
||||||
|
|
||||||
<WelcomeItem>
|
|
||||||
<template #icon>
|
|
||||||
<CommunityIcon />
|
|
||||||
</template>
|
|
||||||
<template #heading>Community</template>
|
|
||||||
|
|
||||||
Got stuck? Ask your question on
|
|
||||||
<a href="https://chat.vuejs.org" target="_blank" rel="noopener">Vue Land</a>
|
|
||||||
(our official Discord server), or
|
|
||||||
<a href="https://stackoverflow.com/questions/tagged/vue.js" target="_blank" rel="noopener"
|
|
||||||
>StackOverflow</a
|
|
||||||
>. You should also follow the official
|
|
||||||
<a href="https://bsky.app/profile/vuejs.org" target="_blank" rel="noopener">@vuejs.org</a>
|
|
||||||
Bluesky account or the
|
|
||||||
<a href="https://x.com/vuejs" target="_blank" rel="noopener">@vuejs</a>
|
|
||||||
X account for latest news in the Vue world.
|
|
||||||
</WelcomeItem>
|
|
||||||
|
|
||||||
<WelcomeItem>
|
|
||||||
<template #icon>
|
|
||||||
<SupportIcon />
|
|
||||||
</template>
|
|
||||||
<template #heading>Support Vue</template>
|
|
||||||
|
|
||||||
As an independent project, Vue relies on community backing for its sustainability. You can help
|
|
||||||
us by
|
|
||||||
<a href="https://vuejs.org/sponsor/" target="_blank" rel="noopener">becoming a sponsor</a>.
|
|
||||||
</WelcomeItem>
|
|
||||||
</template>
|
|
||||||
@ -1,87 +0,0 @@
|
|||||||
<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>
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
<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>
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
<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>
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
<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>
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
<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>
|
|
||||||
@ -1,19 +0,0 @@
|
|||||||
<!-- 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>
|
|
||||||
@ -1,6 +0,0 @@
|
|||||||
import './assets/main.css'
|
|
||||||
|
|
||||||
import { createApp } from 'vue'
|
|
||||||
import App from './App.vue'
|
|
||||||
|
|
||||||
createApp(App).mount('#app')
|
|
||||||
@ -1,18 +0,0 @@
|
|||||||
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
114
todo-app/使用指南.md
@ -1,114 +0,0 @@
|
|||||||
# 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
1302
weather-cards.html
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user