【问题标题】:SQL partition by with original orderSQL 按原始顺序分区
【发布时间】:2020-10-07 20:51:52
【问题描述】:

这是原始的 MySQL 表:

+----+-----+
| Id | Num |
+----+-----+
| 1  |  1  |
| 2  |  1  |
| 3  |  1  |
| 4  |  2  |
| 5  |  1  |
| 6  |  2  |
| 7  |  2  |
+----+-----+

当我使用select Id, Num, row_number() over(partition by Num) from t 时,MySQL 会自动打乱Num 列的顺序。但是,我想保持Num 列顺序不变。 具体来说,理想的输出应该是这样的:

+----+-----+-----+
| Id | Num | row |
+----+-----+-----+
| 1  |  1  |  1  |
| 2  |  1  |  2  |
| 3  |  1  |  3  |
| 4  |  2  |  1  |
| 5  |  1  |  1  |
| 6  |  2  |  1  |
| 7  |  2  |  2  |
+----+-----+-----+

如何编写这个 MySQL 查询?

【问题讨论】:

  • 您可以按 ID 订购吗?
  • @urlreader 我认为他的意思是每次原始订单转到新的Num 时,row_number() 都必须从 1 开始。
  • 您是否有另一列可以区分每组行?您可以在分区中使用它。
  • @Barmar 不幸的是,我没有其他可区分的列。
  • 您可能不得不回到使用会话变量的旧方法来记住上一行中的num,以及一个@row_number 会话变量,它会根据num 已更改。

标签: mysql sql window-functions gaps-and-islands


【解决方案1】:

这是一个孤岛问题。我建议使用行号之间的差异来识别组。

如果id 始终无间隙地递增:

select id, num, 
    row_number() over(partition by num, id - rn order by id) rn
from (
    select t.*, row_number() over(partition by num order by id) rn
    from mytable t
) t
order by id

否则,我们可以使用另一个row_number() 生成我们自己的递增id:

select id, num, 
    row_number() over(partition by num, rn1 - rn2 order by id) rn
from (
    select t.*, 
        row_number() over(order by id) rn1,
        row_number() over(partition by num order by id) rn2
    from mytable t
) t
order by id

Demo on DB Fiddle - 对于您的示例数据,两个查询都会产生:

编号 |编号 | rn -: | --: | -: 1 | 1 | 1 2 | 1 | 2 3 | 1 | 3 4 | 2 | 1 5 | 1 | 1 6 | 2 | 1 7 | 2 | 2

【讨论】:

  • 假设 ID 没有间隙可能是不现实的。
  • @Barmar:当然,我用替代方法进行了编辑。不过我不得不建议,因为在子查询中只使用一次row_number() 而不是两次更有效。
  • 这些天我需要学习所有复杂的窗口函数。我们仍在使用 5.x,所以我无法使用 8.x 功能进行任何练习。
  • @Barmar:MySQL 真的来晚了,而其他一些数据库实际上已经有几十年的功能了......窗口函数非常方便地解决了一整类问题。通常,在没有它们的情况下解决差距和孤岛问题确实具有挑战性。
  • @GMB 正如我在上面的评论中提到的,使用会话变量并不难。
【解决方案2】:

您可以通过writing your own row_number 执行此操作,以更好地控制其分区。

set @prev_num = null;
set @row_number = 0;

select
  id,
  -- Reset row_number to 1 whenever num changes, else increment it.
  @row_number := case
    when @prev_num = num then 
      @row_number + 1
    else
      1
    end as `row_number`,
  -- Emulate lag(). This must come after the row_number.
  @prev_num := num as num
from foo
order by id;

【讨论】:

    【解决方案3】:

    与 Schwern 提出的解决方案的想法相同。只是 MySQL 中的另一种语法风格,我发现它非常简单易用。

    Select
        id
      , num
      , value
    from
    (select
          T.id,
          T.num,
          if( @lastnum = T.num, @Value := @Value + 1,@Value := 1) as Value,
          @lastnum := T.num as num2
       from
          mytable T,
          ( select @lastnum := 0,
                   @Value := 1 ) SQLVars
       order by
          T.id) T;
    

    DB 小提琴链接 - https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=e04692841d091ccd54ee3435a409c67a

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-03-16
      • 1970-01-01
      • 2021-10-01
      • 2013-09-13
      相关资源
      最近更新 更多