mySQL - 过滤行的分页

时间:2016-11-11 17:06:57

标签: mysql sql

我有一个REST服务,它根据当前页面和每页结果返回数据库表中的行。

当不过滤结果时,它很容易做到,我只做一个SELECT WHERE id> =(page - 1)* perPage + 1和LIMIT to perPage。

问题在于尝试对过滤结果使用分页,例如如果我选择仅过滤行WHERE type = someType。

在这种情况下,第一页的第一个匹配可以在id 7中开始,最后一个可以在id 5046中。然后第二页的第一个匹配可以从7302开始并在12430结束,依此类推。

对于过滤结果的第一页,我只能从id 1和LIMIT到perPage开始,但对于第二页等,我需要知道最后一个匹配行的索引。上一页,甚至更好 - 当前页面中第一个匹配的行,或其他一些指示。

我该如何有效地做到这一点?我需要能够在具有数百万行的表上执行此操作,因此显然获取所有行并从中获取它不是一种选择。

这个想法是这样的:

SELECT ... FROM ... WHERE filterKey = filterValue AND id >= id_of_first_match_in_current_page

id_of_first_match_in_current_page是个谜。

2 个答案:

答案 0 :(得分:3)

您无法知道给定网页上的第一个ID是什么,因为ID号不一定是连续的。换句话说,序列中可能存在间隙,因此100行的第五页上的行不必从id 500开始。例如,它可以从id 527开始,它是不可能知道的。

另一种说法是: id是一个值,而不是一个行号。

如果您的客户端按升序推进页面,则一种可能的解决方案是每个REST请求获取数据,记录该页面上的最大 id值,然后在 next < / em> REST请求,因此它会查询更大的id值。

SELECT ... FROM ... WHERE filterKey = filterValue 
AND id > id_of_last_match_of_previous_page

但是,如果您的REST请求可以获取任何随机页面,则此解决方案无法正常工作。这取决于已经取得前一页。

另一种解决方案是使用LIMIT <x> OFFSET <y>语法。这允许您请求任意页面。 LIMIT <y>, <x>的工作原理相同,但由于某些原因,x和y在两种不同的语法形式中相反,所以请记住这一点。

当您请求结果中有多页的页面时,使用LIMIT...OFFSET非常有效。假设您要求第5,000页。 MySQL必须在服务器端生成5,000页的结果,然后丢弃4,999个结果并返回结果中的最后一页。抱歉,但这是如何运作的。

重新评论:

您必须了解WHERE在行中上应用条件,但页面由行的位置定义。这是确定行的两种不同方法!

如果您的列保证为行号,则可以将该值用作行位置。您甚至可以在其上添加索引,或将其用作主键。

但是主键值可能会更改,并且可能不会是连续的,例如,如果更新或删除行,或者回滚某些事务,等等。重新编号主键值是个坏主意,因为其他表或外部数据可能会引用主键值。

因此,您可以添加另一列主键,但只能添加行号。

ALTER TABLE MyTable ADD COLUMN row_number BIGINT UNSIGNED, ADD KEY (row_number);

然后在需要重新编号行时填充值。

SET @row := 0;
UPDATE MyTable SET row_number = (@row := @row + 1) ORDER BY id;

例如,如果删除某些行,则必须重新对行进行编号。根据桌子的大小,经常这样做效率不高。

此外,新插入无法在不锁定表的情况下创建正确的行号值。这对于防止竞争条件是必要的。

如果您保证row_number是一系列连续值,那么它既是值又是行位置,因此您可以将其用于任何页面的高性能索引查找行。

SELECT * FROM MyTable WHERE row_number BETWEEN 401 AND 500;

至少在下次通过删除或新插入对行号序列产生疑问之前。

答案 1 :(得分:1)

您出于错误的目的使用ID列。 ID是记录的标识符,而不是任何给定结果集的记录的序列号

LIMIT关键字扩展到基本分页。如果您只想要前10条记录,您可以执行以下操作:

LIMIT 10

要分页,如果你想要第二个 10条记录,你可以这样做:

LIMIT 10,10

之后的10:

LIMIT 20,10

等等。

LIMIT子句独立于WHERE子句。使用WHERE过滤结果,使用LIMIT对其进行分页。