GROUP BY 和自定义顺序
GROUP BY and custom order
我已经通读了 MySQL order by before group by 上的答案,但是将它应用于我的查询最终会在一个相当简单的情况下以子查询中的子查询结束,所以我想知道是否可以简化:
带有示例数据的架构
为简洁起见,我省略了 members
table 上的其他字段。此外,在实际应用程序中还有更多 table 加入,但这些加入很简单。给我带来问题的是 membership_stack
table。
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');
这按预期工作,但如果 "most recent sequence" 涉及多个团队,成员可能会出现多次。我需要做的是再次聚合 id
和 select 每组中的第一行。
然而这会带来一些问题:
- 有no
FIRST()
function in MySQL
- 整个结果集将变成一个子table(子查询),这在这里没什么大不了的,但查询在应用程序上相当大。
- 它需要与 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 |
我该怎么办?
编辑: 我注意到有些成员不属于任何团队。这些成员应该包含在结果集中,这些字段的值为空。问题已更新以反映新信息。
我会使用变量来做到这一点。
您正在寻找最适合您特殊订购的 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 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
table 中没有条目的成员,您应该使用 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;
我已经通读了 MySQL order by before group by 上的答案,但是将它应用于我的查询最终会在一个相当简单的情况下以子查询中的子查询结束,所以我想知道是否可以简化:
带有示例数据的架构
为简洁起见,我省略了 members
table 上的其他字段。此外,在实际应用程序中还有更多 table 加入,但这些加入很简单。给我带来问题的是 membership_stack
table。
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');
这按预期工作,但如果 "most recent sequence" 涉及多个团队,成员可能会出现多次。我需要做的是再次聚合 id
和 select 每组中的第一行。
然而这会带来一些问题:
- 有no
FIRST()
function in MySQL - 整个结果集将变成一个子table(子查询),这在这里没什么大不了的,但查询在应用程序上相当大。
- 它需要与 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 |
我该怎么办?
编辑: 我注意到有些成员不属于任何团队。这些成员应该包含在结果集中,这些字段的值为空。问题已更新以反映新信息。
我会使用变量来做到这一点。
您正在寻找最适合您特殊订购的 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 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
table 中没有条目的成员,您应该使用 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;