在日常数据库操作中,我们经常遇到这样的场景:查询结果需要展示序号列,但原始数据表并没有专门存储序号字段。比如制作报表时要求每行显示序号,或者前端分页展示时需要连续编号。MySQL本身不自动提供行号功能,这就需要我们掌握几种实用的序号生成方法。
sql复制SELECT
(@row_number:=@row_number + 1) AS row_num,
id,
name,
age
FROM
users,
(SELECT @row_number:=0) AS t
ORDER BY
age DESC;
注意:变量初始化必须放在FROM子句中,确保每次查询都重新计数。如果放在WHERE之后会导致计数不准确。
当需要按分组生成序号时:
sql复制SELECT
department,
name,
(@rank := IF(@current_dept = department, @rank + 1, 1)) AS rank,
(@current_dept := department) AS dummy
FROM
employees,
(SELECT @rank := 0, @current_dept := '') AS t
ORDER BY
department,
salary DESC;
sql复制SELECT
ROW_NUMBER() OVER (ORDER BY create_time DESC) AS seq,
order_id,
amount
FROM
orders
WHERE
status = 'completed';
sql复制SELECT
ROW_NUMBER() OVER (PARTITION BY department ORDER BY score DESC) AS dept_rank,
employee_name,
department,
score
FROM
performance;
sql复制CREATE TEMPORARY TABLE temp_results (
row_num INT AUTO_INCREMENT PRIMARY KEY,
user_id INT,
user_name VARCHAR(50)
);
INSERT INTO temp_results (user_id, user_name)
SELECT id, name FROM users ORDER BY register_date;
SELECT * FROM temp_results;
对于大数据量:
sql复制CREATE TEMPORARY TABLE temp_orders ENGINE=MEMORY
SELECT
NULL AS row_num,
o.*
FROM
orders o
ORDER BY
o.total_amount DESC;
SET @i = 0;
UPDATE temp_orders SET row_num = (@i:=@i+1);
SELECT * FROM temp_orders;
php复制$stmt = $pdo->query("SELECT id, name FROM products ORDER BY price");
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach ($results as $index => &$row) {
$row['rank'] = $index + 1;
}
java复制List<Map<String, Object>> results = jdbcTemplate.queryForList(
"SELECT * FROM employees ORDER BY salary DESC");
for (int i = 0; i < results.size(); i++) {
results.get(i).put("rowNumber", i + 1);
}
| 方案 | 10万行耗时 | 100万行耗时 | 特点 |
|---|---|---|---|
| 用户变量 | 0.8s | 9.2s | 简单但可能不稳定 |
| 窗口函数 | 1.2s | 12.4s | 语法清晰,需要MySQL 8.0+ |
| 临时表+自增 | 2.1s | 25.7s | 稳定但消耗额外存储 |
| 应用层处理 | 0.5s | 5.3s | 依赖应用服务器资源 |
sql复制-- 第一页
SELECT
(@rn:=@rn+1) AS row_num,
t.*
FROM
target_table t,
(SELECT @rn:=10) AS vars -- 假设每页10条
LIMIT 0, 10;
-- 第二页
SELECT
(@rn:=@rn+1) AS row_num,
t.*
FROM
target_table t,
(SELECT @rn:=20) AS vars
LIMIT 10, 10;
sql复制SET @sql = CONCAT('
SELECT
(@row_num:=@row_num+1) AS no,
id,
name
FROM
products,
(SELECT @row_num:=0) AS r
WHERE
category="', @category, '"
ORDER BY
price DESC
');
PREPARE stmt FROM @sql;
EXECUTE stmt;
现象:序号不从1开始
解决:确保变量初始化在FROM子句:
sql复制-- 错误写法
SELECT (@i:=@i+1), name FROM users, (SELECT @i:=0) AS t WHERE @i:=0;
-- 正确写法
SELECT (@i:=@i+1), name FROM users, (SELECT @i:=0) AS t;
MySQL 5.7及以下版本不支持窗口函数,可用以下替代:
sql复制SELECT
t.*,
(SELECT COUNT(*) FROM products p WHERE p.id <= t.id) AS fake_row_num
FROM
products t
ORDER BY
id;
遇到"Can't create/write to file"错误时:
sql复制SHOW VARIABLES LIKE 'tmpdir';
-- 确保有写入权限或设置新的临时目录
SET GLOBAL tmpdir = '/new/tmp/path';
sql复制SELECT
DATE_ADD('2023-01-01', INTERVAL (@i:=@i+1)-1 DAY) AS date_seq
FROM
mysql.help_topic,
(SELECT @i:=0) AS t
WHERE
@i < DATEDIFF('2023-02-01', '2023-01-01');
sql复制SELECT
(@rn:=@rn+1) AS sample_id,
data.*
FROM
big_data_table data,
(SELECT @rn:=0) AS r
WHERE
MOD(id, 100) = 0 -- 每100条取1条
ORDER BY
RAND()
LIMIT 1000;