MySql:查找特定记录的行号

时间:2013-06-07 18:12:47

标签: mysql pagination

我正在使用通用数据面板,该面板可以为该面板提供各种不同的查询。它们可能是从表或视图中选择的简单查询,也可能是用户使用复杂的连接和其他表达式定义的复杂查询。我正在尝试修改我的数据面板,以便如果用户选择一个记录,然后对表进行排序,我找到记录现在打开的页面,移动到它,然后重新选择记录。

我已经解决了大部分问题,但我无法找到要移动的页码。我最初只是简单地循环数据面板中的行,但由于分页,这个页面编号越大,效率就越低。我决定直接通过SQL来做这个,这就是我现在被困住的地方。

我决定如果通过运行产生结果的相同查询找到所选行的行数,我可以计算出我需要移动到的最终页码并跳转到该页面。我接受了运行的查询来生成结果,并递增一个变量来获取行号。

原始查询是

select *
from table_a
order by column_c desc

带行号的修改后的查询变为

select *, (@rownum := @rownum + 1) as rownum
from
(select @rownum := 0) rn
, (
  select *
  from table_a
  order by column_c desc
) data

此时我正在选择所有记录。然后,我将上面的查询包装起来并选择记录与所选记录匹配的最小行,如此

select min(rownum)
from
(  
  select *, (@rownum := @rownum + 1) as rownum
  from
  (select @rownum := 0) rn
  , (
    select *
    from table_a
    order by column_c desc
  ) data
) wrapper
where
  primarykeyfield1 = ?
  and primarykeyfield2 = ?

起初这似乎有效。然而,在测试期间,我发现如果我在一个不够独特的字段上排序(例如,1000个记录在该字段中都具有相同的值),它就会停止工作。我做了一些挖掘,发现上面的代码每次运行查询时都会返回不同的rownum。

经过一些额外的挖掘,我发现如果要运行以下查询,我会得到我想要的结果

select * from table_a order by column_c

但是,如果我只是像这样包装那个查询

select * from (select * from table_a order by column_c)

每次运行查询时,记录的顺序都会发生巨大变化。这解释了为什么行数正在改变,因为它实际上正在改变。但是,我无法通过包装查询来弄清楚为什么订单会发生变化。我已经在其他数据库引擎中完成了这个,所以我认为它与MySql有关,但是我无法找到解释原因的信息。我的假设是,在这样的查询中包装时,order by不会被应用,或者行为不符合预期。

接下来,我尝试将rownum计数直接移动到主/基本查询中,如此

select *, (@rownum := @rownum + 1) as rownum
from (select @rownum := 0) rn, table_a
order by column_c desc

单独运行此查询会创建正确的行号。但是,由于我需要找到所选记录的特定行号,我必须像这样包装该查询

select min(rownum)
from (
  select *, (@rownum := @rownum + 1) as rownum
  from (select @rownum := 0) rn, table_a
  order by column_c desc
) data
where
  primarykeyfield1 = ?
  and primarykeyfield2 = ?

一旦我这样做,order by似乎被忽略,它按照记录在表中出现的顺序计算,而不是它们在基本查询中的顺序。

我有兴趣了解数据集排序在包装时未正确应用的根本问题,以及用于查找特定记录所在页码的潜在其他解决方案。

注意,我在最后的外部查询上使用min,因为最终用户可以选择多行,并且所有这些行都进入最后的where子句。所以我想找到最低的行数并移动到那个页面。

3 个答案:

答案 0 :(得分:4)

我的目的已经解决了:) 所以,如果有人认为合适,我会在这里发帖:

SELECT d.myRowSerial
FROM (
    SELECT *, @rownum:=@rownum + 1 AS myRowSerial 
    FROM myTable, (SELECT @rownum:=0) AS nothingButSetInitialValue 
    WHERE 1=1 -- Optional: filter if required, otherwise, omit this line;
    ORDER BY AnyColumn -- Apply the order you like; 
) d
WHERE d.myColumn = 'Anything'; -- If you like to limit it to only
-- for any specific row(s), similar to the *MAIN query.

如果您还需要页码,可用于确定分页的偏移值,那么只需更改上面的第一行,如下所示:

SELECT d.myRowSerial, FLOOR((d.myRowSerial-1)/10) AS pageNumber
-- Say, 10 is per page;

第1页的pageNumber == 0,第2页的pageNumber == 1等等.....

答案 1 :(得分:0)

这是很多东西,所以让我试着打破这个问题。首先,您似乎需要解决不可预测的行排序问题。

我在这里给你的建议是,你总是以某种方式对表的唯一主键进行排序。如果用户正在查看页面中的原始“未排序”(通过用户选择)订单,仍然使用类似的类型:

SELECT *
FROM table
ORDER BY primary_key ASC
LIMIT 0, [page limit value]

当用户选择按其他字段排序时,请确保主键字段仍然使用如下排序:

SELECT *
FROM table
ORDER BY sort_field [ASC|DESC], primary_key ASC
LIMIT 0, [page limit value] 

即使用户排序的字段的基数较低(即没有很多不同的值),这也可以保证您在多次调用相同排序时获得相同的顺序,因为您总是拥有唯一的主键字段来指定顺序当排序字段中的值相等时。

现在问题是能够直接转到选定的行和页面,并改变排序。我假设您知道所选行的主键,并且能够在查询中使用它。

您可以先在新排序中确定此行的偏移量,如下所示:

SELECT count(*)
FROM table
WHERE
  sort_field <= (SELECT sort_field FROM table WHERE primary_key = ?)
  AND primary_key < ?

它告诉您所选行(偏移量)之前的数据集中的行数(包括所选行)。请注意,这是一种升序排序。显然,对于降序排序,您需要使用>=>

然后在之前显示的查询中使用此偏移量,如下所示:

SELECT *
FROM table
ORDER BY sort_field [ASC|DECS], primary_key ASC
LIMIT [offset], [page limit value]

如果您想要行所在的实际页码(例如,如果您想要固定页面的概念(您选择的行可能是或可能不是显示的第一行),您也可以通过将偏移量除以页面限制值并对结果进行舍入。然后,您将通过“页面限制”多次“偏移页面”来确定要使用的偏移值。

所以说你的行总偏移量为125,页面限制为50.这是伪代码

selected_row_offset = 125;
page_limit = 50;
offset_page = floor(selected_row_offset/page_limit);
query_offset = offset_page * page_limit;

您的查询将是

SELECT *
FROM table
ORDER BY sort_field [ASC|DECS], primary_key ASC
LIMIT [query_offset], [page limit value]

显然,出于性能原因,您需要确保将所有允许排序的字段编入索引。

答案 2 :(得分:0)

谢谢Reza Mamun, 我已经设计了一种方法来记录数字,即使你使用限制获得数据,你也会确切地知道记录对应的数字。

SELECT @rownum:=@rownum + 1 AS myRowSerial,
*
FROM myTable, (SELECT @rownum:=0) AS nothingButSetInitialValue
LIMIT 0,10

这会给你记录号码。 现在要实现限制,传递相同的限制,例如LIMIT 10,10,然后将rownum设置为限制的开头,即10子句中的FROM

修改后的限制查询将是:

SELECT @rownum:=@rownum + 1 AS myRowSerial,
*
FROM myTable, (SELECT @rownum:=10) AS nothingButSetInitialValue
LIMIT 10,10

然后对于下一组记录,它将是:

SELECT @rownum:=@rownum + 1 AS myRowSerial,
*
FROM myTable, (SELECT @rownum:=20) AS nothingButSetInitialValue
LIMIT 20,10

再次感谢,这真的节省了我的一天! :)

**更新:**如果你的查询中有GROUP BY子句,这将不起作用。