SQL 分页查询+C# WinForm 分页查询代码拆分版
核心考点:两种分页方式(ROW_NUMBER()、OFFSET...FETCH)、分页公式推导、版本适配(SQL Server 2012及以上/以下)、排序与分页结合使用
适配表结构:Students(StudentId, Name, Age, Sex, Address, ClassId),分页核心依赖行编号或跳过行数实现
一、分页查询基础概念
分页查询用于将大量数据按指定行数(每页条数)拆分展示,避免一次性加载所有数据,提升查询效率和用户体验。核心参数:
-
CurrentPage:当前页数(如第1页、第2页)
-
PageSize:每页显示的记录条数(如每页3条、10条)
-
RowId:行编号(用于定位每页的起始和结束位置)
分页核心公式(必背):
起始行编号:RowId ≥ (CurrentPage - 1) * PageSize + 1
结束行编号:RowId ≤ CurrentPage * PageSize
示例:若PageSize=3,第1页:1-3行;第2页:4-6行;第3页:7-9行,以此类推。
二、方式一:ROW_NUMBER() 分页(适配 SQL Server 2012以下版本)
通过 ROW_NUMBER() 函数为每条记录生成唯一行编号,结合子查询和筛选条件实现分页,兼容性强,适用于低版本SQL Server。
--声明分页参数变量
declare @CurrentPage int; --当前页数
declare @PageSize int; --每页显示条数
--为参数赋值
set @CurrentPage=1; --当前为第1页
set @PageSize = 3; --每页显示3条记录
--分页查询核心语句
select * from(
--子查询:为每条记录添加行编号RowId
--ROW_NUMBER():生成连续的行编号,从1开始
--OVER(ORDER BY Age):按年龄排序后生成行编号(排序字段可根据需求修改)
select ROW_NUMBER() over (order by Age)as RowId,* from Students
)as S --为子查询结果集起别名S(必须起别名,否则语法错误)
--筛选当前页的记录,使用分页公式
where RowId between (@CurrentPage -1)* @PageSize + 1 and @CurrentPage * @PageSize
--拓展:修改参数查看其他页
--set @CurrentPage=2; --查询第2页,显示4-6行
--set @CurrentPage=3; --查询第3页,显示7-9行
关键说明:
-
ROW_NUMBER() 函数必须配合 OVER 子句使用,OVER 子句中需指定排序字段(避免行编号混乱)。
-
子查询必须起别名(如示例中的S),否则SQL会报错,无法识别子查询结果集。
-
排序字段可根据需求修改(如按StudentId、Name排序),不影响分页逻辑。
三、方式二:OFFSET...FETCH 分页(适配 SQL Server 2012及以上版本)
SQL Server 2012及以上版本新增的分页语法,语法简洁、效率更高,无需生成行编号,直接通过“跳过行数+拉取行数”实现分页。
--声明分页参数变量
declare @CurrentPage1 int; --当前页数
declare @PageSize1 int; --每页显示条数
--为参数赋值
set @CurrentPage1=1; --当前为第1页
set @PageSize1 = 3; --每页显示3条记录
--分页查询核心语句
select * from Students --查询学生表所有字段
order by StudentId --按学生ID排序(必须排序,否则OFFSET语法报错)
--OFFSET:跳过指定条数的记录,公式:(当前页-1)*每页条数
offset (@CurrentPage1 - 1)*@PageSize1 rows
--FETCH NEXT:拉取指定条数的记录(即每页显示的条数),only表示仅拉取这些记录
fetch next @PageSize1 rows only
--拓展:修改参数查看其他页
--set @CurrentPage1=2; --查询第2页,跳过3条,拉取3条(4-6行)
--set @CurrentPage1=3; --查询第3页,跳过6条,拉取3条(7-9行)
关键说明:
-
OFFSET...FETCH 必须配合 ORDER BY 子句使用,否则会报错(需先排序才能确定跳过和拉取的记录)。
-
OFFSET 后接跳过的行数,公式为 (CurrentPage - 1) * PageSize(如第2页跳过3条,第3页跳过6条)。
-
FETCH NEXT ... ROWS ONLY 表示仅拉取指定条数的记录,不能单独使用,必须跟在 OFFSET 之后。
四、两种分页方式对比(必背)
|
分页方式 |
适配版本 |
语法复杂度 |
效率 |
核心特点 |
|---|---|---|---|---|
|
ROW_NUMBER() |
SQL Server 2012以下 |
较高(需子查询、行编号) |
一般(需生成行编号) |
兼容性强,适用于所有版本,需配合子查询使用 |
|
OFFSET...FETCH |
SQL Server 2012及以上 |
较低(语法简洁) |
较高(无需生成行编号) |
语法简洁,效率更高,必须配合ORDER BY使用 |
五、核心考点汇总(必背)
-
分页公式:起始行=(当前页-1)*每页条数+1,结束行=当前页*每页条数,两种分页方式均依赖此公式。
-
ROW_NUMBER():必须配合OVER子句和排序字段,子查询必须起别名,适用于低版本SQL Server。
-
OFFSET...FETCH:必须配合ORDER BY子句,OFFSET跳过行数,FETCH拉取行数,适用于2012及以上版本。
-
排序要求:两种分页方式均需排序(ROW_NUMBER()在OVER中,OFFSET在单独的ORDER BY中),否则结果混乱或报错。
-
参数作用:CurrentPage控制当前页数,PageSize控制每页显示条数,修改参数即可切换不同页面。
六、易错踩坑点
-
使用ROW_NUMBER()时,忘记为子查询起别名,导致语法错误。
-
使用OFFSET...FETCH时,省略ORDER BY子句,导致报错(必须先排序)。
-
分页公式计算错误,如起始行少加1(正确:(CurrentPage-1)*PageSize+1),导致页码错位。
-
排序字段选择不当,导致行编号或跳过/拉取的记录混乱,建议使用主键(如StudentId)排序。
-
低版本SQL Server使用OFFSET...FETCH,导致语法不支持,需切换为ROW_NUMBER()方式。
七、语法速记模板
--模板1:ROW_NUMBER()分页(低版本)
declare @CurrentPage int, @PageSize int
set @CurrentPage=当前页
set @PageSize=每页条数
select * from(
select ROW_NUMBER() over (order by 排序字段)as RowId,* from 表名
)as 别名
where RowId between (@CurrentPage-1)*@PageSize+1 and @CurrentPage*@PageSize
--模板2:OFFSET...FETCH分页(2012及以上)
declare @CurrentPage int, @PageSize int
set @CurrentPage=当前页
set @PageSize=每页条数
select * from 表名
order by 排序字段
offset (@CurrentPage-1)*@PageSize rows
fetch next @PageSize rows only
C# WinForm 分页查询代码拆分版
说明:代码按「引用与命名空间」「全局变量」「构造函数」「核心数据绑定」「按钮事件」拆分,保留原功能,适配期末复习查看。
模块1:命名空间与程序集引用
核心作用:引入所需类库,支持数据库操作、WinForm界面、数据处理等功能。
using Microsoft.ApplicationBlocks.Data.Ch; // 引入SqlHelper所在程序集(数据库操作封装类)
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data; // 用于DataSet、DataTable等数据类型,承载查询结果
using System.Data.SqlClient; // 用于SqlParameter、SqlDbType等数据库相关类型
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms; // WinForm界面控件相关(窗体、按钮、文本框等)
模块2:命名空间与窗体类定义
核心作用:定义项目命名空间和窗体主类,承载所有分页查询相关逻辑。
namespace 分页查询
{
public partial class Form1 : Form
{
// 后续所有模块代码依次插入此处
}
}
模块3:全局变量定义
核心作用:定义数据库连接字符串和分页核心参数,全局可用,方便后续调用。
// 数据库连接字符串(硬编码,实际开发建议配置在app.config中)
public string connString = "server=.,1344;database=BankSystem;uid=sa;pwd=123456";
public int CurrentPage = 1; // 当前页数,默认初始化为第1页
public int PageSize = 3; // 每页显示的记录条数,默认每页3条
public int TotalPage = 0; // 总页数,初始化为0,后续通过计算赋值
模块4:构造函数(Form1)
核心作用:初始化窗体,加载所有控件,无额外业务逻辑。
public Form1()
{
InitializeComponent(); // 初始化窗体所有控件(由WinForm设计器自动生成)
}
模块5:核心数据绑定方法(BindGridView)
核心作用:实现分页查询、条件筛选、总页数计算、按钮状态控制,是整个分页功能的核心。
///
public void BindGridView()
{
// 1. 定义统计总记录数的SQL语句(用于计算总页数)
string sql1 = "select count(*) from StudentInfo where 1=1 ";
// 2. 定义SQL参数,防止SQL注入,适配筛选条件和分页参数
SqlParameter[] sqlParameters = new SqlParameter[]
{
new SqlParameter ("@CurrentPage", CurrentPage), // 当前页数(分页用)
new SqlParameter ("@PageSize", PageSize), // 每页条数(分页用)
new SqlParameter ("@StudentName", $"%{txtStudentName.Text}%"), // 学生姓名模糊查询
new SqlParameter ("@ScoreStart", SqlDbType.Int), // 成绩起始值(整数类型)
new SqlParameter ("@ScoreEnd", SqlDbType.Int) // 成绩结束值(整数类型)
};
// 3. 为成绩起始值参数赋值(处理文本框空值情况)
if (!string.IsNullOrEmpty(txtScoreStart.Text))
{
sqlParameters[3].Value = int.Parse(txtScoreStart.Text);
}
else
{
sqlParameters[3].Value = 0; // 文本框为空时,赋值0
}
// 4. 为成绩结束值参数赋值(处理文本框空值情况)
if (!string.IsNullOrEmpty(txtScoreEnd.Text))
{
sqlParameters[4].Value = int.Parse(txtScoreEnd.Text);
}
else
{
sqlParameters[4].Value = 0; // 文本框为空时,赋值0
}
// 5. 定义分页查询的SQL语句(基于ROW_NUMBER()生成行编号实现分页)
string sql2 = "select * from (select ROW_NUMBER() over (order by Score) as RowId,* from StudentInfo) as S where 1=1 ";
// 6. 拼接筛选条件(同时拼接统计总记录数和分页查询的SQL)
// 条件1:学生姓名模糊查询(文本框非空时拼接)
if (!string.IsNullOrEmpty(txtStudentName.Text))
{
sql1 += "and StudentName like @StudentName ";
sql2 += "and StudentName like @StudentName ";
}
// 条件2:成绩大于等于起始值(文本框非空时拼接)
if (!string.IsNullOrEmpty(txtScoreStart.Text))
{
sql1 += "and Score >= @ScoreStart ";
sql2 += "and Score >= @ScoreStart ";
}
// 条件3:成绩小于等于结束值(文本框非空时拼接)
if (!string.IsNullOrEmpty(txtScoreEnd.Text))
{
sql1 += "and Score <= @ScoreEnd ";
sql2 += "and Score <= @ScoreEnd ";
}
// 拼接分页核心条件:根据当前页数和每页条数,筛选对应行
sql2 += "and RowId between (@CurrentPage-1) * @PageSize + 1 and @CurrentPage * @PageSize ";
// 7. 执行分页查询,将结果绑定到DataGridView
DataSet set = SqlHelper.ExecuteDataset(connString, CommandType.Text, sql2, sqlParameters);
dataGridView1.DataSource = set.Tables[0]; // 将查询结果的第一张表绑定到控件
// 8. 执行统计查询,获取总记录数,计算总页数
object result = SqlHelper.ExecuteScalar(connString, CommandType.Text, sql1, sqlParameters);
int totalCount = Convert.ToInt32(result); // 总记录数
int total = 0; // 总页数
// 计算总页数:向上取整,公式 (总记录数 + 每页条数 - 1) / 每页条数
if (totalCount == 0)
{
total = 0; // 无数据时,总页数为0
}
else
{
total = (totalCount + PageSize - 1) / PageSize;
}
TotalPage = result != null ? total : 0; // 赋值总页数,处理无数据情况
// 9. 页码越界处理:当前页数超过总页数时,自动切换到最后一页
if (TotalPage > 0 && CurrentPage > TotalPage)
{
CurrentPage = TotalPage;
// 重新查询数据,刷新界面
DataSet set1 = SqlHelper.ExecuteDataset(connString, CommandType.Text, sql2, sqlParameters);
dataGridView1.DataSource = set1.Tables[0];
}
// 10. 更新页码显示标签(格式:当前页 / 总页数)
lblCurrentPageAndTotalPage.Text = $"{CurrentPage} / {TotalPage}";
// 11. 控制分页按钮的启用/禁用状态
// 情况1:当前页为第1页,禁用“首页”“上一页”
if (CurrentPage == 1)
{
btnFirst.Enabled = false;
btnPrev.Enabled = false;
btnNext.Enabled = true;
btnLast.Enabled = true;
}
// 情况2:当前页为最后一页,禁用“下一页”“末页”
if (CurrentPage == TotalPage)
{
btnFirst.Enabled = true;
btnPrev.Enabled = true;
btnNext.Enabled = false;
btnLast.Enabled = false;
}
// 情况3:当前页在中间,所有按钮启用
if (CurrentPage > 1 && CurrentPage < TotalPage)
{
btnFirst.Enabled = true;
btnPrev.Enabled = true;
btnNext.Enabled = true;
btnLast.Enabled = true;
}
}
模块6:搜索按钮点击事件(btnSearch_Click)
核心作用:点击搜索按钮时,执行查询,刷新DataGridView数据。
///
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnSearch_Click(object sender, EventArgs e)
{
CurrentPage = 1; // 搜索时,重置为第1页
BindGridView(); // 调用核心方法,刷新数据
}
模块7:窗体加载事件(Form1_Load)
核心作用:窗体打开时,自动加载初始数据,初始化每页条数下拉框。
///
/// <param name="sender"></param>
/// <param name="e"></param>
private void Form1_Load(object sender, EventArgs e)
{
BindGridView(); // 加载初始数据
cbbPageSize.SelectedIndex = 0; // 下拉框默认选中第一个选项(如3条/页)
}
模块8:首页按钮点击事件(btnFirst_Click)
核心作用:点击首页按钮,跳转到第1页,刷新数据。
///
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnFirst_Click(object sender, EventArgs e)
{
CurrentPage = 1; // 重置当前页数为1
BindGridView(); // 刷新数据
}
模块9:上一页按钮点击事件(btnPrev_Click)
核心作用:点击上一页按钮,跳转到当前页的上一页(需校验页码有效性)。
///
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnPrev_Click(object sender, EventArgs e)
{
if (CurrentPage > 1) // 防止页码小于1
{
CurrentPage--; // 当前页数减1
}
BindGridView(); // 刷新数据
}
模块10:下一页按钮点击事件(btnNext_Click)
核心作用:点击下一页按钮,跳转到当前页的下一页(需校验页码有效性)。
///
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnNext_Click(object sender, EventArgs e)
{
if (CurrentPage < TotalPage) // 防止页码超过总页数
{
CurrentPage++; // 当前页数加1
}
BindGridView(); // 刷新数据
}
模块11:末页按钮点击事件(btnLast_Click)
核心作用:点击末页按钮,跳转到最后一页,刷新数据。
///
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnLast_Click(object sender, EventArgs e)
{
CurrentPage = TotalPage; // 重置当前页数为总页数
BindGridView(); // 刷新数据
}
模块12:每页条数下拉框选择事件(cbbPageSize_SelectedIndexChanged)
核心作用:切换每页显示条数,重置为第1页,刷新数据。
///
/// <param name="sender"></param>
/// <param name="e"></param>
private void cbbPageSize_SelectedIndexChanged(object sender, EventArgs e)
{
// 将下拉框选中的值转为整数,更新每页条数
PageSize = int.Parse(cbbPageSize.SelectedItem.ToString());
CurrentPage = 1; // 切换每页条数后,重置为第1页
BindGridView(); // 刷新数据
// 重置按钮状态:第1页时,禁用“首页”“上一页”
btnFirst.Enabled = false;
btnPrev.Enabled = false;
}
模块13:跳转指定页码按钮点击事件(btnGo_Click)
核心作用:根据文本框输入的页码,跳转到指定页面(原代码未做校验,保留原样)。
///
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnGo_Click(object sender, EventArgs e)
{
// 直接将文本框内容转为整数,赋值给当前页数(原代码未做校验)
CurrentPage = int.Parse(txtCurrentPage.Text);
BindGridView(); // 刷新数据
}
核心考点与优化说明
1. 分页核心逻辑(必背)
-
总页数计算:公式
(总记录数 + 每页条数 - 1) / 每页条数,实现向上取整,避免出现小数页数。 -
分页查询SQL:基于
ROW_NUMBER()生成行编号,结合between ... and ...筛选当前页记录,适配低版本SQL Server。 -
参数化查询:所有筛选条件和分页参数均使用
SqlParameter,防止SQL注入,符合考试和开发规范。
2. 关键优化点(避免报错)
-
添加页码输入校验(
int.TryParse),避免输入非整数导致程序崩溃。 -
添加页码范围校验(1到总页数),避免输入无效页码。
-
处理成绩文本框空值情况,为空时赋值0,避免拼接SQL时出现语法错误。
-
添加页码越界处理,当前页数超过总页数时,自动切换到最后一页。
3. 控件功能对应(适配窗体设计)
-
txtStudentName:学生姓名筛选文本框(模糊查询)。 -
txtScoreStart/txtScoreEnd:成绩范围筛选文本框(起始/结束值)。 -
cbbPageSize:每页条数下拉框(如3、10、20条/页)。 -
txtCurrentPage:指定页码输入框,配合btnGo实现跳转。 -
lblCurrentPageAndTotalPage:显示当前页/总页数的标签。 -
btnFirst/btnPrev/btnNext/btnLast:分页按钮(首页/上一页/下一页/末页)。
4. SqlHelper核心方法(必考)
-
ExecuteDataset:执行分页查询,返回DataSet,用于绑定DataGridView。 -
ExecuteScalar:执行统计总记录数的查询,返回第一行第一列的值,用于计算总页数。
更多推荐



所有评论(0)