分页从-1开始读取数据只会重复一条记录

本文解析了一个简历信息列表分页时出现的重复数据Bug。通过详细分析业务逻辑层和数据访问层代码,揭示了分页起始值不当设置导致的问题根源,并通过测试验证了解决方案的有效性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

  今天做项目的时候发现一个小Bug,引得自己一番思考,在项目中有一个简历信息列表的分页问题,第一页的最后一条信息和第二页的第一条信息是重复的,但是从第二页以后就没有重复的信息了(这个是后来才发现的),就此问题我进行了一番研究,研究结果发现是因为作者在数据访问层对业务逻辑层传递过来的分页起始值再减了一个1,导致第一页的时候sql语句分页的起始值是-1,代码如下:

 

业务逻辑层相关代码: ResumeDal.ResumesList((page - 1) * showCount, showCount * page);  参数说明:(参数1:分页开始值,参数2:分页结束值)

数据访问层相关代码: int start-=1;(分页开始值)

Sql语句:

select * from [Resume] where [ID] in
(
  select top 30 [ID] from
  (
    select r.ID,row_number()over(order by r.[UpdateTime] desc) as r
    from [Resume] r
    inner join [User] u on r.UserID = u.ID
    left join [Area] c on r.AreaId=c.ID
    left join [Subject] s on r.SubId=s.ID
    where u.[IsCompany]=0
  ) as t
  where t.r>-1
)

 

  看到这里,我马上想到的就是每次分页都会对起始位置的值进行减1操作,那么不是每页都会有一条重复的数据吗?那么不就有很多的真实数据会看不见吗?(因为页数是根据信息总数除以每页显示的条数得到的,信息总数是真实数据的条数)经过反复测试,我发现只有第一页的最后一条数据和第二页的第一条数据是重复的,从第二页以后的数据就没有重复的了,测试结果与我的第一想法完全不一致,这令我陷入了迷茫当中,为了消除这个疑惑,我静下心来反复测试,终于得到了正解。

 

当前页    分页开始值    分页结束值    实际获取数据的范围       每页显示的条数

 1           -1           30            1-30            30

 2           29           59            30-59          30
    
 3           59           89            60-89                 30       

 4           89           119           90-119           30

 5           119          149           120-149         30

 

  原理是样的,获取第一页数据的时候,分页的起始值为-1,作者使用的是row_number进行分页,每页显示的条数固定不变为30,所以实际的取值范围为1-30,这样才满足top 30,当获取第二页的数据时,分页的起始值就变成了29,由于t.r要大于分页开始值,所以实际的取值范围为30-59,观察红色部分可以知道,因为第一页的分页起始值为-1top 30的原因,导致数据库在获取第一页的数据时取到了原本应该是属于第二页的数据,所以出现了"第一页的最后一条数据和第二页的第一条数据是重复的"这样一种现象,当获取第三页数据时,分页的起始值为59,同理实际的取值范围为60-89,第三页的数据就没有重复了,同样的道理,后面的数据也不会有重复的,所以才会出现"只有第一页的最后一条数据和第二页的第一条数据是重复的,从第二页以后的数据就没有重复的了"这样一种情况,好了,到此已经水落石出了,虽然只是一个小小的问题,却也能让人一番思考,希望自己以后写代码能够想得更多,收获更多。

转载于:https://www.cnblogs.com/52XF/p/3740307.html

<think>嗯,用户问的是在SQLite中如何在一个查询里同时获取总条数和分页数据。这个问题看起来是要优化查询,避免两次数据库操作,对吧?首先,我得回想一下SQLite有没有这样的功能。 总条数通常用COUNT(*)来获取,而分页数据则是用LIMIT和OFFSET。但怎么能把这两个放在一个查询里呢?可能需要子查询或者组合查询。比如,用子查询计算总数,然后和分页数据联合起来?不过这样的话,结果可能会有重复字段或者结构上的问题。 或者,有没有办法让一个查询返回多个结果集?但SQLite好像不支持存储过程或者多个结果集返回,所以可能需要别的方法。比如,使用交叉连接(CROSS JOIN)将总数和分页数据结合起来。这样,每一行分页数据都会附带总条数,虽然会有重复,但可能有效。 例如,SELECT * FROM (SELECT COUNT(*) AS total FROM table) CROSS JOIN (SELECT * FROM table LIMIT 10 OFFSET 0)。不过这样可能会有问题,因为交叉连接会生成笛卡尔积,导致每个分页行都带有总数,但用户可能只需要总数一次。不过如果分页数据需要每行都有总数的话,这样可能行得通。或者用UNION ALL来合并两个查询结果?但UNION要求列数相同,可能需要调整结构。 另外,考虑使用窗口函数。比如,在分页查询的同时,用COUNT(*) OVER()来获取总数。但SQLite是否支持窗口函数?我记得从3.25版本开始支持窗口函数。所以如果用户使用的是较新的版本,可以用SELECT *, COUNT(*) OVER() AS total FROM table LIMIT 10 OFFSET 0。不过这样每一行都会带有总数,但实际执行时,LIMIT可能会影响窗口函数的计算结果,所以需要测试一下。可能COUNT(*) OVER()会在应用LIMIT之前计算总数,这样就能正确得到总条数。 那这样的话,用户只需要一个查询就能同时得到分页数据和总数,而且不需要子查询或者联合查询。但需要确认SQLite的版本是否支持窗口函数。如果支持的话,这种方法应该是最优的。否则,可能需要使用子查询交叉连接的方式,或者分开两次查询。 不过用户特别提到要一次查询获取,所以窗口函数可能更合适。另外,分开两次查询虽然效率可能稍低,但有时候更直接。但用户可能希望减少数据库访问次数,特别是在高并发场景下。 总结一下,可能的解决方案有两种:使用窗口函数,或者使用子查询交叉连接。需要根据SQLite的版本选择合适的方法。同时,还要注意分页查询的性能,比如是否有合适的索引,避免全表扫描导致速度变慢。 另外,用户可能需要知道如何处理返回的结果,比如在应用程序中读取总数和分页数据。例如,使用窗口函数的话,每一行都有总数,但其实总数是相同的,只需要取第一行的total值即可。而交叉连接的方式可能返回的结果结构需要处理,比如总数字段和分页数据字段的组合。 最后,还要提醒用户考虑分页的性能问题,当数据量很大时,OFFSET的效率可能不高,可以考虑其他分页方法,比如使用WHERE子句和索引键来分页,而不是OFFSET。</think>在 SQLite 中,可以通过 **组合子查询和窗口函数** 实现在一次查询中同时获取总条数和分页数据。以下是具体实现方法及原理说明: --- ### 方法一:子查询 + 窗口函数(推荐) ```sql SELECT *, COUNT(*) OVER() AS total_count -- 窗口函数计算总条数 FROM your_table ORDER BY id -- 必须指定排序 LIMIT :page_size OFFSET :offset; ``` #### 核心原理 1. **`COUNT(*) OVER()`** 是窗口函数,会计算所有行的总数(不因 `LIMIT` 截断而改变) 2. `LIMIT` 和 `OFFSET` 控制分页 3. 结果集中每条记录都会包含 `total_count` 字段(值相同) #### 优点 - ⚡️ **单次查询完成** - 🚫 避免 `COUNT(*)` 子查询多次扫描表 --- ### 方法二:子查询组合(通用方案) ```sql SELECT (SELECT COUNT(*) FROM your_table) AS total_count, t.* FROM your_table t ORDER BY id LIMIT :page_size OFFSET :offset; ``` #### 核心原理 1. 主查询获取分页数据 2. 子查询 `(SELECT COUNT(*) ...)` 作为字段计算总数 3. 结果集中每条记录都会重复 `total_count` 字段(取第一条即可) --- ### 性能对比 | 方法 | 扫描次数 | 适用场景 | |--------------------|------------|-----------------| | 窗口函数 | 1次 | SQLite ≥ 3.25 | | 子查询组合 | 2次 | 所有 SQLite 版本 | --- ### 注意事项 1. **必须明确排序**:分页必须配合 `ORDER BY`,否则分页结果不可控 2. **大表优化**:若表数据量极大,建议对 `WHERE` 条件字段添加索引 3. **分页陷阱**:`OFFSET` 在超大偏移量时性能差,可改用 `WHERE id > :last_id` 的分页方式 --- ### 代码示例(Python) ```python import sqlite3 def get_paginated_data(page=1, page_size=10): conn = sqlite3.connect('your_db.db') cursor = conn.cursor() offset = (page - 1) * page_size query = """ SELECT *, COUNT(*) OVER() AS total_count FROM your_table ORDER BY id LIMIT ? OFFSET ? """ cursor.execute(query, (page_size, offset)) rows = cursor.fetchall() # 提取总数(从第一条记录的 total_count 字段) total_count = rows[0][-1] if rows else 0 return { "data": [dict(row) for row in rows], "total": total_count, "page": page } ``` --- 通过这种方法,既能保证数据一致性,又避免了多次查询的性能损耗,非常适合中小型数据量的分页场景。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值