用Session和唯一索引字段实现通用Web分页功能

本文为原创,如需转载,请注明作者和出处,谢谢!


   
Web系统虽然现在很流行,但是分页问题一直长期困扰着Web系统的开发人员。对于不同的数据库,可能开发人员对分页的处理分有很大差别。个人认为,使用MySQL开发Web系统的程序员是感到最舒服的,因为,在MySQL中提供了limit语句,可以获得查询结果的一段数据。如下面的SQL语句所示:

select * from table1 limit 1, 20

    上面的SQL表示从table1中查出记录,并返回从第2条开始的20条记录(第1条记录从0开始)。

    对于其他的数据库,恐怕就没MySQL那么容易查询出记录段了。在SQL Server2005中也提供了类似MySQL的处理方法(可以使用ROW_NUMBER()函数来实现这个功能),SQL语句如下:

 

With t AS 
(
    SELECT contactid, namestyle, lastname,
    ROW_NUMBER() over(order by namestyle) as RowNumber 
    FROM Person.Contact
)
select * from t
Where RowNumber Between 20 and 30

    虽然上面的SQL语句虽然也可以实现和MySQL一样的功能,但却比MySQL的limit复杂一些。

    如果在数据库中提供了实现Web分页的机制,就算复杂一些,也是可以解决的。但有效数据库可能并未提供这种机制。这就得使用更复杂的方法来实现Web分页,如在SQL
Server2000中未提供ROW_NUMBER()函数,就有很多开发人员通过编写分页的存储过程来处理。这样做既复杂,又不通用。假设要移植到Oracle上,还得费一番功夫。

    在本文给出一种直接使用Web中的Session对象来方式来实现分页的功能,Session是在Web系统中保存当前分话数据的。我们可以想象。分页的难点在哪里,就象MySQL中的limit语句一样,只需要有两个值:起使记录数和要获得的记录总数就可以了。要获得的记录总数这个我们很容易知道,一般就是分一页的记录数。但是起使记录数却很难获得。

    如果使用自增键当然可以,但这要建立在表只增不删,而且id从1或一个已知的起始位置开始的情况。如果删除了表中的一些数据,自增键就不再是从1到n,依次递增了。也就是中间可能有空档。如自增键从20至100,中间可能只有10条记录。因此,单纯使用自增键并不能很好地解决分页问题。

    但却可以将Session和自增键组合来解决分页问题。大家可以设想,在用户第一次查询时,如select
* from table1 where field1 like '%abc%',这时将记录全部查出。假设每页显示50条记录,这时可以从头开始取出50条记录。这不会有任何问题。然后,当用户要查看第2页时,最普通的做是再执行一次上面的SQL语句,然后从第51第记录开始,再取出50条记录。如果这样做,将大大浪费服务器的资源。

    为了解决这个问题,可以在每一次执行完上面的SQL语句后,除了取出前50条记录外,再通过记录的定位,将其他页面的起始id值保存在Session中(可以放在List对象中)。然后在用户要查看第2页或后面的页时,直接从Session中取出该页起始id的值,如果使用的是SQL Servlet数据库,可以使用top n,其中n表示每页记录数,来查询当前页的记录。

    先拿Java为例来说明一下。下面的代码在Session中记录了第一页到最后一页的起始id:

 

//  rs为记录集,其他语言的操作类似
ResultSet rs = stmt.executeQuery("select * from table1 where field1 like '%abc%'");  
int n = 1;
while(rs.absolute(n))
{
    int id = rs.getInt(id)
    //  将id保存在Session中
    n += 50;
}

    从上面的代码可以看出,使用ResultSet的absolute来定位记录,并取出当前记录的id值(一个自增字段),并将其保存在Session中。

    假设共查询出500条记录,那么Session中保存的id值有可能是下面的样子:

   
1 51 123 179 229 290 367 567 699

    从上面的id值可以看出,中间有断档。但这9个id值之间的记录数都是50个。如下面的SQL语句将查询出50个记录:   

select * from table1 where field1 like '%abc%' and (id >= 290 and id < 367)

    假设用户要查看第3页的话,就会取出123和179,并将其加入select 语句的where条件,类似上面的SQL语句。这样用户除了第一次查询外,查看其他页都会只返回当前页面的记录了。

    上面的方法还有一些问题,如当第一次返回的记录很多的话,使用absolute方法进行循环所有的记录可能有些慢,那可以在程序中做个约定,只循环41次,也就是保存前40页的记录,当用户要查看第41页的话,再取出第40页的开始记录的id值,将再次查询从该id值往后的所有记录,再记录40页的id值,也就是这时已经有80页的id记录被保存在Session中的。以此类推,

    当然,这种方法也不可避免地遇到删除记录的情况,如果用户正在查看页面,这时某一页的记录被删除了,当用户再次要查看这页时,根据Session中保存的id区间,就会得到少于50的记录。在这种情况下,如果使用的是SQL Servlet,就好办一些,可以在where条件中只加id的上限,不加下限,然后使用top关键字来限制查询出的记录数,SQL语句如下:

select top 50 * from table1 where field1 like '%abc%' and id >= 290

    如果使用的是其他数据库,没有类型top的关键字,可以在查询时多加一个区间,如用户要查询第2页的数据,可以将第2页和第3页的都查出来,这样一般就可以获得超过50条的记录。但如果记录数还不够(这个表的记录被删除的太多了),笔者建议重新查询所有的记录,重新更新一下Session对象中的id值。

    总之,本算法就是在第一次查询时预先将后面页面的起始记录的id值事先保存起来,然后等待以后查看其他页面时使用。如果这时某个页面的记录被删除(如果当前页面记录数不足页面记录总数,被示为有记录删除),可以重新更新一下Session中的id值,然后根据新的id值再查一遍。但要注意的是这个id值最好使用数据库的自增型字段(一般的数据库,甚至桌面数据库都会有自增型字段类型)。为了尽量避免总更新Session中的id值,可以在查询一个页面时查询出两个页面的记录,这样在一般情况下,会保证记录数超过页面记录总数。但这样做一个缺点,就是可能两个相邻页面的记录有一定的重复。不过并没有太大影响。我们在网上看某些论坛的贴子时,有时可能也会发现两个相邻页面的记录有重复。

    本分页方法适合于所有的数据库,无论是网络数据库(Oracle、SQL Servlet、DB2等),以及桌面数据库(access、paradox、pdf等)。并且不需要在数据库中建立额外的资源,如存储过程等。(当然,每个表需要有一个自增类型字段,这一点很关键)。

   
补充一下,这种方法只适合于一个排序字段的查询,而且这个排序字段值不能有重复的,也就是说得是有唯一索引的字段。在本文中使用了自增键来说明,但也可以
是其他字段,如不重复的时间字段,按时间排序后。可以使用本文的方法。而且唯一字段区间值也可以使用其他的方式保存,如viewstate,hide
input等。 

    哪位读者有更好,更通用的分页方法(最好不要在数据库中建立象存储过程一样的资源,尽量不要使用与数据库相关的语句,如SQL Server中的top),请跟贴。

 

 

国内最棒的Google Android技术社区(eoeandroid),欢迎访问!

《银河系列原创教程》发布

《Java Web开发速学宝典》出版,欢迎定购

时间: 2024-09-20 01:01:04

用Session和唯一索引字段实现通用Web分页功能的相关文章

MySQL 唯一索引和插入重复自动更新

有时我们在往数据库插入数据的时候,需要判断某个字段是否存在,如果存在则执行更新操作,如果不存在则执行插入操作,如果每次首先查询一次判断是否存在,再执行插入或者更新操作,就十分不方便. ON DUPLICATE KEY UPDATE 这个时候可以给这个字段(或者几个字段)建立唯一索引,同时使用以下 sql 语句进行插入或更新操作: INSERT INTO table (id, user_id, token) VALUES (NULL, '2479031', '232') ON DUPLICATE

MySQL建立唯一索引实现插入重复自动更新_Mysql

前言 在我们往数据库插入数据的时候,需要判断某个字段是否存在,如果存在则执行更新操作,如果不存在则执行插入操作,如果每次首先查询一次判断是否存在,再执行插入或者更新操作,就十分不方便.下面给大家分享个方法,方便大家实现这一功能,下面来一起看看吧. ON DUPLICATE KEY UPDATE 这个时候可以给这个字段(或者几个字段)建立唯一索引,同时使用以下 sql 语句进行插入或更新操作: INSERT INTO table (id, user_id, token) VALUES (NULL,

unique-select distinct 多个字段,其中有些字段有联合唯一索引,那么其他字段会参与去重吗

问题描述 select distinct 多个字段,其中有些字段有联合唯一索引,那么其他字段会参与去重吗 t1表的c1,c2字段有唯一索引, create unique index indexname on t1 (c1,c2); 那么在执行这个SQL的时候: select distinct t0.c1, t1.c1, t1.c2, t1.c3, t1.c4 from t0, t1 where t0.c5 = t1.c5; 是只有t0.c1, t1.c1, t1.c2三个字段参与去重,还是5个字

并发操作-a,b两个请求并发 注册相同用户名,假如表中字段未设置唯一索引,程序上如何控制唯一性啊

问题描述 a,b两个请求并发 注册相同用户名,假如表中字段未设置唯一索引,程序上如何控制唯一性啊 a,b同时查询表,结果是可以注册的,所以都执行了insert,但用户名相同,这样数据就不唯一了.是会这样吗,如何避免呢? 解决方案 把查询和插入放在同一事务中,可以保证整个事务中数据库数据的一致性,这样应该可以避免你说的问题. 上述并发一起的问题,根源在于查询与插入两个时间点数据库数据不一致导致. 解决方案二: 必须有有个不同的key,比如你可以增加一个字段,为userid,这个不会变,但用户名可以

用ADox获得表的唯一索引

ado|索引 adox的功能据说很强大,但是我没感觉出来,因为我在asp下编写了很多程序,都运行不了.不知道什么原因,这里有一个程序可以获得唯一索引.可能有的朋友会做一些通用程序,也就是无论数据结构如何变化,程序不变,从数据录入,修改,删除,到数据检索.所有模块都是完全独立于数据库表的结构的.这样就需要获得该表的唯一索引.因为要通过唯一索引来标示当前要删改的是那条记录.如果一个表是正规的表的话,应该至少有一个唯一索引,因为表应该有主键,而主键就是唯一索引.我尝试用adox.key获得表的主键,出

tushare宏观数据使用pandas入库,增加唯一索引

1,对pandas入数据库 pandas代码中自带了to_sql的方法可以直接使用. 但是数据字段是text的,需要修改成 varchar的,否则不能增加索引. 在增加了主键之后可以控制数据不能再增量修改了. 增加:dtype类型可以解决 dtype={col_name: NVARCHAR(length=255) for col_name in data.columns.tolist()} to_sql 中带的 if_exists 是针对不存在的数据表操作的.而不是数据. 查看了pandas i

SQL Server已分区索引的特殊指导原则(2)- 唯一索引分区

一.前言 在MSDN上看到一篇关于SQL Server 表分区的文档:已分区索引的特殊指导原则,如果你对表分区没有实战经验的话是比较难理解文档里面描述的意思.这里我就里面的一些概念进行讲解,方便大家的交流. 二.解读 [对唯一索引进行分区] "对唯一索引(聚集或非聚集)进行分区时,必须从唯一索引键使用的分区依据列中选择分区依据列.此限制将使 SQL Server 只调查单个分区,以确保表中不存在重复的新键值.如果分区依据列不可能包含在唯一键中,则必须使用 DML 触发器,而不是强制实现唯一性.&

Oracle中唯一约束和唯一索引的区别

在使用TOAD来操作Oracle数据库时,会注意到创建约束时有Primary Key.Check.Unique和Foreign Key四种类型的约束,这与SQL Server中的约束没有什么区别,这里的Check约束除了用于一般的Check约束外,在Oracle中也用于非空约束的实现.也就是说如果一个字段不允许为空,则系统将会创建一个系统的Check约束,该约束定了某字段不能为空. 除了约束,还有另外一个概念是索引,在TOAD中创建索引的界面如下: 我们可以注意到在唯一性组中有三个选项:不唯一.

MongoDB 唯一索引

MongoDB支持的索引种类很多,诸如单键索引,复合索引,多键索引,TTL索引,文本索引,空间地理索引等.同时索引的属性可以具有唯一性,即唯一索引.唯一索引用于确保索引字段不存储重复的值,即强制索引字段的唯一性.缺省情况下,MongoDB的_id字段在创建集合的时候会自动创建一个唯一索引.本文主要描述唯一索引的用法. 一.创建唯一索引语法 //语法 db.collection.createIndex( <key and index type specification>, { unique: