数据库高级查询学习总结
目录
一、分组查询与统计
1.1 GROUP BY 分组
1.1.1 基本语法
sql
SELECT 聚合函数, 分组列
FROM 表名
[JOIN 关联表 ON 关联条件]
GROUP BY 分组列
核心原则:
- GROUP BY 后的分组列,一般要出现在 SELECT 展示项中
- SELECT 中不要出现非聚合且非分组的列,否则会报错
1.1.2 常用聚合函数
| 函数 | 说明 |
|---|---|
COUNT(*) |
统计记录总数 |
COUNT(列名) |
统计该列非NULL值的数量 |
AVG(列名) |
求平均值 |
MAX(列名) |
求最大值 |
MIN(列名) |
求最小值 |
SUM(列名) |
求和 |
1.1.3 实际应用示例
sql
-- 1. 统计每个班级有多少人
SELECT COUNT(*) AS 总人数, ClassName
FROM Students
INNER JOIN StudentClass ON StudentClass.ClassId = Students.ClassId
GROUP BY ClassName
-- 2. 按性别统计人数
SELECT Sex, COUNT(*) AS 人数
FROM Students
GROUP BY Sex
1.1.4 NULL 值处理注意事项
- NULL 值不参与聚合运算
- 好处:请假没有参加考试的学生(成绩为NULL),在统计平均分时不参与计算
- 坏处:可能被利用于作弊------故意让成绩差的学生不参加考试,提高班级平均分
- 解决方案:如果需要将NULL作为0参与计算,可使用临时表或ISNULL函数处理
1.2 HAVING 分组筛选
1.2.1 为什么需要 HAVING
WHERE 子句只能在分组之前 过滤原始数据行,无法对分组统计后的结果进行筛选。这就需要使用 HAVING 关键字。
1.2.2 基本语法
sql
SELECT 聚合函数, 分组列
FROM 表名
GROUP BY 分组列
HAVING 聚合函数条件
1.2.3 应用示例
sql
-- 查询C#和DB平均分都大于70的班级
SELECT ClassName,
COUNT(*) AS 总人数,
MAX(CSharp) AS C#最高分,
MAX(SQLServer) AS DB最高分,
C#平均分 = AVG(CSharp),
DB平均分 = AVG(SQLServer)
FROM Students
LEFT OUTER JOIN StudentClass ON StudentClass.ClassId = Students.ClassId
LEFT OUTER JOIN ScoreList ON ScoreList.StudentId = Students.StudentId
GROUP BY ClassName
HAVING AVG(CSharp) > 70 AND AVG(SQLServer) > 70
-- 使用HAVING查询重复的分数
SELECT Score, COUNT(*) AS 重复次数
FROM ScoreList
GROUP BY Score
HAVING COUNT(Score) > 1
1.3 查询重复数据(HAVING 经典应用)
1.3.1 场景
当发现数据表中有重复数据(脏数据),需要找到这些重复数据进行删除或处理。
1.3.2 实现方法
sql
-- 方法一:先查重复字段,再用子查询查完整记录(推荐,易理解)
-- 步骤1:找出哪些StudentId有重复
SELECT StudentId
FROM ScoreList
GROUP BY StudentId
HAVING COUNT(*) > 1
-- 步骤2:查询这些重复记录的具体内容
SELECT * FROM ScoreList
WHERE StudentId IN (
SELECT StudentId
FROM ScoreList
GROUP BY StudentId
HAVING COUNT(*) > 1
)
ORDER BY StudentId
-- 方法二:使用相关子查询(类似双层for循环)
SELECT * FROM ScoreList
WHERE (
SELECT COUNT(*)
FROM ScoreList s
WHERE s.StudentId = ScoreList.StudentId
) > 1
ORDER BY StudentId
1.4 WHERE vs GROUP BY vs HAVING 执行顺序
#mermaid-svg-X79EhUO8WJHp9QUx{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-X79EhUO8WJHp9QUx .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-X79EhUO8WJHp9QUx .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-X79EhUO8WJHp9QUx .error-icon{fill:#552222;}#mermaid-svg-X79EhUO8WJHp9QUx .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-X79EhUO8WJHp9QUx .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-X79EhUO8WJHp9QUx .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-X79EhUO8WJHp9QUx .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-X79EhUO8WJHp9QUx .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-X79EhUO8WJHp9QUx .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-X79EhUO8WJHp9QUx .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-X79EhUO8WJHp9QUx .marker{fill:#333333;stroke:#333333;}#mermaid-svg-X79EhUO8WJHp9QUx .marker.cross{stroke:#333333;}#mermaid-svg-X79EhUO8WJHp9QUx svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-X79EhUO8WJHp9QUx p{margin:0;}#mermaid-svg-X79EhUO8WJHp9QUx .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-X79EhUO8WJHp9QUx .cluster-label text{fill:#333;}#mermaid-svg-X79EhUO8WJHp9QUx .cluster-label span{color:#333;}#mermaid-svg-X79EhUO8WJHp9QUx .cluster-label span p{background-color:transparent;}#mermaid-svg-X79EhUO8WJHp9QUx .label text,#mermaid-svg-X79EhUO8WJHp9QUx span{fill:#333;color:#333;}#mermaid-svg-X79EhUO8WJHp9QUx .node rect,#mermaid-svg-X79EhUO8WJHp9QUx .node circle,#mermaid-svg-X79EhUO8WJHp9QUx .node ellipse,#mermaid-svg-X79EhUO8WJHp9QUx .node polygon,#mermaid-svg-X79EhUO8WJHp9QUx .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-X79EhUO8WJHp9QUx .rough-node .label text,#mermaid-svg-X79EhUO8WJHp9QUx .node .label text,#mermaid-svg-X79EhUO8WJHp9QUx .image-shape .label,#mermaid-svg-X79EhUO8WJHp9QUx .icon-shape .label{text-anchor:middle;}#mermaid-svg-X79EhUO8WJHp9QUx .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-X79EhUO8WJHp9QUx .rough-node .label,#mermaid-svg-X79EhUO8WJHp9QUx .node .label,#mermaid-svg-X79EhUO8WJHp9QUx .image-shape .label,#mermaid-svg-X79EhUO8WJHp9QUx .icon-shape .label{text-align:center;}#mermaid-svg-X79EhUO8WJHp9QUx .node.clickable{cursor:pointer;}#mermaid-svg-X79EhUO8WJHp9QUx .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-X79EhUO8WJHp9QUx .arrowheadPath{fill:#333333;}#mermaid-svg-X79EhUO8WJHp9QUx .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-X79EhUO8WJHp9QUx .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-X79EhUO8WJHp9QUx .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-X79EhUO8WJHp9QUx .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-X79EhUO8WJHp9QUx .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-X79EhUO8WJHp9QUx .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-X79EhUO8WJHp9QUx .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-X79EhUO8WJHp9QUx .cluster text{fill:#333;}#mermaid-svg-X79EhUO8WJHp9QUx .cluster span{color:#333;}#mermaid-svg-X79EhUO8WJHp9QUx div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-X79EhUO8WJHp9QUx .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-X79EhUO8WJHp9QUx rect.text{fill:none;stroke-width:0;}#mermaid-svg-X79EhUO8WJHp9QUx .icon-shape,#mermaid-svg-X79EhUO8WJHp9QUx .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-X79EhUO8WJHp9QUx .icon-shape p,#mermaid-svg-X79EhUO8WJHp9QUx .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-X79EhUO8WJHp9QUx .icon-shape .label rect,#mermaid-svg-X79EhUO8WJHp9QUx .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-X79EhUO8WJHp9QUx .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-X79EhUO8WJHp9QUx .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-X79EhUO8WJHp9QUx :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} WHERE 过滤原始数据
GROUP BY 分组统计
HAVING 过滤分组结果
| 子句 | 作用阶段 | 过滤对象 |
|---|---|---|
| WHERE | 分组前 | 原始数据行 |
| GROUP BY | 分组中 | 将数据行分组并计算统计值 |
| HAVING | 分组后 | 分组统计后的结果集 |
1.5 补充:NOT IN 用法
sql
-- 查找没有参加考试的学生(成绩表中没有记录的学生)
SELECT *
FROM Students
WHERE StudentId NOT IN (SELECT StudentId FROM ScoreList)
-- 统计缺考人数
SELECT absentCount = COUNT(*)
FROM Students
WHERE StudentId NOT IN (SELECT StudentId FROM ScoreList)
-- 等价写法:使用左外连接 + IS NULL判断
SELECT *
FROM Students
LEFT OUTER JOIN ScoreList ON Students.StudentId = ScoreList.StudentId
WHERE ScoreList.Score IS NULL
二、游标与临时表
2.1 临时表
2.1.1 什么是临时表
临时表是临时存储数据的表,存储在 tempdb 系统数据库中,类似于 C# 中的局部变量,使用 # 前缀命名。
2.1.2 临时表的特点
- 临时表名以
#开头(局部临时表),如#TempTable - 存储在
tempdb数据库中 - 当前会话结束后自动删除
- 也可以手动删除
2.1.3 创建与删除临时表
sql
-- 判断临时表是否存在,存在则先删除
IF OBJECT_ID('tempdb..#TempTable') IS NOT NULL
DROP TABLE #TempTable
-- 创建临时表
CREATE TABLE #TempTable
(
Score INT NOT NULL,
ScoreCount INT NULL
)
2.1.4 临时表数据插入方式
sql
-- 方式一:使用 INSERT INTO ... VALUES 逐条插入(通常配合游标循环)
INSERT INTO #TempTable(Score, ScoreCount)
VALUES(@Score, @ScoreCount)
-- 方式二:使用 INSERT INTO ... SELECT 批量插入
INSERT INTO #TempTable
SELECT ScoreList.Score, COUNT(Score) AS ScoreCount
FROM ScoreList
WHERE Score > 90 AND Score <= 100
GROUP BY Score
-- 查询临时表数据
SELECT * FROM #TempTable
2.2 游标(Cursor)
2.2.1 什么是游标
游标是一种对结果集进行逐行处理的机制,类似于编程语言中的 foreach 循环,可以对查询结果的每一行进行遍历和操作。
2.2.2 游标的使用步骤
游标使用分为 5个固定步骤:
1. 声明游标 → 2. 打开游标 → 3. 循环读取数据 → 4. 关闭游标 → 5. 释放游标
2.2.3 游标基本语法
sql
-- ====================== 游标五步法 ======================
-- 步骤1:声明变量(用于接收每行数据)
DECLARE @Score INT;
DECLARE @ScoreCount INT;
-- 步骤1:声明游标
DECLARE Score_cursor CURSOR FOR
SELECT ScoreList.Score, COUNT(Score) AS ScoreCount
FROM ScoreList
GROUP BY Score
-- 步骤2:打开游标
OPEN Score_cursor
-- 步骤3:读取第一行数据,存入变量
FETCH NEXT FROM Score_cursor INTO @Score, @ScoreCount
-- 步骤3:循环读取(@@FETCH_STATUS = 0 表示读取成功)
WHILE @@FETCH_STATUS = 0
BEGIN
-- 对当前行数据进行处理(此处示例:插入临时表)
INSERT INTO #TempTable(Score, ScoreCount)
VALUES(@Score, @ScoreCount);
-- 读取下一行数据(非常重要,否则死循环!)
FETCH NEXT FROM Score_cursor INTO @Score, @ScoreCount
END
-- 步骤4:关闭游标
CLOSE Score_cursor
-- 步骤5:释放游标
DEALLOCATE Score_cursor
2.2.4 @@FETCH_STATUS 状态说明
| 返回值 | 含义 |
|---|---|
| 0 | FETCH 语句读取成功 |
| -1 | FETCH 语句失败或者行不在结果集中 |
| -2 | 被提取的行不存在 |
2.2.5 局部变量 vs 全局变量
| 类型 | 前缀 | 说明 |
|---|---|---|
| 局部变量 | @ |
用户自定义,如 @Score、@StudentId |
| 全局变量 | @@ |
SQL Server 系统定义,如 @@FETCH_STATUS |
2.2.6 游标完整示例:遍历学生表
sql
-- 声明接收变量
DECLARE @StudentId INT;
DECLARE @StudentName VARCHAR(20);
DECLARE @Age INT;
DECLARE @Sex NVARCHAR(10);
DECLARE @Address NVARCHAR(10);
DECLARE @ClassId NVARCHAR(10);
-- 1. 声明游标:查询学生ID >= 3的学生
DECLARE My_Cursor CURSOR FOR
SELECT * FROM Students WHERE StudentId >= 3;
-- 2. 打开游标
OPEN My_Cursor;
-- 3. 读取第一行
FETCH NEXT FROM My_Cursor INTO @StudentId, @StudentName, @Age, @Sex, @Address, @ClassId;
-- 4. 循环遍历
WHILE @@FETCH_STATUS = 0
BEGIN
PRINT @StudentId;
PRINT @StudentName;
PRINT @Age;
PRINT '--------'
-- 读取下一行(必须!)
FETCH NEXT FROM My_Cursor INTO @StudentId, @StudentName, @Age, @Sex, @Address, @ClassId;
END
-- 5. 关闭游标
CLOSE My_Cursor;
-- 6. 释放游标
DEALLOCATE My_Cursor;
2.3 游标与临时表小结
-
临时表:
- 用
#表名命名,存储在tempdb - 适合临时存储中间计算结果
- 两种插入方式:
VALUES(逐条)和SELECT(批量)
- 用
-
游标五步法:
- ①
DECLARE cursor_name CURSOR FOR ...声明 - ②
OPEN cursor_name打开 - ③
FETCH NEXT ... INTO循环读取 - ④
CLOSE cursor_name关闭 - ⑤
DEALLOCATE cursor_name释放
- ①
-
注意事项:
- 循环内必须写
FETCH NEXT,否则会造成死循环 - 游标使用完毕必须关闭和释放,避免资源泄漏
- 大量数据处理时,游标性能较低,优先考虑集合操作
- 循环内必须写
三、分页查询
3.1 分页查询概述
当数据表中数据量较大时,一次性加载所有数据会影响性能和用户体验,因此需要分页显示数据。
3.1.1 分页核心参数
| 参数 | 说明 |
|---|---|
CurrentPage |
当前页码(从1开始) |
PageSize |
每页显示条数 |
TotalCount |
总记录数 |
TotalPage |
总页数 |
3.1.2 页码范围计算公式
起始行号 = (CurrentPage - 1) * PageSize + 1
结束行号 = CurrentPage * PageSize
示例:PageSize=3
- 第1页:RowId ≥ 1 且 RowId ≤ 3
- 第2页:RowId ≥ 4 且 RowId ≤ 6
- 第3页:RowId ≥ 7 且 RowId ≤ 9
3.2 方式一:ROW_NUMBER() 函数分页(兼容 SQL Server 2005+)
3.2.1 基本语法
sql
SELECT * FROM (
SELECT ROW_NUMBER() OVER (ORDER BY 排序列) AS RowId, *
FROM 表名
) AS 别名
WHERE RowId BETWEEN 起始行号 AND 结束行号
3.2.2 参数化示例
sql
-- 声明变量
DECLARE @CurrentPage INT;
DECLARE @PageSize INT;
-- 赋值
SET @CurrentPage = 1; -- 第1页
SET @PageSize = 3; -- 每页3条
-- 分页查询
SELECT * FROM (
SELECT ROW_NUMBER() OVER (ORDER BY Age) AS RowId, *
FROM Students
) AS S
WHERE RowId BETWEEN (@CurrentPage - 1) * @PageSize + 1
AND @CurrentPage * @PageSize
说明:
ROW_NUMBER():为结果集的每一行生成一个递增的序号OVER (ORDER BY Age):按 Age 列排序后再生成序号- 子查询必须起别名(此处为
S),否则语法错误
3.3 方式二:OFFSET...FETCH NEXT 分页(SQL Server 2012+)
3.3.1 基本语法
sql
SELECT * FROM 表名
ORDER BY 排序列
OFFSET 跳过的行数 ROWS
FETCH NEXT 取的行数 ROWS ONLY
3.3.2 参数化示例
sql
-- 声明变量
DECLARE @CurrentPage INT;
DECLARE @PageSize INT;
-- 赋值
SET @CurrentPage = 1;
SET @PageSize = 3;
-- 分页查询
SELECT * FROM Students
ORDER BY StudentId
OFFSET (@CurrentPage - 1) * @PageSize ROWS -- 跳过前N行
FETCH NEXT @PageSize ROWS ONLY -- 取接下来的N行
说明:
OFFSET:指定跳过多少行FETCH NEXT:指定在跳过之后返回多少行- 必须配合
ORDER BY使用,否则语法错误 - 代码更简洁,性能与 ROW_NUMBER 相当或略优
3.4 两种分页方式对比
| 对比项 | ROW_NUMBER() | OFFSET...FETCH |
|---|---|---|
| 最低版本 | SQL Server 2005 | SQL Server 2012 |
| 语法复杂度 | 需要子查询,稍复杂 | 简洁直观 |
| 是否必须ORDER BY | 是(在OVER内) | 是(外层必须) |
| 适用场景 | 需要行号或兼容旧版本 | 2012以上版本推荐 |
3.5 C# WinForm 分页完整实现
以下是基于 C# WinForm + SQL Server + OFFSET...FETCH NEXT 的完整分页实现代码:
3.5.1 核心变量定义
csharp
/// <summary>
/// 数据库连接字符串(从App.config配置文件读取)
/// </summary>
public readonly string connectionString = ConfigurationManager.ConnectionStrings["conString"].ConnectionString;
/// <summary>
/// 当前页码(从1开始)
/// </summary>
public int PageIndex = 1;
/// <summary>
/// 每页显示条数
/// </summary>
public int PageSize = 3;
/// <summary>
/// 总页数
/// </summary>
public int TotalPage = 0;
3.5.2 分页数据绑定方法
csharp
/// <summary>
/// 绑定分页数据到DataGridView
/// 包含:条件查询、参数化、OFFSET...FETCH分页、总数统计、按钮状态控制
/// </summary>
private void BindGridView()
{
// ========== 1. 解析查询条件(int.TryParse 安全转换)==========
int.TryParse(txtStartScore.Text, out int scoreStart);
int.TryParse(txtEndScore.Text, out int scoreEnd);
// ========== 2. 定义SQL参数 ==========
SqlParameter[] parameters = new SqlParameter[]
{
new SqlParameter("@PageIndex", PageIndex),
new SqlParameter("@PageSize", PageSize),
new SqlParameter("@Name", $"%{txtName.Text}%"),
new SqlParameter("@ScoreStart", scoreStart),
new SqlParameter("@ScoreEnd", scoreEnd),
};
// ========== 3. 构建查询语句(基于视图 vm_student)==========
// 统计总数SQL(不带分页)
string sql1 = "select count(*) from vm_student where 1 = 1";
// 分页查询SQL(不带分页条件,后续追加)
string sql2 = "select * from vm_student where 1 = 1";
// ========== 4. 动态拼接查询条件 ==========
// 姓名模糊查询
if (!string.IsNullOrWhiteSpace(txtName.Text))
{
sql1 += " and stu_name like @Name";
sql2 += " and stu_name like @Name";
}
// C#分数下限
if (!string.IsNullOrWhiteSpace(txtStartScore.Text))
{
sql1 += " and score_c# >= @ScoreStart";
sql2 += " and score_c# >= @ScoreStart";
}
// C#分数上限
if (!string.IsNullOrWhiteSpace(txtEndScore.Text))
{
sql1 += " and score_c# <= @ScoreEnd";
sql2 += " and score_c# <= @ScoreEnd";
}
// 追加 OFFSET...FETCH NEXT 分页语句
sql2 += " order by stu_id offset (@PageIndex - 1) * @PageSize rows fetch next @PageSize rows only";
// ========== 5. 执行分页查询并绑定数据 ==========
DataSet dataSet = SqlHelper.ExecuteDataset(connectionString, CommandType.Text, sql2, parameters);
dataGridView1.DataSource = dataSet.Tables[0];
// 列宽自适应填充
dataGridView1.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill;
// ========== 6. 计算总页数(使用 Math.Ceiling 向上取整)==========
object result = SqlHelper.ExecuteScalar(connectionString, CommandType.Text, sql1, parameters);
if (int.TryParse(result.ToString(), out int totalCount))
{
// Math.Ceiling:将浮点结果向上取整,再转为int
TotalPage = (int)Math.Ceiling((float)totalCount / PageSize);
}
// 显示当前页/总页数
UpdatePageInfo(TotalPage);
// ========== 7. 按钮状态控制 ==========
if (PageIndex == 1)
{
// 第一页:禁用首页和上一页
btnFirst.Enabled = false;
btnPrev.Enabled = false;
btnNext.Enabled = true;
btnLast.Enabled = true;
}
else if (PageIndex == TotalPage)
{
// 最后一页:禁用下一页和末页
btnFirst.Enabled = true;
btnPrev.Enabled = true;
btnNext.Enabled = false;
btnLast.Enabled = false;
}
else
{
// 中间页:全部启用
btnFirst.Enabled = true;
btnPrev.Enabled = true;
btnNext.Enabled = true;
btnLast.Enabled = true;
}
}
/// <summary>
/// 更新页码显示信息
/// </summary>
/// <param name="totalPage">总页数</param>
private void UpdatePageInfo(int totalPage)
{
lblPageInfo.Text = $"{PageIndex}/{totalPage}";
}
3.5.3 分页按钮事件处理
csharp
/// <summary>
/// 窗体加载事件:初始化分页数据和每页条数下拉框
/// </summary>
private void Form1_Load(object sender, EventArgs e)
{
BindGridView();
cmbPageSize.SelectedIndex = 0;
}
/// <summary>
/// 查询按钮:根据条件查询并刷新分页
/// </summary>
private void btnQuery_Click(object sender, EventArgs e)
{
BindGridView();
}
/// <summary>
/// 首页按钮:跳转到第1页(添加判断避免重复绑定)
/// </summary>
private void btnFirst_Click(object sender, EventArgs e)
{
if (PageIndex != 1)
{
PageIndex = 1;
BindGridView();
}
}
/// <summary>
/// 上一页按钮:页码减1
/// </summary>
private void btnPrev_Click(object sender, EventArgs e)
{
if (PageIndex > 1)
{
PageIndex--;
}
BindGridView();
}
/// <summary>
/// 下一页按钮:页码加1
/// </summary>
private void btnNext_Click(object sender, EventArgs e)
{
if (PageIndex < TotalPage)
{
PageIndex++;
}
BindGridView();
}
/// <summary>
/// 末页按钮:跳转到最后一页(添加判断避免重复绑定)
/// </summary>
private void btnLast_Click(object sender, EventArgs e)
{
if (PageIndex != TotalPage)
{
PageIndex = TotalPage;
BindGridView();
}
}
/// <summary>
/// 跳转按钮:跳转到指定页码(带输入合法性验证)
/// </summary>
private void btnRedirectTo_Click(object sender, EventArgs e)
{
// 验证是否为合法整数
if (int.TryParse(txtPageIndex.Text, out int pageIndex))
{
// 验证页码范围是否有效
if (pageIndex <= TotalPage && pageIndex >= 1)
{
PageIndex = pageIndex;
BindGridView();
return;
}
}
MessageBox.Show("请输入正确的页码!");
}
/// <summary>
/// 每页条数下拉框改变事件:切换每页条数并重置为第1页
/// </summary>
private void cmbPageSize_SelectedIndexChanged(object sender, EventArgs e)
{
PageSize = Convert.ToInt32(cmbPageSize.SelectedItem);
PageIndex = 1;
BindGridView();
btnFirst.Enabled = false;
btnPrev.Enabled = false;
}
3.6 分页查询小结
-
两种SQL分页方式:
ROW_NUMBER():兼容 SQL Server 2005+,需要子查询 + BETWEEN 范围OFFSET...FETCH NEXT:SQL Server 2012+,语法更简洁(date714项目采用此方式)
-
C#分页实现要点:
- 连接字符串:从
App.config配置文件读取(ConfigurationManager) - 两个SQL:一个查总数(
COUNT(*)),一个查分页数据 - 分页方式:
OFFSET (@PageIndex-1)*@PageSize ROWS FETCH NEXT @PageSize ROWS ONLY - 参数化查询:使用
SqlParameter+int.TryParse安全解析,防止SQL注入和格式异常 - 动态条件:支持姓名模糊查询(
LIKE @Name)、C#分数范围查询(>=/<=) - 总页数计算:
(int)Math.Ceiling((float)totalCount / PageSize)浮点数向上取整 - 跳转验证:
int.TryParse+ 范围判断 + 错误提示,避免非法跳转 - 按钮优化:
PageIndex != 1条件判断,避免第一页重复点击首页时重复绑定 - 辅助方法:
UpdatePageInfo()独立封装页码显示更新,代码更清晰
- 连接字符串:从
-
关键步骤:
- 定义分页参数(PageIndex/PageSize)→ 安全解析输入 → 构建两条SQL
- 动态拼接查询条件 → 追加 OFFSET...FETCH 分页语句
- 执行分页查询绑定 DataGridView → 执行 COUNT 计算总页数
Math.Ceiling向上取整算总页 → 调用 UpdatePageInfo 显示页码- 根据当前页位置控制按钮启用状态 → 跳转按钮做合法性校验