没有偏移的基于游标的分页?

时间:2018-05-14 17:15:32

标签: mysql sql database-cursor

对于大型数据集,基于offset的分页变得很慢,因此更快的方法是使用基于光标的分页。基本上,一个锚点,数据库知道从那一点开始查找结果。考虑到这一点,这是我面临的问题:

我有一张表tv_watchers,其中包含自动增量idmins_watching_tvuser_id(下面共20行)。在此示例中,user_id将是相同的1,因此无需担心。我们希望按照从最高到最低的时间看电视的分钟数进行排序。

使用此查询可以轻松完成此操作:

SELECT * FROM tv_watchers
ORDER BY mins_watching_tv DESC, id ASC

这将通过id:

以这种方式返回我们想要的20个字段的正确顺序
2, 17, 1, 16, 15, 5, 6, 7, 8, 9, 10, 11, 12, 13, 20, 3, 4, 14, 19, 18

问题是我们希望将其拆分为5个块(我们称之为批次),因为我们希望按上述顺序返回5个结果。我们通过检索前6个结果,将前5个结果返回给用户,并使用第6个结果(如果它作为光标(锚点)存在)来获取下一个批次: 这将正确返回第一批:

-- (Batch 1) 2, 17, 1, 16, 15, 5
SELECT * FROM tv_watchers
ORDER BY mins_watching_tv DESC, id ASC
LIMIT 6

此处的第6项是标识5,其mins_watching_tv60,因此,由于这是光标,我们使用它来获取下一个6:

-- (Batch 2) 5, 6, 7, 8, 9, 10
SELECT * FROM tv_watchers
WHERE mins_watching_tv <= 60 OR id=5
ORDER BY mins_watching_tv DESC, id ASC
LIMIT 6

此处的第6项是标识10,其中mins_watching_tv的{​​{1}}也是60,所以由于这是游标,我们用它来获得下一个6:

-- (Batch 3 should be) 10, 11, 12, 13, 20, 3
-- (Batch 3 returns incorrectly) 5, 6, 7, 8, 9, 10
SELECT * FROM tv_watchers
WHERE mins_watching_tv <= 60 OR id=10
ORDER BY mins_watching_tv DESC, id ASC
LIMIT 6

但问题是返回的结果不正确,它返回上面评论中看到的错误的批次3 ID。我确信它与WHERE部分有关,它似乎拿起mins_watching_tv <= 60部分,但id=10部分是为了让数据库知道从该锚点获取结果60分钟和10分,但这不能正常工作。

最终批次结果应如下所示:

-- (Batch 4) 3, 4, 14, 19, 18

我设置sql fiddle here来显示问题。我们如何修复查询,以便它将mins_watching_tv的游标组合与id结合使用,以批量返回正确的结果?

2 个答案:

答案 0 :(得分:3)

  1. 选择前6个,就像你已经做过的那样,WHERE没有任何内容。

    SELECT *
           FROM tv_watchers
           ORDER BY mins_watching_tv DESC,
                    id ASC
           LIMIT 6;
    
  2. 上一步结果的最后一行的持续时间@duration和ID @id,并将它们放入WHERE

    SELECT *
           FROM tv_watchers
           WHERE mins_watching_tv < @duration
                  OR mins_watching_tv = @duration
                     AND id >= @id
           ORDER BY mins_watching_tv DESC,
                    id ASC
           LIMIT 6;
    
  3. 重复2.直到达到结束。

  4. 说明:

    • 如果mins_watching_tv < @duration我们可以确定,相应的行不在我们之前的结果中mins_watching_tv小于我们之前结果中的最小@duration而我们做了ORDER BY mins_watching_tv DESC
    • 如果mins_watching_tv = @duration我们还不知道我们是否已经拥有该排。但是,当我们另外执行ORDER BY id ASC时,我们知道我们已经拥有相同mins_watching_tv的所有行的ID都小于或等于当前最大值@id(每mins_watching_tv )。因此,我们只希望那些行id > @id或者我们也希望重复前一个结果的最后一行id = @id。简而言之,id >= @id

    由于我们想要这两个集合的联合,我们必须将上述谓词分离,因此使用OR。我们得到(括号仅为清晰起见,不需要它们):

    (mins_watching_tv < @duration)
     OR (mins_watching_tv = @duration
         AND id >= @id)
    

    here是小提琴。

答案 1 :(得分:2)

我只是略过了,但我认为你只需要调整你的条件(例如)

mins_watching_tv < 60 OR (mins_watching_tv = 60 AND id>=5)