MySQL表筛选分组全解析:排序、分组与限制的艺术

一、ORDER BY:让数据有序排列

1.1 基础语法与单列排序

bash 复制代码
sql
SELECT 列名1, 列名2, ...
FROM 表名
ORDER BY 排序列 [ASC | DESC];
示例1:基本升序排序
sql
-- 创建示例表:学生成绩表
CREATE TABLE students (
    id INT PRIMARY KEY,
    name VARCHAR(50),
    class VARCHAR(20),
    chinese_score INT,
    math_score INT,
    english_score INT,
    total_score INT,
    birthday DATE
);

INSERT INTO students VALUES
(1, '张三', '一班', 85, 92, 78, 255, '2005-03-15'),
(2, '李四', '二班', 92, 88, 90, 270, '2005-07-22'),
(3, '王五', '一班', 78, 85, 92, 255, '2005-11-05'),
(4, '赵六', '二班', 65, 72, 68, 205, '2005-01-30'),
(5, '孙七', '一班', 88, 94, 87, 269, '2005-06-18'),
(6, '周八', '二班', 95, 89, 93, 277, '2005-09-10');

-- 按总分降序排列
SELECT 
    id AS '学号',
    name AS '姓名',
    total_score AS '总分'
FROM students
ORDER BY total_score DESC;
查询结果:

text
学号  姓名  总分
6     周八  277
2     李四  270
5     孙七  269
1     张三  255
3     王五  255
4     赵六  205
示例2:多列排序
sql
-- 先按班级排序,再按总分降序排序
SELECT 
    name AS '姓名',
    class AS '班级',
    total_score AS '总分',
    chinese_score AS '语文',
    math_score AS '数学',
    english_score AS '英语'
FROM students
ORDER BY class ASC, total_score DESC;
查询结果:

text
姓名  班级  总分  语文  数学  英语
孙七  一班  269   88    94    87
张三  一班  255   85    92    78
王五  一班  255   78    85    92
周八  二班  277   95    89    93
李四  二班  270   92    88    90
赵六  二班  205   65    72    68

1.2 高级排序技巧

bash 复制代码
使用表达式排序
sql
-- 按平均分排序
SELECT 
    name,
    chinese_score,
    math_score,
    english_score,
    (chinese_score + math_score + english_score) / 3 AS average_score
FROM students
ORDER BY (chinese_score + math_score + english_score) / 3 DESC;
使用函数排序
sql
-- 按名字长度排序
SELECT 
    name,
    LENGTH(name) AS name_length,
    class
FROM students
ORDER BY LENGTH(name) DESC;
自定义排序顺序
sql
-- 按班级自定义顺序排序(先二班,后一班)
SELECT 
    name,
    class,
    total_score
FROM students
ORDER BY 
    CASE class 
        WHEN '二班' THEN 1
        WHEN '一班' THEN 2
        ELSE 3
    END,
    total_score DESC;

1.3 NULL值处理

bash 复制代码
sql
-- 创建包含NULL值的示例
CREATE TABLE employees (
    id INT,
    name VARCHAR(50),
    salary DECIMAL(10,2),
    commission DECIMAL(10,2)
);

INSERT INTO employees VALUES
(1, '张三', 8000, 2000),
(2, '李四', 9500, NULL),
(3, '王五', 7500, 1500),
(4, '赵六', 8800, NULL);

-- NULL值默认排在最后(升序时)
SELECT * FROM employees ORDER BY commission ASC;

-- 让NULL值排在最前
SELECT * FROM employees 
ORDER BY 
    CASE WHEN commission IS NULL THEN 0 ELSE 1 END,
    commission ASC;

二、GROUP BY与HAVING:数据分组与筛选

2.1 GROUP BY基础语法

bash 复制代码
sql
SELECT 
    分组列,
    聚合函数(列名)
FROM 表名
GROUP BY 分组列
[HAVING 分组条件];

常用聚合函数

2.2 基础分组查询示例

bash 复制代码
示例1:按班级分组统计
sql
-- 统计每个班级的学生人数、平均分、最高分、最低分
SELECT 
    class AS '班级',
    COUNT(*) AS '学生人数',
    AVG(total_score) AS '平均总分',
    MAX(total_score) AS '最高分',
    MIN(total_score) AS '最低分',
    SUM(total_score) AS '总分合计'
FROM students
GROUP BY class;
查询结果:

text
班级  学生人数  平均总分  最高分  最低分  总分合计
一班      3     259.67    269     255     779
二班      3     250.67    277     205     752
示例2:多列分组
sql
-- 创建销售记录表
CREATE TABLE sales (
    sale_id INT PRIMARY KEY,
    sale_date DATE,
    product_name VARCHAR(50),
    category VARCHAR(30),
    salesperson VARCHAR(50),
    quantity INT,
    unit_price DECIMAL(10,2),
    region VARCHAR(20)
);

INSERT INTO sales VALUES
(1, '2023-10-01', '笔记本电脑', '电子产品', '张三', 2, 6999.00, '北京'),
(2, '2023-10-01', '智能手机', '电子产品', '李四', 5, 2999.00, '上海'),
(3, '2023-10-02', '办公椅', '家具', '王五', 3, 599.00, '北京'),
(4, '2023-10-02', '笔记本电脑', '电子产品', '张三', 1, 6999.00, '广州'),
(5, '2023-10-03', '智能手机', '电子产品', '李四', 3, 2999.00, '北京'),
(6, '2023-10-03', '显示器', '电子产品', '赵六', 4, 1599.00, '上海'),
(7, '2023-10-04', '办公桌', '家具', '王五', 2, 1299.00, '广州');

-- 按产品和地区分组统计
SELECT 
    product_name AS '产品名称',
    region AS '地区',
    COUNT(*) AS '销售笔数',
    SUM(quantity) AS '总销量',
    SUM(quantity * unit_price) AS '总销售额',
    AVG(unit_price) AS '平均单价'
FROM sales
GROUP BY product_name, region;

2.3 HAVING子句:分组后的筛选

bash 复制代码
示例1:筛选分组结果
sql
-- 查找平均分大于260的班级
SELECT 
    class,
    COUNT(*) AS student_count,
    AVG(total_score) AS avg_score
FROM students
GROUP BY class
HAVING AVG(total_score) > 260;

-- 查找销售总额超过10000的产品
SELECT 
    product_name,
    SUM(quantity * unit_price) AS total_sales
FROM sales
GROUP BY product_name
HAVING SUM(quantity * unit_price) > 10000;
示例2:复杂的HAVING条件
sql
-- 查找满足多个条件的销售分组
SELECT 
    region AS '地区',
    category AS '类别',
    COUNT(*) AS '订单数',
    SUM(quantity * unit_price) AS '总销售额'
FROM sales
GROUP BY region, category
HAVING 
    SUM(quantity * unit_price) > 5000
    AND COUNT(*) >= 2
ORDER BY SUM(quantity * unit_price) DESC;
示例3:WHERE与HAVING组合使用
sql
-- 先筛选,再分组,再筛选
SELECT 
    salesperson AS '销售员',
    region AS '地区',
    COUNT(*) AS '订单数量',
    SUM(quantity * unit_price) AS '销售总额'
FROM sales
WHERE sale_date BETWEEN '2023-10-01' AND '2023-10-03'  -- 先按日期筛选
GROUP BY salesperson, region
HAVING SUM(quantity * unit_price) > 5000  -- 再按总额筛选
ORDER BY SUM(quantity * unit_price) DESC;

2.4 高级分组技巧

bash 复制代码
使用GROUP_CONCAT合并分组数据
sql
-- 查询每个班级的学生名单
SELECT 
    class AS '班级',
    GROUP_CONCAT(name ORDER BY total_score DESC SEPARATOR ', ') AS '学生名单',
    AVG(total_score) AS '班级平均分'
FROM students
GROUP BY class;
查询结果:

text
班级  学生名单                班级平均分
一班   孙七, 张三, 王五         259.67
二班   周八, 李四, 赵六         250.67
按日期分组统计
sql
-- 按日期统计销售额
SELECT 
    DATE_FORMAT(sale_date, '%Y-%m-%d') AS '销售日期',
    COUNT(*) AS '订单数量',
    SUM(quantity) AS '总销量',
    SUM(quantity * unit_price) AS '日销售额'
FROM sales
GROUP BY sale_date
ORDER BY sale_date;
使用ROLLUP生成小计
sql
-- 生成带小计的分组统计
SELECT 
    IFNULL(region, '所有地区') AS '地区',
    IFNULL(category, '所有类别') AS '产品类别',
    COUNT(*) AS '订单数',
    SUM(quantity * unit_price) AS '销售额'
FROM sales
GROUP BY region, category WITH ROLLUP
ORDER BY region, category;

三、LIMIT:限制结果集数量

3.1 基础语法

bash 复制代码
sql
-- 格式1:限制返回的行数
SELECT * FROM 表名 LIMIT 数量;

-- 格式2:指定偏移量
SELECT * FROM 表名 LIMIT 偏移量, 数量;
-- 或者
SELECT * FROM 表名 LIMIT 数量 OFFSET 偏移量;

3.2 分页查询应用

c 复制代码
示例1:基本分页
sql
-- 每页显示3条记录
-- 第1页
SELECT * FROM students 
ORDER BY total_score DESC 
LIMIT 0, 3;

-- 第2页
SELECT * FROM students 
ORDER BY total_score DESC 
LIMIT 3, 3;

-- 使用OFFSET语法
SELECT * FROM students 
ORDER BY total_score DESC 
LIMIT 3 OFFSET 3;
示例2:分页参数化
sql
-- 创建分页存储过程
DELIMITER $$
CREATE PROCEDURE get_students_by_page(
    IN page_num INT,      -- 页码
    IN page_size INT      -- 每页大小
)
BEGIN
    DECLARE offset_val INT;
    SET offset_val = (page_num - 1) * page_size;
    
    SELECT 
        name,
        class,
        total_score
    FROM students
    ORDER BY total_score DESC
    LIMIT offset_val, page_size;
END$$
DELIMITER ;

-- 调用:获取第2页,每页3条
CALL get_students_by_page(2, 3);

3.3 TOP N查询

bash 复制代码
示例1:查询前N名
sql
-- 查询总分前三名的学生
SELECT 
    name,
    total_score,
    class
FROM students
ORDER BY total_score DESC
LIMIT 3;

-- 查询每个班级的前两名
SELECT 
    name,
    class,
    total_score
FROM students
WHERE (
    SELECT COUNT(*) 
    FROM students AS s2 
    WHERE s2.class = students.class 
      AND s2.total_score >= students.total_score
) <= 2
ORDER BY class, total_score DESC;
示例2:百分比查询
sql
-- 查询前50%的学生(假设有6个学生,取前3名)
SELECT 
    name,
    total_score
FROM students
ORDER BY total_score DESC
LIMIT (SELECT COUNT(*) FROM students) / 2;

3.4 随机抽样

bash 复制代码
sql
-- 随机抽取3个学生
SELECT 
    name,
    total_score
FROM students
ORDER BY RAND()
LIMIT 3;

四、综合应用案例

4.1 电商数据分析报表

bash 复制代码
sql
-- 创建完整的电商数据分析查询
SELECT 
    DATE_FORMAT(sale_date, '%Y年%m月') AS '销售月份',
    region AS '销售地区',
    category AS '产品类别',
    COUNT(DISTINCT salesperson) AS '销售员人数',
    COUNT(*) AS '订单数量',
    SUM(quantity) AS '销售总量',
    SUM(quantity * unit_price) AS '销售总额',
    AVG(unit_price) AS '平均单价',
    MAX(quantity * unit_price) AS '最大单笔订单',
    MIN(unit_price) AS '最低单价'
FROM sales
WHERE sale_date >= '2023-01-01'
GROUP BY 
    DATE_FORMAT(sale_date, '%Y-%m'),
    region,
    category
HAVING 
    SUM(quantity * unit_price) > 1000
    AND COUNT(*) >= 1
ORDER BY 
    DATE_FORMAT(sale_date, '%Y-%m') DESC,
    SUM(quantity * unit_price) DESC
LIMIT 20;

4.2 学生成绩分析系统

bash 复制代码
sql
-- 学生成绩综合统计
WITH class_stats AS (
    SELECT 
        class,
        COUNT(*) AS total_students,
        AVG(total_score) AS class_avg,
        MAX(total_score) AS class_max,
        MIN(total_score) AS class_min
    FROM students
    GROUP BY class
),
student_rank AS (
    SELECT 
        name,
        class,
        total_score,
        RANK() OVER (PARTITION BY class ORDER BY total_score DESC) AS class_rank,
        RANK() OVER (ORDER BY total_score DESC) AS overall_rank
    FROM students
)
SELECT 
    s.name AS '姓名',
    s.class AS '班级',
    s.total_score AS '总分',
    sr.class_rank AS '班级排名',
    sr.overall_rank AS '全校排名',
    cs.class_avg AS '班级平均分',
    CASE 
        WHEN s.total_score >= cs.class_avg THEN '高于平均'
        ELSE '低于平均'
    END AS '与班级平均分比较'
FROM students s
JOIN student_rank sr ON s.name = sr.name
JOIN class_stats cs ON s.class = cs.class
ORDER BY 
    s.class,
    s.total_score DESC
LIMIT 10;

五、性能优化建议

5.1 ORDER BY优化

bash 复制代码
sql
-- 1. 为ORDER BY列创建索引
CREATE INDEX idx_total_score ON students(total_score);

-- 2. 避免使用表达式排序(如果可能)
-- 优化前
SELECT * FROM students ORDER BY total_score + 10 DESC;
-- 优化后
SELECT *, total_score + 10 AS adjusted_score 
FROM students 
ORDER BY total_score DESC;

-- 3. 使用覆盖索引
-- 优化前
SELECT name, class FROM students ORDER BY total_score;
-- 优化后(创建复合索引)
CREATE INDEX idx_covering ON students(total_score, name, class);

5.2 GROUP BY优化

bash 复制代码
sql
-- 1. 使用EXPLAIN分析
EXPLAIN SELECT class, COUNT(*) FROM students GROUP BY class;

-- 2. 减少分组列的数量
-- 优化前
SELECT class, YEAR(birthday), COUNT(*) FROM students GROUP BY class, YEAR(birthday);
-- 优化后
SELECT class, COUNT(*) FROM students GROUP BY class;

-- 3. 在WHERE中过滤,减少GROUP BY数据量
SELECT class, AVG(total_score) 
FROM students 
WHERE total_score > 200  -- 先过滤
GROUP BY class;

5.3 LIMIT优化

bash 复制代码
sql
-- 1. 使用有索引的列进行ORDER BY
-- 慢
SELECT * FROM students ORDER BY name LIMIT 100000, 20;
-- 快(如果id是主键)
SELECT * FROM students ORDER BY id LIMIT 100000, 20;

-- 2. 使用子查询优化深分页
-- 优化前(深分页效率低)
SELECT * FROM students ORDER BY id LIMIT 100000, 20;
-- 优化后
SELECT * FROM students 
WHERE id >= (SELECT id FROM students ORDER BY id LIMIT 100000, 1)
ORDER BY id 
LIMIT 20;
相关推荐
tsyjjOvO2 小时前
JDBC(Java Database Connectivity)
java·数据库
陌上丨2 小时前
如何保证Redis缓存和数据库数据的一致性?
数据库·redis·缓存
l1t2 小时前
一个用postgresql的自定义函数求解数独的程序
数据库·postgresql·数独
IvorySQL2 小时前
改变工作方式的 PostgreSQL 实用模式
数据库·postgresql
Anarkh_Lee2 小时前
在VSCode中使用MCP实现智能问数
数据库·ide·vscode·ai·编辑器·ai编程·数据库开发
晓13133 小时前
第八章:Redis底层原理深度详细解析
数据库·redis·缓存
电商API&Tina3 小时前
电商数据采集 API 接口 全维度解析(技术 + 商业 + 合规)
java·大数据·开发语言·数据库·人工智能·json
liwulin05063 小时前
【JSON】使用com.fasterxml.jackson解析json字符串
java·数据库·json
ActionTech3 小时前
MySQL 用好 Optimizer Trace,深刻理解 SQL 优化过程!
sql·mysql·oracle