从数据库捞数据出来,默认顺序是按插入顺序排列的。但实际业务中,你得按价格排序、按时间排序、按销量排序。这时候ORDER BY就派上用场了。
排序规则:
-
ASC — 升序,从小到大,从A到Z。不写的话默认就是ASC
-
DESC — 降序,从大到小,从Z到A
一个真实场景: 电商后台看订单,运营肯定想先看新的订单在最上面,那就ORDER BY created_at DESC。要是看销量排行榜,那就是ORDER BY sales_count DESC。
ORDER BY语法
SELECT 字段名 FROM 表名 ORDER BY 排序字段 ASC|DESC;
支持多字段排序:
SELECT * FROM products ORDER BY category ASC, price DESC;
先按分类升序排,同一个分类里面再按价格降序排。
示例数据表
本文用到的表:employees
| id | name | salary |
|---|---|---|
| 1 | 赵大柱 | 30000 |
| 2 | 钱小毛 | 40000 |
| 3 | 孙漂亮 | 35000 |
升序排列(ASC)
默认就是升序,所以ORDER BY name和ORDER BY name ASC结果一样。
<?php
$host = 'localhost:3306';
$user = 'root';
$pass = '123456';
$dbname = 'company_db';
$conn = mysqli_connect($host, $user, $pass, $dbname);
if (!$conn) {
die('连不上数据库:' . mysqli_connect_error());
}
$sql = 'SELECT id, name, salary FROM employees ORDER BY name';
$result = mysqli_query($conn, $sql);
if (mysqli_num_rows($result) > 0) {
while ($row = mysqli_fetch_assoc($result)) {
echo "工号:" . $row['id'] . "<br>";
echo "姓名:" . $row['name'] . "<br>";
echo "工资:" . $row['salary'] . "<br>";
echo "-------------------<br>";
}
} else {
echo "没有查到数据";
}
mysqli_close($conn);
?>
排序前(原始数据):
| id | name | salary |
|---|---|---|
| 1 | 赵大柱 | 30000 |
| 2 | 钱小毛 | 40000 |
| 3 | 孙漂亮 | 35000 |
排序后(ORDER BY name升序):
| id | name | salary |
|---|---|---|
| 3 | 孙漂亮 | 35000 |
| 2 | 钱小毛 | 40000 |
| 1 | 赵大柱 | 30000 |
按拼音字母顺序排:孙(sun)、钱(qian)、赵(zhao)
个人经验: 中文排序在MySQL里是按拼音来的,这点和英文不一样。如果表字段用的是utf8_general_ci,中文排序基本没问题。但如果你想要按笔画排序,那得单独处理。
降序排列(DESC)
工资从高到低排,老板看工资表的时候最常用。
<?php
$host = 'localhost:3306';
$user = 'root';
$pass = '123456';
$dbname = 'company_db';
$conn = mysqli_connect($host, $user, $pass, $dbname);
if (!$conn) {
die('连不上数据库:' . mysqli_connect_error());
}
$sql = 'SELECT id, name, salary FROM employees ORDER BY salary DESC';
$result = mysqli_query($conn, $sql);
if (mysqli_num_rows($result) > 0) {
while ($row = mysqli_fetch_assoc($result)) {
echo "工号:" . $row['id'] . "<br>";
echo "姓名:" . $row['name'] . "<br>";
echo "工资:" . $row['salary'] . "<br>";
echo "-------------------<br>";
}
} else {
echo "没有查到数据";
}
mysqli_close($conn);
?>
排序前:
| id | name | salary |
|---|---|---|
| 1 | 赵大柱 | 30000 |
| 2 | 钱小毛 | 40000 |
| 3 | 孙漂亮 | 35000 |
排序后(ORDER BY salary DESC):
| id | name | salary |
|---|---|---|
| 2 | 钱小毛 | 40000 |
| 3 | 孙漂亮 | 35000 |
| 1 | 赵大柱 | 30000 |
说一下: 30000是较低的,所以排到了之后。这就是降序的效果。
MySQLi面向对象方式排序
<?php
$mysqli = new mysqli("localhost", "root", "123456", "company_db");
if ($mysqli->connect_error) {
die("连接失败:" . $mysqli->connect_error);
}
$sql = "SELECT id, name, salary FROM employees ORDER BY salary DESC";
$result = $mysqli->query($sql);
if ($result->num_rows > 0) {
echo "<table border='1'>";
echo "<tr><th>工号</th><th>姓名</th><th>工资</th></tr>";
while ($row = $result->fetch_assoc()) {
echo "<tr>";
echo "<td>" . $row['id'] . "</td>";
echo "<td>" . $row['name'] . "</td>";
echo "<td>" . $row['salary'] . "</td>";
echo "</tr>";
}
echo "</table>";
} else {
echo "没有数据";
}
$mysqli->close();
?>
原始表数据:
| id | name | salary |
|---|---|---|
| 1 | 赵大柱 | 30000 |
| 2 | 钱小毛 | 40000 |
| 3 | 孙漂亮 | 35000 |
输出表格:
| 工号 | 姓名 | 工资 |
|---|---|---|
| 2 | 钱小毛 | 40000 |
| 3 | 孙漂亮 | 35000 |
| 1 | 赵大柱 | 30000 |
PDO方式排序
PDO的预处理配合ORDER BY有个细节需要注意:ORDER BY后面的字段名不能用占位符绑定。这是SQL语法的限制,字段名和表名不能参数化。
<?php
try {
$pdo = new PDO("mysql:host=localhost;dbname=company_db", "root", "123456");
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// 排序字段和方向不能绑定参数,只能直接拼接
// 但如果你是从用户输入获取排序字段,必须做白名单校验
$sort_field = 'salary'; // 来自用户输入时要校验
$sort_order = 'DESC'; // 来自用户输入时要校验
$sql = "SELECT id, name, salary FROM employees ORDER BY $sort_field $sort_order";
$stmt = $pdo->prepare($sql);
$stmt->execute();
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);
echo "<table border='1'>";
echo "<tr><th>工号</th><th>姓名</th><th>工资</th></table>";
foreach ($results as $row) {
echo "<td>";
echo "<td>" . $row['id'] . "</td>";
echo "<td>" . $row['name'] . "</td>";
echo "<td>" . $row['salary'] . "</td>";
echo "</tr>";
}
echo "</table>";
} catch (PDOException $e) {
echo "查询失败:" . $e->getMessage();
}
$pdo = null;
?>
个人见解: PDO确实好用,但ORDER BY这里是个例外。因为字段名不能做参数绑定,所以如果排序字段来自前端传参(比如用户点击表头排序),必须在后端做白名单校验,不然会有SQL注入风险。
安全写法示例:
// 允许排序的字段白名单
$allowed_sort_fields = ['id', 'name', 'salary', 'created_at'];
$sort_field = $_GET['sort'] ?? 'id';
if (!in_array($sort_field, $allowed_sort_fields)) {
$sort_field = 'id'; // 非法字段就回退到默认
}
$allowed_orders = ['ASC', 'DESC'];
$sort_order = strtoupper($_GET['order'] ?? 'ASC');
if (!in_array($sort_order, $allowed_orders)) {
$sort_order = 'ASC';
}
$sql = "SELECT * FROM employees ORDER BY $sort_field $sort_order";
多字段排序
实际业务经常需要先按A排,再按B排。
代码号学习编程示例:
<?php
// 先按部门升序,同一部门内按工资降序
$sql = "SELECT id, name, department, salary
FROM employees
ORDER BY department ASC, salary DESC";
$result = mysqli_query($conn, $sql);
数据示例:
| id | name | department | salary |
|---|---|---|---|
| 1 | 张三 | 技术部 | 15000 |
| 2 | 李四 | 技术部 | 12000 |
| 3 | 王五 | 市场部 | 13000 |
| 4 | 赵六 | 市场部 | 11000 |
排序后:
| id | name | department | salary |
|---|---|---|---|
| 1 | 张三 | 技术部 | 15000 |
| 2 | 李四 | 技术部 | 12000 |
| 3 | 王五 | 市场部 | 13000 |
| 4 | 赵六 | 市场部 | 11000 |
技术部在前(A在M前面),技术部内部工资高的在前;市场部在后,内部也是工资高的在前。
结合WHERE条件排序
<?php
// 只查工资大于10000的员工,按工资从高到低排
$sql = "SELECT id, name, salary
FROM employees
WHERE salary > 10000
ORDER BY salary DESC";
执行顺序是:WHERE先过滤 → 再排序 → 之后返回结果。
HTML表格输出排序结果
后台管理页面经常需要把排序后的数据用表格展示。
<?php
$conn = mysqli_connect("localhost", "root", "123456", "company_db");
if (!$conn) {
die("连接失败:" . mysqli_connect_error());
}
$sql = "SELECT id, name, salary FROM employees ORDER BY salary DESC";
if ($result = mysqli_query($conn, $sql)) {
if (mysqli_num_rows($result) > 0) {
echo "<table border='1' cellpadding='8'>";
echo "<tr><th>工号</th><th>姓名</th><th>工资(元)</th></table>";
while ($row = mysqli_fetch_array($result)) {
echo "<tr>";
echo "<td>" . $row['id'] . "</td>";
echo "<td>" . $row['name'] . "</td>";
echo "<td>" . number_format($row['salary']) . "</td>";
echo "</tr>";
}
echo "</table>";
mysqli_free_result($result);
} else {
echo "没有找到记录";
}
} else {
echo "查询执行失败:" . mysqli_error($conn);
}
mysqli_close($conn);
?>
number_format()的作用: 把30000显示成30,000,老板看着舒服。
本节课程知识要点
-
默认排序是ASC — ORDER BY字段名 等同于 ORDER BY字段名 ASC
-
多字段排序有优先级 — 写在前面的字段先排序,前面的字段值相同才用到后面的字段
-
排序字段不能参数化 — PDO的prepare不能绑定字段名,必须用白名单校验
-
大表排序要建索引 — 经常用来排序的字段(比如created_at、price),记得建索引。没索引的话数据量大了会慢得很明显
-
NULL值的排序位置 — MySQL里NULL在升序时排在最前面,降序时排在之后面。这点和有些数据库不一样,需要注意
-- 把NULL值放到之后(升序时)
SELECT * FROM products ORDER BY IFNULL(price, 999999) ASC;
-- 或者用COALESCE
SELECT * FROM products ORDER BY COALESCE(price, 999999) ASC;
常见问题
Q:ORDER BY和GROUP BY一起用时怎么写?
A:GROUP BY在前,ORDER BY在后。先分组再排序。
SELECT department, AVG(salary) as avg_salary
FROM employees
GROUP BY department
ORDER BY avg_salary DESC;
Q:中文排序不按拼音怎么办?
A:检查表的排序规则,改成utf8_general_ci或者gbk_chinese_ci。
-- 临时指定排序规则
SELECT name FROM employees ORDER BY name COLLATE utf8_general_ci;