【问题标题】:Seek a specific record in MySQL paged results在 MySQL 分页结果中查找特定记录
【发布时间】:2013-11-08 11:37:36
【问题描述】:

我有一个使用 LIMIT startrecord, endrecord 的经典分页系统,我想弄清楚 X 记录位于哪个页码中。

我现在唯一的想法是递归地寻找所有记录以找到它。但我正在寻找一种更“经济”的方法!

有什么想法吗?

【问题讨论】:

  • X 记录是什么意思?你的意思是第 18 条记录在第 2 页上?
  • 我的意思是 ID 为 18 的记录(或其他类似标题 "Hello" 的记录)在第 2 页上。记录可以以不同的方式排序。所以我不能只数它
  • 记录是记录数/每页
  • 您可以使用添加的过滤器再次重新运行查询,以缩小结果范围,但除此之外,您还必须搜索整个结果(不仅仅是有限的结果)
  • @Waygood Javascript 分页不是一个好的选择。如果我有 10k 条记录,我必须加载所有 10k 条记录并研究大量数据!并使用 php/js 分页,我必须在所有页面中寻找。这是我想要避免的

标签: php mysql pagination seek


【解决方案1】:

您可以使用子查询创建一个包含结果及其位置的表,然后查询您正在查看的特定条目:

SET @rank=0; 
SELECT rank, record 
FROM (
    SELECT 
        @rank:=@rank+1 AS rank, 
        record 
    FROM table
) as subquery 
WHERE record = x;

返回的表将显示记录及其在原始查询中出现的排名。您可以将排名除以每页的结果数......或者将其构建到查询中。希望这会有所帮助。

【讨论】:

  • 这个方法好像是我需要的!但是,如果我在子查询中使用连接,则结果会丢失排序。例如:SET @rank=0; SELECT * FROM ( SELECT pt.id, pt.attr_code, pal.attribute_label, pt.is_required, pt.is_locked, pt.is_visible, pt.is_comparable, @rank:=@rank+1 AS rank FROM fm_product_attributes AS pt INNER JOIN fm_product_attributes_data AS pal ON (pt.id = pal.attributeid and pal.languageid = 1) GROUP BY id ORDER by pt.attr_code ASC) sq;使用此查询,订单语句被忽略,记录获得默认排序
  • 不,结果按我的意愿排序,但排名变量不跟随它s11.postimg.org/bdk2cksr7/rankjoin.png
  • 内部查询是否需要按id分组?
【解决方案2】:

计算出您要查找的记录之前的记录数。这要求您为您的查询假设一个自然的顺序。

SELECT COUNT(id) AS c
FROM tbl
WHERE sort_field < ((SELECT sort_field FROM tbl WHERE id = 18))
OR (sort_field = ((SELECT sort_field FROM tbl WHERE id = 18)) AND id < 18);

然后只需检索c 并计算ceilling(c/page_size)。这将为您提供记录所在的页码。唯一要记住的重要事项是,您需要按照与限制查询相同的顺序对记录进行排序。

为了描述查询的作用,它计算 id 为 18 的记录之前的记录数。唯一棘手的部分是记录与 sort_field 的值相同的记录,其中 MySQL 将使用主键在我们的例子中是id。这就是为什么我们的条件中有OR 部分。在我的回答中,我假设您将原始查询(其中包含限制语句)按升序排序,但如果您按降序排序,则需要将所有 &lt; 更改为 &gt;

【讨论】:

  • 不,任何排序的 COUNT 都会返回相同的结果
  • 这是正确的答案。首先计算行数直到找到你的记录,然后除以你想要的分页数。
【解决方案3】:

在您的查询中使用类似的内容作为s 子选择的一部分

SELECT s.row, s.RECORD, YOUR_OTHER_FIELDS...
FROM (SELECT @row := 0) cnt
  JOIN (SELECT @row := @row + 1 row, RECORD, ...YOUR QUERY WITH ORDER BY ...) s
WHERE s.RECORD = <desired record number>

并将行除以分页中的页面大小。 具体但荒谬的例子:

SELECT p.row, p.id
FROM (SELECT @row := 0) cnt
  JOIN (SELECT @row := @row + 1 row, id FROM products ORDER BY id desc) p
WHERE p.id = 485166

正如预期的那样,row 的值会随着您在子选择中使用的顺序而变化。

它将变量初始化折叠到查询中,因此这只是一个语句。 它也不取决于行的自然顺序或分布 - 只要它们返回的顺序对于您指定(或省略)的任何 ORDER 保持不变。

【讨论】:

  • 我试过这个,虽然它有效,但由于某种原因,它似乎以相反的顺序返回行数。这是正确的行为吗?如果是这样,是否有解决方法?
  • 您在子选择之外有group byorder by 吗?
  • 是的,我做到了。我通过改变一些事情来解决它。
【解决方案4】:

如果这是您经常使用的东西,我认为创建一个stored procedure or a function 是个好主意。我们可以在里面使用cursor,遍历结果并获得所需项目的位置。我认为这会更快,它不必遍历所有记录,并且不需要子查询(对于所有这些,我会说它更经济),您可以使用 order、join 和任何您需要的东西。

DELIMITER $$


CREATE FUNCTION position ( looking_for INT ) 
 RETURNS INT
READS SQL DATA
BEGIN
  -- First we declare all the variables we will need
  DECLARE id   INT;
  DECLARE pos   INT;
  SET pos=0;
  -- flag which will be set to true, when cursor reaches end of table
  DECLARE exit_loop BOOLEAN;         

  -- Declare the sql for the cursor
  DECLARE pos_cursor CURSOR FOR
    SELECT id  
    FROM your_table
    --you can use where, join, group by, order and whatever you need 
 --end of query

  -- Let mysql set exit_loop to true, if there are no more rows to iterate
  DECLARE CONTINUE HANDLER FOR NOT FOUND SET exit_loop = TRUE;

  -- open the cursor
  OPEN example_cursor;

  -- marks the beginning of the loop
  example_loop: LOOP

    -- read the id from next row into the variable id
    FETCH  pos_cursor INTO  id;
    -- increment the pos var
    SET pos=pos+1;

    -- check if we found the desired item, 
    -- if it has been set we close the cursor and exit 
    -- the loop
    IF id=looking_for THEN
        CLOSE example_cursor;
        LEAVE example_loop;
    END IF;


    -- check if the exit_loop flag has been set by mysql, 
    -- if it has been set we close the cursor and exit 
    -- the loop
    IF exit_loop THEN
        CLOSE example_cursor;
        LEAVE example_loop;
    END IF;

  END LOOP example_loop;
 RETURN pos;
END $$

DELIMITER ;

你只需创建一次函数,使用它,你只需要使用这个sql:

CALL position(ID_OF_THE_ITEM_YOU_ARE_LOOKING_FOR);

它返回项目的位置,在返回的行集的位置 [0][0] 中。

当然,您可以创建一个函数来比较名称或任何其他字段,甚至多个字段,而不是 id。

如果查询总是不同的,则不能使用函数,但仍可以使用游标(语法相同)。您可以在您的 PHP 中构建游标,让pos 成为系统变量(使用@pos),并且无论如何只需添加查询的特定sql(DECLARE pos_cursor CURSOR FOR--end of query 之间的部分)

【讨论】:

    【解决方案5】:

    你不能真正创造一种“经济”的方式。您必须从数据库中获取完整的记录列表,因为无法从 MySQL 中知道记录的位置。

    根据您的排序、数据更改的频率,您可以为记录分配其在列中的位置:将列位置添加到您正在查询的表中。这可能并非在所有情况下都可行。

    【讨论】:

    • 有 rank 方法,但是当记录被“排名”时它会跳过排序。
    猜你喜欢
    • 2013-09-12
    • 1970-01-01
    • 2013-06-04
    • 2020-06-24
    • 1970-01-01
    • 2016-09-14
    • 1970-01-01
    • 1970-01-01
    • 2023-03-23
    相关资源
    最近更新 更多