【问题标题】:GROUP BY and custom orderGROUP BY 和自定义顺序
【发布时间】:2017-08-02 02:25:54
【问题描述】:

我已经阅读了MySQL order by before group by 上的答案,但将其应用于我的查询最终会在子查询中出现一个子查询,这是一个相当简单的情况,所以我想知道这是否可以简化:

带有示例数据的架构

为简洁起见,我省略了members 表中的其他字段。此外,在实际应用程序中加入了更多表,但这些表很容易加入。 membership_stack 表给我带来了问题。

CREATE TABLE members (
  id int unsigned auto_increment,
  first_name varchar(255) not null,
  PRIMARY KEY(id)
);

INSERT INTO members (id, first_name)
     VALUES (1, 'Tyler'),
            (2, 'Marissa'),
            (3, 'Alex'),
            (4, 'Parker');

CREATE TABLE membership_stack (
  id int unsigned auto_increment,
  member_id int unsigned not null,
  sequence int unsigned not null,
  team varchar(255) not null,
  `status` varchar(255) not null,
  PRIMARY KEY(id),
  FOREIGN KEY(member_id) REFERENCES members(id)
);

-- Algorithm to determine correct team:
-- 1. Only consider rows with the highest sequence number
-- 2. Order statuses and pick the first one found:
--    (active, completed, cancelled, abandoned)

INSERT INTO membership_stack (member_id, sequence, team, status)
     VALUES (1, 1, 'instinct', 'active'),
            (1, 1, 'valor', 'abandoned'),
            (2, 1, 'valor', 'active'),
            (2, 2, 'mystic', 'abandoned'),
            (2, 2, 'valor', 'completed'),
            (3, 1, 'instinct', 'completed'),
            (3, 2, 'valor', 'active');

我无法更改数据库架构,因为数据与外部数据源同步。

查询

这是我目前所拥有的:

    SELECT m.id, m.first_name, ms.sequence, ms.team, ms.status
      FROM membership_stack AS ms
      JOIN (
    SELECT member_id, MAX(sequence) AS sequence
      FROM membership_stack
  GROUP BY member_id
           ) AS t1
        ON ms.member_id = t1.member_id
       AND ms.sequence = t1.sequence
RIGHT JOIN members AS m
        ON ms.member_id = m.id
  ORDER BY m.id, FIELD(ms.status, 'active', 'completed', 'cancelled', 'abandoned');

这按预期工作,但如果成员的“最近序列”涉及多个团队,则成员可能会出现多次。我需要做的是在id 上再次聚合并选择每个组中的第一行。

但是这会带来一些问题:

  1. no FIRST() function in MySQL
  2. 整个结果集将变成一个子表(子查询),在这里这没什么大不了的,但查询在应用程序上却相当大。
  3. 它需要与ONLY_FULL_GROUP_BY mode 兼容,因为它在 MySQL 5.7 上默认启用。我还没有检查,但我怀疑FIELD(ms.status, 'active', 'completed', 'cancelled', 'abandoned') 是否被认为是此结果集的功能依赖字段。该查询还需要与 MySQL 5.1 兼容,因为这是我们目前正在运行的。

目标

| id | first_name | sequence |     team |    status |
|----|------------|----------|----------|-----------|
|  1 |      Tyler |        1 | instinct |    active |
|  2 |    Marissa |        2 |    valor | completed |
|  3 |       Alex |        2 |    valor |    active |
|  4 |     Parker |     NULL |     NULL |      NULL |

对此我能做些什么?

编辑:我注意到有些成员不属于任何团队。这些成员应包含在结果集中,这些字段的值为空。已更新问题以反映新信息。

【问题讨论】:

  • 如果序列状态相同怎么办?示例 Tyler@valor 的状态为“活动”?
  • @PaulSpiegel 我也曾对此提出质疑,但数据提供者尚未回复这是否可能。同一序列中的active 永远不应超过一个,但我想如果它们以相同的序列连续执行它们,则可能有多个completed
  • 但是你应该定义一个“不同的”顺序。在我的解决方案中,我使用membership_stack.id ASC 作为 ORDER BY 子句中的最后一列(以防万一)。

标签: mysql group-by sql-order-by mysql-5.7 mysql-5.1


【解决方案1】:

您可以在 LIMIT 1 的 WHERE 子句中使用相关子查询:

SELECT m.id, m.first_name, ms.sequence, ms.team, ms.status
FROM members AS m
JOIN membership_stack AS ms ON ms.member_id = m.id
WHERE ms.id = (
    SELECT ms1.id
    FROM membership_stack AS ms1
    WHERE ms1.member_id = ms.member_id
    ORDER BY ms1.sequence desc, 
             FIELD(ms1.status, 'active', 'completed', 'cancelled', 'abandoned'),
             ms1.id asc
    LIMIT 1
)
ORDER BY m.id;

演示:http://rextester.com/HGU18448

更新

要包含在membership_stack 表中没有条目的成员,您应该使用 LEFT JOIN,并将子查询条件从 WHERE 子句移到 ON 子句:

SELECT m.id, m.first_name, ms.sequence, ms.team, ms.status
FROM members AS m
LEFT JOIN membership_stack AS ms 
    ON  ms.member_id = m.id
    AND ms.id = (
        SELECT ms1.id
        FROM membership_stack AS ms1
        WHERE ms1.member_id = ms.member_id
        ORDER BY ms1.sequence desc, 
                 FIELD(ms1.status, 'active', 'completed', 'cancelled', 'abandoned'),
                 ms1.id asc
        LIMIT 1
    )
ORDER BY m.id;

演示:http://rextester.com/NPI79503

【讨论】:

  • 是的,我明白了。再次尝试理解您的问题。
  • 我已经编辑了我的问题以包括成员可能在成员资格堆栈中没有任何条目的情况。有没有办法让这个查询适应这些情况?或者将WHERE 条件与LEFT JOIN 放在ON 子句中是否有任何问题?
  • @rink.attendant.6 使用左连接。检查更新。
【解决方案2】:

我会使用变量来做到这一点。

您正在寻找最适合您的特殊订购的membership_stack 行。我只专注于此。 join 回到 members 是微不足道的。

select ms.*
from (select ms.*,
             (@rn := if(@m = member_id, @rn + 1,
                        if(@m := member_id, 1, 1)
                       )
             ) as rn
      from membership_stack ms cross join
           (select @m := -1, @rn := 0) params
      order by member_id, sequence desc,
               field(ms.status, 'active', 'completed', 'cancelled', 'abandoned')
     ) ms
where rn = 1;

变量是逻辑的实现方式。排序是获得正确结果的关键。

编辑:

MySQL 在子查询中对LIMIT 非常挑剔。这可能会起作用:

select ms.*
from membership_stack ms
where (sequence, status) = (select ms2.sequence, ms2.status
                            from membership_stack ms2
                            where ms2.member_id = ms.member_id
                            order by ms2.member_id, ms2.sequence desc,
                                     field(ms2.status, 'active', 'completed', 'cancelled', 'abandoned')
                            limit 1
                           );

【讨论】:

  • 子查询中带有 LIMIT 的 WHERE 会产生错误 Unknown column 'field' in 'where clause'。变量 one 似乎工作正常,您介意多解释一下,以便我可以理解并向其他人提出解决方案吗?
  • @rink.attendant.6 。 . .您似乎对窗口函数很熟悉。这是row_number() over (partition by member_id order by field(. . .) 的MySQL 等价物。变量逐行跟踪状态。 (第二个查询中的错误是列名输入错误。)
  • 我实际上从未听说过窗口函数。我只看到row_number() 来自尝试解决这个确切问题的人,他有过另一个 RDBMS 的经验。无论如何,我将与我的团队讨论这个 SO 问题,并希望在下周的某个时候接受解决方案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-09-27
  • 2011-12-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-07-22
  • 1970-01-01
相关资源
最近更新 更多