排序 ORDER BY

# 排序 ORDER BY

# 概述

ORDER BY子句用于对查询结果进行排序,可以按照一个或多个列进行升序(ASC)或降序(DESC)排列。排序是SQL查询中最常用的功能之一。

# 基本语法

SELECT column1, column2, ...
FROM table_name
WHERE condition
ORDER BY column1 [ASC|DESC], column2 [ASC|DESC], ...;

# 测试数据准备

-- 创建员工表
CREATE TABLE employees (
    employee_id INT PRIMARY KEY,
    name VARCHAR(50),
    department VARCHAR(30),
    position VARCHAR(30),
    salary DECIMAL(10,2),
    hire_date DATE,
    age INT
);

-- 插入测试数据
INSERT INTO employees VALUES
(1, '张三', '技术部', '高级工程师', 12000, '2020-01-15', 28),
(2, '李四', '技术部', '工程师', 8000, '2021-03-20', 25),
(3, '王五', '技术部', '初级工程师', 6000, '2022-06-10', 23),
(4, '赵六', '销售部', '销售经理', 10000, '2019-08-05', 32),
(5, '钱七', '销售部', '销售代表', 7000, '2021-11-12', 26),
(6, '孙八', '销售部', '销售代表', 6500, '2022-02-28', 24),
(7, '周九', '人事部', '人事经理', 9000, '2020-05-18', 30),
(8, '吴十', '人事部', '人事专员', 5500, '2021-09-30', 22),
(9, '郑一', '财务部', '财务经理', 11000, '2019-12-01', 35),
(10, '王二', '财务部', '会计', 6800, '2022-01-20', 27);

-- 创建产品表
CREATE TABLE products (
    product_id INT PRIMARY KEY,
    product_name VARCHAR(50),
    category VARCHAR(30),
    price DECIMAL(10,2),
    stock_quantity INT,
    created_date DATE
);

-- 插入产品数据
INSERT INTO products VALUES
(1, 'iPhone 14', '手机', 5999.00, 50, '2023-01-10'),
(2, 'MacBook Pro', '笔记本', 12999.00, 20, '2023-01-15'),
(3, 'iPad Air', '平板', 3999.00, 30, '2023-02-01'),
(4, 'AirPods Pro', '耳机', 1999.00, 100, '2023-02-10'),
(5, 'Apple Watch', '手表', 2999.00, 40, '2023-03-01');

# 单列排序

# 升序排序(ASC)

-- 按工资升序排列(ASC可以省略,默认为升序)
SELECT name, salary
FROM employees
ORDER BY salary ASC;

-- 省略ASC
SELECT name, salary
FROM employees
ORDER BY salary;

# 降序排序(DESC)

-- 按工资降序排列
SELECT name, salary
FROM employees
ORDER BY salary DESC;

-- 按入职日期降序排列(最新入职的在前)
SELECT name, hire_date
FROM employees
ORDER BY hire_date DESC;

# 多列排序

-- 先按部门升序,再按工资降序
SELECT name, department, salary
FROM employees
ORDER BY department ASC, salary DESC;

-- 先按部门,再按职位,最后按工资
SELECT name, department, position, salary
FROM employees
ORDER BY department, position, salary DESC;

-- 复杂多列排序
SELECT name, department, age, salary, hire_date
FROM employees
ORDER BY 
    department ASC,     -- 部门升序
    age DESC,          -- 年龄降序
    salary DESC,       -- 工资降序
    hire_date ASC;     -- 入职日期升序

# 按表达式排序

# 计算字段排序

-- 按年薪排序
SELECT name, salary, salary * 12 as annual_salary
FROM employees
ORDER BY salary * 12 DESC;

-- 按工作年限排序
SELECT 
    name, 
    hire_date,
    DATEDIFF(CURDATE(), hire_date) / 365 as work_years
FROM employees
ORDER BY DATEDIFF(CURDATE(), hire_date) DESC;

# 条件表达式排序

-- 按工资等级排序
SELECT 
    name, 
    salary,
    CASE 
        WHEN salary >= 10000 THEN '高薪'
        WHEN salary >= 7000 THEN '中薪'
        ELSE '低薪'
    END as salary_level
FROM employees
ORDER BY 
    CASE 
        WHEN salary >= 10000 THEN 1
        WHEN salary >= 7000 THEN 2
        ELSE 3
    END;

# 按列位置排序

-- 按SELECT列表中的位置排序
SELECT name, department, salary
FROM employees
ORDER BY 2, 3 DESC;  -- 按第2列(department)和第3列(salary降序)排序

-- 注意:不推荐使用列位置,可读性差
-- 推荐使用列名
SELECT name, department, salary
FROM employees
ORDER BY department, salary DESC;

# 字符串排序

# 基本字符串排序

-- 按姓名字母顺序排序
SELECT name, department
FROM employees
ORDER BY name;

-- 按部门名称排序
SELECT name, department
FROM employees
ORDER BY department, name;

# 中文排序

-- MySQL中文排序(使用CONVERT函数)
SELECT name, department
FROM employees
ORDER BY CONVERT(name USING gbk);

-- 或者使用COLLATE
SELECT name, department
FROM employees
ORDER BY name COLLATE utf8mb4_unicode_ci;

# 字符串长度排序

-- 按姓名长度排序
SELECT name, LENGTH(name) as name_length
FROM employees
ORDER BY LENGTH(name), name;

# 日期时间排序

# 基本日期排序

-- 按入职日期排序
SELECT name, hire_date
FROM employees
ORDER BY hire_date;

-- 最新入职的员工在前
SELECT name, hire_date
FROM employees
ORDER BY hire_date DESC;

# 日期部分排序

-- 按入职月份排序(忽略年份)
SELECT name, hire_date, MONTH(hire_date) as hire_month
FROM employees
ORDER BY MONTH(hire_date), DAY(hire_date);

-- 按星期几排序
SELECT name, hire_date, DAYNAME(hire_date) as day_of_week
FROM employees
ORDER BY DAYOFWEEK(hire_date);

# NULL值排序

-- 创建包含NULL值的测试表
CREATE TABLE test_null (
    id INT,
    name VARCHAR(50),
    score INT
);

INSERT INTO test_null VALUES
(1, '张三', 85),
(2, '李四', NULL),
(3, '王五', 92),
(4, '赵六', NULL),
(5, '钱七', 78);

-- 默认NULL值排序(MySQL中NULL值排在最前面)
SELECT * FROM test_null
ORDER BY score;

-- 将NULL值排在最后
SELECT * FROM test_null
ORDER BY score IS NULL, score;

-- 将NULL值排在最前
SELECT * FROM test_null
ORDER BY score IS NOT NULL, score;

-- 使用COALESCE处理NULL值
SELECT * FROM test_null
ORDER BY COALESCE(score, 0);  -- 将NULL替换为0

# 与其他子句结合

# 与WHERE结合

-- 查询技术部员工,按工资降序排列
SELECT name, salary
FROM employees
WHERE department = '技术部'
ORDER BY salary DESC;

-- 查询工资大于7000的员工,按部门和工资排序
SELECT name, department, salary
FROM employees
WHERE salary > 7000
ORDER BY department, salary DESC;

# 与GROUP BY结合

-- 按部门统计平均工资,按平均工资降序排列
SELECT department, AVG(salary) as avg_salary
FROM employees
GROUP BY department
ORDER BY avg_salary DESC;

-- 按部门统计员工数,按员工数和部门名排序
SELECT department, COUNT(*) as employee_count
FROM employees
GROUP BY department
ORDER BY employee_count DESC, department;

# 与LIMIT结合

-- 查询工资最高的3名员工
SELECT name, salary
FROM employees
ORDER BY salary DESC
LIMIT 3;

-- 查询工资第4-6名的员工
SELECT name, salary
FROM employees
ORDER BY salary DESC
LIMIT 3 OFFSET 3;

# 实战案例

# 案例1:电商产品排序

-- 创建订单表
CREATE TABLE orders (
    order_id INT PRIMARY KEY,
    product_id INT,
    customer_id INT,
    quantity INT,
    order_date DATE,
    total_amount DECIMAL(10,2)
);

-- 插入订单数据
INSERT INTO orders VALUES
(1, 1, 101, 2, '2023-01-15', 11998.00),
(2, 2, 102, 1, '2023-01-16', 12999.00),
(3, 3, 103, 1, '2023-02-10', 3999.00),
(4, 1, 104, 1, '2023-02-15', 5999.00),
(5, 4, 105, 3, '2023-03-05', 5997.00);

-- 产品销量排行榜
SELECT 
    p.product_name,
    p.category,
    SUM(o.quantity) as total_sold,
    SUM(o.total_amount) as total_revenue
FROM products p
JOIN orders o ON p.product_id = o.product_id
GROUP BY p.product_id, p.product_name, p.category
ORDER BY total_sold DESC, total_revenue DESC;

-- 按价格和库存综合排序
SELECT 
    product_name,
    price,
    stock_quantity,
    price * stock_quantity as inventory_value
FROM products
ORDER BY 
    CASE 
        WHEN stock_quantity = 0 THEN 1  -- 缺货的排在最后
        ELSE 0
    END,
    price DESC,
    stock_quantity DESC;

# 案例2:学生成绩排名

-- 创建学生成绩表
CREATE TABLE student_grades (
    student_id INT,
    student_name VARCHAR(50),
    subject VARCHAR(30),
    grade DECIMAL(5,2),
    exam_date DATE
);

-- 插入成绩数据
INSERT INTO student_grades VALUES
(1, '张三', '数学', 85.5, '2023-06-15'),
(1, '张三', '语文', 78.0, '2023-06-16'),
(1, '张三', '英语', 92.0, '2023-06-17'),
(2, '李四', '数学', 76.5, '2023-06-15'),
(2, '李四', '语文', 88.0, '2023-06-16'),
(2, '李四', '英语', 84.5, '2023-06-17');

-- 学生总分排名
SELECT 
    student_name,
    SUM(grade) as total_score,
    AVG(grade) as avg_score,
    COUNT(*) as subject_count
FROM student_grades
GROUP BY student_id, student_name
ORDER BY total_score DESC, avg_score DESC;

-- 各科成绩排名
SELECT 
    subject,
    student_name,
    grade,
    RANK() OVER (PARTITION BY subject ORDER BY grade DESC) as ranking
FROM student_grades
ORDER BY subject, ranking;

# 案例3:销售业绩分析

-- 创建销售记录表
CREATE TABLE sales_records (
    sale_id INT PRIMARY KEY,
    salesperson VARCHAR(50),
    region VARCHAR(30),
    sale_amount DECIMAL(10,2),
    sale_date DATE,
    customer_type VARCHAR(20)
);

-- 插入销售数据
INSERT INTO sales_records VALUES
(1, '张三', '华北', 15000, '2023-01-15', 'VIP'),
(2, '李四', '华南', 8000, '2023-01-20', '普通'),
(3, '王五', '华东', 12000, '2023-02-10', 'VIP'),
(4, '张三', '华北', 20000, '2023-02-15', 'VIP'),
(5, '赵六', '华西', 9000, '2023-03-05', '普通');

-- 销售员业绩排名
SELECT 
    salesperson,
    COUNT(*) as sale_count,
    SUM(sale_amount) as total_sales,
    AVG(sale_amount) as avg_sale,
    MAX(sale_amount) as max_sale
FROM sales_records
GROUP BY salesperson
ORDER BY total_sales DESC, avg_sale DESC;

-- 按地区和客户类型排序
SELECT 
    region,
    customer_type,
    COUNT(*) as sale_count,
    SUM(sale_amount) as total_sales
FROM sales_records
GROUP BY region, customer_type
ORDER BY 
    region,
    CASE customer_type 
        WHEN 'VIP' THEN 1 
        WHEN '普通' THEN 2 
        ELSE 3 
    END,
    total_sales DESC;

# 高级排序技巧

# 1. 自定义排序顺序

-- 按自定义的部门优先级排序
SELECT name, department, salary
FROM employees
ORDER BY 
    CASE department
        WHEN '技术部' THEN 1
        WHEN '销售部' THEN 2
        WHEN '财务部' THEN 3
        WHEN '人事部' THEN 4
        ELSE 5
    END,
    salary DESC;

-- 按职位等级排序
SELECT name, position, salary
FROM employees
ORDER BY 
    CASE 
        WHEN position LIKE '%经理%' THEN 1
        WHEN position LIKE '%高级%' THEN 2
        WHEN position LIKE '%工程师%' THEN 3
        ELSE 4
    END,
    salary DESC;

# 2. 随机排序

-- 随机排序(MySQL)
SELECT name, department
FROM employees
ORDER BY RAND()
LIMIT 5;

-- 随机排序(PostgreSQL)
-- ORDER BY RANDOM()

-- 随机排序(SQL Server)
-- ORDER BY NEWID()

# 3. 条件排序

-- 根据条件决定排序方向
SET @sort_direction = 'DESC';

SELECT name, salary
FROM employees
ORDER BY 
    CASE 
        WHEN @sort_direction = 'ASC' THEN salary
        ELSE NULL
    END ASC,
    CASE 
        WHEN @sort_direction = 'DESC' THEN salary
        ELSE NULL
    END DESC;

# 性能优化

# 1. 索引优化

-- 为排序列创建索引
CREATE INDEX idx_salary ON employees(salary);
CREATE INDEX idx_dept_salary ON employees(department, salary);

-- 复合索引的列顺序很重要
-- 如果经常按(department, salary)排序,创建复合索引
CREATE INDEX idx_dept_salary_name ON employees(department, salary, name);

# 2. 避免排序的方法

-- 如果只需要最大值,避免排序
-- 低效
SELECT salary FROM employees ORDER BY salary DESC LIMIT 1;

-- 高效
SELECT MAX(salary) FROM employees;

-- 如果只需要前几条记录,考虑使用索引
-- 确保有适当的索引支持ORDER BY + LIMIT

# 3. 大数据量排序优化

-- 对于大表,避免复杂的排序表达式
-- 低效
SELECT * FROM large_table
ORDER BY SUBSTRING(name, 1, 3), LENGTH(description);

-- 考虑预计算排序键
ALTER TABLE large_table ADD COLUMN sort_key VARCHAR(10);
UPDATE large_table SET sort_key = SUBSTRING(name, 1, 3);
CREATE INDEX idx_sort_key ON large_table(sort_key);

SELECT * FROM large_table
ORDER BY sort_key;

# 常见错误

# 1. 在GROUP BY中使用ORDER BY

-- 错误:ORDER BY列不在GROUP BY中
SELECT department, COUNT(*)
FROM employees
GROUP BY department
ORDER BY salary;  -- salary不在GROUP BY中

-- 正确:使用聚合函数
SELECT department, COUNT(*), AVG(salary) as avg_salary
FROM employees
GROUP BY department
ORDER BY avg_salary;

# 2. 字符串数字排序

-- 错误:字符串排序
SELECT * FROM (
    SELECT '1' as num UNION ALL
    SELECT '10' UNION ALL
    SELECT '2'
) t
ORDER BY num;  -- 结果:1, 10, 2

-- 正确:数值排序
SELECT * FROM (
    SELECT '1' as num UNION ALL
    SELECT '10' UNION ALL
    SELECT '2'
) t
ORDER BY CAST(num AS UNSIGNED);  -- 结果:1, 2, 10

# 3. NULL值处理不当

-- 注意NULL值的排序行为
-- 在不同数据库中NULL值的排序位置可能不同
-- 明确指定NULL值的排序位置
SELECT * FROM test_null
ORDER BY score IS NULL, score DESC;

# 最佳实践

# 1. 性能考虑

  • 为经常排序的列创建索引
  • 避免在ORDER BY中使用复杂表达式
  • 对于大结果集,考虑分页查询

# 2. 可读性

  • 使用列名而不是列位置
  • 明确指定ASC或DESC
  • 复杂排序逻辑添加注释

# 3. 数据一致性

  • 注意NULL值的处理
  • 考虑字符集对排序的影响
  • 验证排序结果的正确性

# 4. 维护性

  • 避免硬编码排序逻辑
  • 使用参数化排序
  • 文档化特殊排序规则

# 总结

ORDER BY是SQL查询中控制结果顺序的重要子句。正确使用ORDER BY可以让查询结果更有意义和可读性。在实际应用中,需要考虑性能影响,特别是在大数据量的情况下。通过合理的索引设计和查询优化,可以显著提高排序查询的性能。