核心考点:两种分页方式(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)*每页条数+1,结束行=当前页*每页条数,两种分页方式均依赖此公式。

  2. ROW_NUMBER():必须配合OVER子句和排序字段,子查询必须起别名,适用于低版本SQL Server。

  3. OFFSET...FETCH:必须配合ORDER BY子句,OFFSET跳过行数,FETCH拉取行数,适用于2012及以上版本。

  4. 排序要求:两种分页方式均需排序(ROW_NUMBER()在OVER中,OFFSET在单独的ORDER BY中),否则结果混乱或报错。

  5. 参数作用: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:执行统计总记录数的查询,返回第一行第一列的值,用于计算总页数。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐