• 分页写法小结


    分页写法小结

    分页的实现方式有这几种:
    1. 在前台程序中,将所有的记录都读到本地,前台程序通过游标在数据集中上下移动,数据量大的话,性能很差,不推荐
    2. 前台程序请求某一页数据时,到数据库做一次查询,返回符合条件的相应记录,这也是目前常用的方法
    3. 对方式2的改进,当请求某一页时,同时将前后几页一并返回,用户翻页时就不需要反复请求数据库了。


    对于2,3的实现,随着SQL Server版本的升级,常用的方法有三种:TOP,ROW_NUMBER,OFFSET/FETCH NEXT。
    测试数据:

    复制代码
    if OBJECT_ID('split_pages') is not null
    drop table split_pages
    GO
    create table split_pages 
    (
    ID int primary key,
    Name varchar(100) 
    )
    GO
    declare @i int
    set @i=1
    while @i<=300
    begin
    insert into split_pages
    select @i,'TEST' + CAST(@i as varchar(10))
    set @i=@i+10
    end
    GO
    select * from split_pages
    复制代码

    一. TOP

    SQL Server 2000时多用,另外,有人用过set rowcount来分页,原理类似TOP,不过需要反复设置set rowcount,不方便,而且set rowcount中的行数不能参与select语句的执行计划,只能起到控制行数的作用。
    1. 哪种TOP写法更高效
    (1) 利用order by正反排序

    复制代码
    select * 
    from (select top 10 * 
    from (select top 20 * from split_pages order by ID) a 
    order by ID desc) b 
    order by ID
    复制代码

    公式为:

    复制代码
    declare @page_no int
    declare @page_size int
    
    select * 
    from (select top @page_size * 
    from (select top @page_size*@page_no * from split_pages order by ID) a 
    order by ID desc) b 
    order by ID
    复制代码

    做完最里层select后,再对派生表查询时,index就没有效果了,而且越往后面要top更多的数据,这种写法会更慢。

    (2) 利用NOT IN或者NOT EXISTS

    复制代码
    select top 10 *
    from split_pages
    where ID NOT IN (select top 10 ID from split_pages order by ID)
    order by ID
    复制代码

    公式为:

    复制代码
    declare @page_no int
    declare @page_size int
    
    select top @page_size *
    from split_pages
    where ID NOT IN (select top @page_size*(@page_no-1) ID from split_pages order by ID)
    order by ID
    复制代码

    通常在写SQL语句时,用IN/EXISTS不一样,如果逻辑不变的话, EXISTS的效率高。

    不过,利用NOT IN分页,和用NOT EXISTS效果基本一样,因为都需要扫完全部数据。

    (3) 利用ID大于MAX(ID)

    复制代码
    select top 10 *
    from split_pages
    where ID > (select MAX(ID) from (select top 10 ID from split_pages order by ID) t)
    order by ID
    复制代码

    公式为:

    复制代码
    declare @page_no int
    declare @page_size int
    
    select top @page_size *
    from split_pages
    where ID > (select MAX(ID) from (select top @page_size*(@page_no-1) ID from split_pages order by ID) t)
    order by ID
    复制代码

    在使用TOP分页时,这种用法效率最高。

    2. SQL Server 2000与2005中TOP的区别
    (1) SQL Server 2000时,top不支持变量,所以分页时,这些语句都要改成动态SQL
    如下:

    复制代码
    declare @page_no int
    declare @page_size int
    declare @sql varchar(8000)
    
    set @page_no = 2
    set @page_size = 10
    set @sql = 
    'select top ' + str(@page_size) + '*
    from split_pages ' + '
    where ID > (select MAX(ID) from (select top ' + str(@page_size*(@page_no-1))+ ' ID from split_pages order by ID) t)
    order by ID'
    exec(@sql)
    复制代码

    (2) SQL Server 2005开始,top可以直接使用变量

    如下:

    复制代码
    declare @page_no int
    declare @page_size int
    set @page_no = 2
    set @page_size = 10
    select top (@page_size) *
    from split_pages
    where ID NOT IN (select top (@page_size*(@page_no-1)) ID from split_pages order by ID)
    order by ID
    复制代码

    二. ROW_NUMBER
    SQL Server 2005开始的新语法,和ORACLE,DB2中的row_number()类似。性能比用TOP有所提升。

    在利用ROW_NUMBER分页时,总页数/行数的计算,可以有这几种写法。
    (1) 单独的SQL语句去获得总行数

    复制代码
    select COUNT(*) AS TotRows
    from split_pages
    GO
    declare @page_no int
    declare @page_size int
    
    set @page_no = 2
    set @page_size = 10
    
    ;with tmp
    AS
    (
    select *,
    ROW_NUMBER() OVER(order by ID) num
    from split_pages
    )
    select ID, Name
    from tmp
    where num BETWEEN (@page_size*(@page_no-1)+1) AND @page_size*@page_no
    order by num
    复制代码

    (2) 在ROW_NUMBER的同时用COUNT计算总行数

    复制代码
    declare @page_no int
    declare @page_size int
    
    set @page_no = 2
    set @page_size = 10
    
    ;WITH tmp
    AS
    (
    select *, 
    ROW_NUMBER() OVER(order by ID) num, 
    COUNT(*) OVER() total
    from split_pages
    )
    select ID, Name
    from tmp
    where num BETWEEN (@page_size*(@page_no-1)+1) AND @page_size*@page_no
    order by num
    复制代码

    (3) 仅使用ROW_NUMBER计算总行数,IO最少

    复制代码
    declare @page_no int
    declare @page_size int
    
    set @page_no = 2
    set @page_size = 10
    
    ;with tmp
    as
    (
    select *, 
    ROW_NUMBER() OVER(order by ID) num, 
    ROW_NUMBER() OVER(order by ID desc) num_desc
    from split_pages
    )
    select ID, Name, num_desc + num -1 as total
    from tmp
    where num BETWEEN (@page_size*(@page_no-1)+1) AND @page_size*@page_no
    order by num
    复制代码

    三. OFFSET/FETCH NEXT
    SQL Server 2012的新语法,类似MYSQL,POSTGRESQL中的LIIMIT/OFFSET,据称性能比ROW_NUMBER又有了提升。

    复制代码
    declare @page_no int
    declare @page_size int
    
    set @page_no = 3
    set @page_size = 10
    
    SELECT *,COUNT(*) OVER() AS Total
    FROM split_pages
    ORDER BY ID
    OFFSET (@page -1) * @size ROWS
    FETCH NEXT @size ROWS ONLY;
    复制代码

    四. 其他方法
    (1) 临时表
    在SQL Server 2000时,利用临时表生成rownum来分页,不推荐。

    复制代码
    select IDENTITY(int,1,1) as num,* into #t from split_pages
    select * from #t where num between 11 and 20
    复制代码

    (2) sp_cursoropen
    利用游标,性能较差,原理类似在程序中把数据全部加载到本地,然后通过游标移动来分页。不作推荐。

     

    呵呵第一次认真写一次技术问题 今天遇到的Linq未提交之前插入/修改时重新查询不准确问题

     

    来园子已经两年了,每次都是看,这次咱也写一次。

    说一下今天遇到的Linq问题:

    每一次插入流水表时,都需要查找表中最大的流水号+1,并且将该流水号返回,但是在同一个SubmitChange之内插入多条时,流水号就一直是表中实际最大的,而不是我上一次插入的最大的。不描述了 贴代码:

    这个是DataContext

    复制代码
     View Code

    public class DataContext : LinqDataContext
    {
    public DataContext()
    : base()
    { }

    /// <summary>
    /// 打开隐式Linq事务
    /// 对于在BeginTransaction()之前已经SubmitChanges()的操作不在此事务之内。
    /// 开启事务以后,必须要Commit()提交事务才会更改到数据库中。
    /// </summary>
    public void BeginTransaction()
    {
    if (this.Connection.State == ConnectionState.Closed)
    {
    this.Connection.Open();
    this.Transaction = this.Connection.BeginTransaction();
    }
    }

    /// <summary>
    /// 提交隐式Linq事务
    /// 对于在BeginTransaction()之前已经SubmitChanges()的操作不在此事务之内。
    /// </summary>
    public void Commit()
    {
    if (this.Transaction != null)
    this.Transaction.Commit();
    }
    }

    复制代码

     两个公用方法:

    复制代码
     View Code

    /// <summary>
    /// 取得数据表中最大的Number值
    /// </summary>
    /// <param name="da"></param>
    /// <returns></returns>
    static int GetMaxNumber(DataContext da)
    {
    return (from v in da.LinqTable
    orderby v.Number descending
    select v.Number).FirstOrDefault();
    }

    static void Continue()
    {
    Console.WriteLine("请按任意键继续");
    Console.ReadKey();
    }

    复制代码

    表结构:

    复制代码
     View Code

    /*
    * LinqTable表结构
    * ID int 自增主键
    * Number int NOT NULL
    */

    复制代码

    第一种情况:修改提交前可以重新查询获得已经更新的内容,读取的是内存中的 未使用隐式事务

    复制代码
     View Code
    复制代码

    //假设数据表中只有一条数据 1 1

    Console.WriteLine("修改提交前可以重新查询获得已经更新的内容 未使用隐式事务");
    Continue();
    using (var da = new DataContext())
    {
    var single = (from v in da.LinqTable
    where v.ID.Equals(1)
    select v).Single();
    Console.WriteLine("读取ID为1的Number值为:" + single.Number);
    //输出:读取ID为1的Number值为:1
    Continue();
    single.Number = -single.Number;
    Console.WriteLine("将Number值修改为该值的相反数。");
    Continue();
    var newSingle = (from v in da.LinqTable
    where v.ID.Equals(1)
    select v).Single();
    Console.WriteLine("未提交之前重新查询ID为1的Number值为:" + newSingle.Number);
    //输出:未提交之前重新查询ID为1的Number值为::-1
    Continue();
    da.SubmitChanges();
    var submitSingle = (from v in da.LinqTable
    where v.ID.Equals(1)
    select v).Single();
    Console.WriteLine("提交之后重新查询ID为1的Number值为:" + submitSingle.Number);
    //输出:提交之后重新查询ID为1的Number值为:-1
    Continue();
    }

    /*
    * 修改时,在未提交之前重新查询改对象的值是查询内存中的
    * 但是新增时又不一样,请看下面
    */

    第二种情况:添加后未提交前取得的最大的Number值永远是数据表中真实的值,不是内存中的 未使用隐式事务
    复制代码
     View Code

    //假设数据表中只有一条数据 1 1
    using (var da = new DataContext())
    {
    LinqTable t1 = new LinqTable()
    {
    Number = GetMaxNumber(da) + 1,
    };
    da.LinqTable.InsertOnSubmit(t1);
    Console.WriteLine("添加t1,t1的Number为:" + t1.Number + ",此时重新查询最大的Number为:" + GetMaxNumber(da));
    //输出:添加t1,t1的Number为:2,此时重新查询最大的Number为:1
    //想要的效果:添加t1,t1的Number为:2,此时重新查询最大的Number为:2
    Continue();
    LinqTable t2 = new LinqTable()
    {
    Number = GetMaxNumber(da) + 1,
    };
    da.LinqTable.InsertOnSubmit(t2);
    Console.WriteLine("添加t2,t2的Number为:" + t2.Number + ",此时重新查询最大的Number为:" + GetMaxNumber(da));
    //输出:添加t2,t2的Number为:2,此时重新查询最大的Number为:1
    //想要的效果:添加t2,t2的Number为:3,此时重新查询最大的Number为:3
    Continue();
    da.SubmitChanges();
    }

    /*
    * 根据第一种情况,结果应该是我想要的那种结果,但事实上不是。
    * 这个是什么原因呢?求解。
    */

    复制代码

    第三种情况:开启隐式事务,添加后提交前取得的最大的Number值是我想要的值,最后再Commit

    复制代码
     View Code

    //假设数据表中只有一条数据 1 1
    using (var da = new DataContext())
    {
    da.BeginTransaction();//开启隐式事务
    LinqTable t1 = new LinqTable()
    {
    Number = GetMaxNumber(da) + 1,
    };
    da.LinqTable.InsertOnSubmit(t1);
    da.SubmitChanges();//插入后立即提交
    Console.WriteLine("添加t1,t1的Number为:" + t1.Number + ",此时重新查询最大的Number为:" + GetMaxNumber(da));
    //输出:添加t1,t1的Number为:2,此时重新查询最大的Number为:2
    //是我想要的效果
    Continue();
    LinqTable t2 = new LinqTable()
    {
    Number = GetMaxNumber(da) + 1,
    };
    da.LinqTable.InsertOnSubmit(t2);
    da.SubmitChanges();//插入后立即提交
    Console.WriteLine("添加t2,t2的Number为:" + t2.Number + ",此时重新查询最大的Number为:" + GetMaxNumber(da));
    //输出:添加t2,t2的Number为:3,此时重新查询最大的Number为:3
    //是我想要的效果
    Continue();
    da.Commit();
    }

    /*
    * 这次开启了隐式事务,达到了我想要的结果,但是第二种情况为什么不行?
    * TransactionCope 需要开启DTC 不建议使用
    */

    复制代码

    这就是今天遇到的问题,现在没办法只能开启隐式事务了,不过为什么第二种情况达不到我想要的结果呢?求大神解。

     
     
     
    分类: .Net
     
    标签: 分页TOPROW_NUMBEROFFSET
  • 相关阅读:
    BZOJ 2006: [NOI2010]超级钢琴 [ST表+堆 | 主席树]
    CF 741D. Arpa’s letter-marked tree and Mehrdad’s Dokhtar-kosh paths [dsu on tree 类似点分治]
    CF 716E. Digit Tree [点分治]
    CF 291E. Tree-String Problem [dfs kmp trie图优化]
    CF 208E. Blood Cousins [dsu on tree 倍增]
    CF 246E. Blood Cousins Return [dsu on tree STL]
    CF 570D. Tree Requests [dsu on tree]
    [dsu on tree]【学习笔记】
    测试markdown
    BZOJ 1969: [Ahoi2005]LANE 航线规划 [树链剖分 时间倒流]
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/3273597.html
Copyright © 2020-2023  润新知