【问题标题】:MySql: Find row number of specific recordMySql:查找特定记录的行号
【发布时间】:2013-06-04 03:12:30
【问题描述】:

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

我已经解决了大部分问题,但我无法找到要移动到的页码。我最初只是简单地遍历数据面板中的行,但由于分页,这被证明是非常低效的,页码越大。相反,我决定直接通过 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)

每次我运行查询时,记录的顺序都会发生巨大变化。这解释了为什么行 num 会发生变化,因为它实际上正在发生变化。但是,我无法弄清楚为什么仅通过包装查询就会改变顺序。我已经在其他数据库引擎中做到了这一点,所以我认为它与 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 子句。所以我想找到最低的行号并移动到该页面。

【问题讨论】:

  • 考虑简化您的问题。没有人愿意阅读所有内容!
  • 请同时包含您的数据库架构,最好包含一些示例数据。

标签: mysql pagination


【解决方案1】:

需要考虑的内容很多,所以让我试着分解一下这个问题。首先,您似乎需要解决不可预测的行排序问题。

我在这里给你的建议是,你总是以某种方式对表的唯一主键进行排序。如果用户以原始“未排序”(即按用户选择)顺序查看页面,则仍使用如下排序:

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 < ?

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

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

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】:

    我的目的解决了:) 所以,如果有人觉得合适,我会在这里发帖:

    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 等等.....

    【讨论】:

      【解决方案3】:

      感谢 Reza Mamun, 我设计了一种放置记录编号的方法,即使您使用 limit 获取数据,您也会确切知道该记录对应的编号。

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

      这将为您提供记录编号。 现在要实现限制,传入相同的限制,例如LIMIT 10,10,然后在FROM 子句中将rownum 设置为限制的开头,即10

      修改后的限制查询将是:

      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 子句,这将不起作用。

      【讨论】:

        猜你喜欢
        • 2018-10-31
        • 1970-01-01
        • 1970-01-01
        • 2013-11-08
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-06-24
        • 1970-01-01
        相关资源
        最近更新 更多