在锦标赛中寻找每个玩家组的获胜者 - PostgreSQL
Finding winner of each player group in a tournament - PostgreSQL
关于 PostgreSQL 的这个问题困扰了我一段时间。
我尝试了自己并尽我所能进行了搜索,但最终无法得出将第 3 组玩家考虑在内的结果。
所以即使这个问题可能是重复的,也没有找到正确的答案。
希望得到一些帮助。
问题如下:
编写一个 SQL 查询,return 是一个 table,其中包含每个组中的获胜者。
每条记录应包含组的ID和本组获胜者的ID(来自同一组的玩家竞争)。
记录按照组内ID号递增的顺序排列,平局时,ID号小者获胜。
鉴于此架构:
玩家:
+-------------+-------+
| Column Name | Type |
+-------------+-------+
| player_id | int |
| group_id | int |
+-------------+-------+
匹配项:
+---------------+---------+
| Column Name | Type |
+---------------+---------+
| match_id | int |
| first_player | int |
| second_player | int |
| first_score | int |
| second_score | int |
+---------------+---------+
对于下面的例子:
玩家table:
+-----------+------------+
| player_id | group_id |
+-----------+------------+
| 20 | 2 |
| 30 | 1 |
| 40 | 3 |
| 45 | 1 |
| 50 | 2 |
| 40 | 1 |
+-----------+------------+
匹配 table:
+------------+--------------+---------------+-------------+--------------+
| match_id | first_player | second_player | first_score | second_score |
+------------+--------------+---------------+-------------+--------------+
| 1 | 30 | 45 | 10 | 12 |
| 2 | 20 | 50 | 5 | 5 |
| 3 | 65 | 45 | 10 | 10 |
| 4 | 30 | 65 | 3 | 15 |
| 5 | 45 | 65 | 8 | 4 |
+------------+--------------+---------------+-------------+--------------+
您的查询应该 return:
+-----------+------------+------------+
| group_id | winner_id | tot_score |
+-----------+------------+------------+
| 1 | 45 | 30 |
| 2 | 20 | 5 |
| 3 | 40 | 0 |
+-----------+------------+------------+
第一组选手45分得分最高。
在第 2 组中,两个玩家都获得了 5 分,但是玩家 20 的 ID 较低,因此他是赢家。
第三组只有一名选手,虽然他没有参加任何比赛,但他是获胜者。
我到目前为止做的最好的是(在 PostgreSQL 上):
SELECT group_id, player_id, score
FROM
(
SELECT sq2.player_id, p.group_id, sq2.score,
RANK() OVER (PARTITION BY p.group_id ORDER BY score DESC) as position
FROM
(
SELECT player_id, SUM(score) score
FROM (
SELECT first_player as player_id, first_score as score FROM matches
UNION ALL
SELECT second_player as player_id, second_score as score FROM matches
) as sq1
GROUP BY player_id
) as sq2
right join players p
on p.player_id = sq2.player_id
) as sq3
WHERE position = 1 order by group_id, player_id
输出如下:
+-----------+-----------------------+------------+
| group_id | player_id | score |
+-----------+-----------------------+------------+
| 1 | 45 | 30 |
| 2 | 20 | 5 |
| 2 | 50 | 5 |
| 3 | [NULL](instead of 40) | [NULL] (should be 0)|
+-----------+-----------------------+------------+
你能帮忙生成一个结果完全正确的查询吗? (第 3 组玩家的详细信息)
还想知道为什么在 player_id 中查询 returning NULL 用于正确的连接。
感谢您的帮助!
* 这个问题显然也是 Leetcode.com 上的一个问题,叫做 "Tournament winners" *
您可以使用 distinct on
和聚合,如下所示:
select distinct on (p.group_id)
p.group_id,
player_id winner_id,
coalesce(sum(first_score) filter(where p.player_id = m.first_player), 0)
+ coalesce(sum(second_score) filter(where p.player_id = m.second_player), 0)
tot_score
from players p
left join matches m on p.player_id in (m.first_player, m.second_player)
group by p.group_id, p.player_id
order by p.group_id, tot_score desc, p.player_id
这通过加入玩家和具有 in
条件的比赛来实现。然后,我们使用条件聚合来计算每个玩家的总分。最后,distinct on
获得每组的头号玩家。
group_id | winner_id | tot_score
-------: | --------: | --------:
1 | 45 | 30
2 | 20 | 5
3 | 40 | 0
注意:感谢您提出的问题!所以可以使用更多。
我将推荐横向连接以及 distinct on
:
select distinct on (p.group_id) p.group_id, p.player_id, sum(v.win_score)
from players p left join
(matches m left join lateral
(values ((case when m.first_score > m.second_score then m.first_player else m.second_player end),
(case when m.first_score > m.second_score then m.first_score else m.second_score end)
)
) v(winner, win_score)
on 1=1
)
on v.winner = p.player_id
group by p.group_id, p.player_id
order by p.group_id, sum(v.win_score) desc nulls last, p.player_id;
Here 是一个 db<>fiddle
这是我的解决方案。基本上我做了什么:
- 将table分成两列
- left join,使用coalesce修复空值
- 创建score_rank,根据分数排名(降序)
- 使用 where 函数 where score_rank = 1(这将首先根据最高分筛选玩家)
- 现在解决 group_id 数字 2 的平分(其中得分是平分,获胜者是基于 winner_id),创建一个 row_number 基于 winner_id, 升序。因为 20 比 50 高,它会得到第 1 位。现在我们可以使用 where winner_rank = 1
问题是,我认为我的代码不太有效,因为我使用了 5 个 ctes。期待知道是否有更简单的解决方案(我不喜欢使用子查询)
这是 dbfiddle link
https://www.db-fiddle.com/f/oyTAQZL5bcFARvvjv2ad4X/1
with matches as(
select *
from
(
select first_player as winner_id,first_score as score
from matches
union
select second_player, second_score
from matches) as matches
),
players_1 as(
select group_id,
coalesce(winner_id,player_id) as winner_id,
coalesce(score,0) as score
from players
left join matches on matches.winner_id = players.player_id
),
players_2 as (
select
group_id,
winner_id,
sum(score) as score
from players_1
group by group_id,winner_id
),
players_3 as (
select
group_id,
winner_id,
score,
rank () over (partition by group_id order by score desc) as score_rank
from players_2
),
players_4 as (
select group_id,winner_id,score,
row_number () over (partition by group_id order by winner_id asc) as winner_rank
from players_3
where score_rank =1
)
select group_id, winner_id, score
from players_4
where winner_rank = 1
这里是 MSSQL 的解决方案。
WITH match_winners AS
(
select case
when m1.first_score > m1.second_score THEN m1.first_player
when m1.first_score < m1.second_score THEN m1.second_player
when m1.first_score = m1.second_score AND m1.first_player > m1.second_player THEN m1.second_player
when m1.first_score = m1.second_score AND m1.first_player < m1.second_player THEN m1.first_player
END as 'winner_id',
case WHEN m1.first_score>m1.second_score THEN m1.first_score
ELSE m1.second_score
END AS 'winscore',
match_id
from matches m1
)
SELECT group_id, player_id, COALESCE(winscore,0)
FROM
(
SELECT p.group_id, p.player_id, SUM(mw.winscore) as 'winscore',
rank() OVER (PARTITION BY p.group_id ORDER BY SUM(mw.winscore) DESC) AS result
FROM players p
LEFT JOIN match_winners mw ON mw.winner_id = p.player_id
LEFT JOIN matches m ON m.match_id = mw.match_id
GROUP BY p.group_id, p.player_id
) rank_group_winners
WHERE rank_group_winners.result = 1
可能不是最 eloquent 的解决方案,但我发现首先构造获胜者 table 更直观。显然,欢迎挑战。
关于 PostgreSQL 的这个问题困扰了我一段时间。 我尝试了自己并尽我所能进行了搜索,但最终无法得出将第 3 组玩家考虑在内的结果。 所以即使这个问题可能是重复的,也没有找到正确的答案。 希望得到一些帮助。
问题如下: 编写一个 SQL 查询,return 是一个 table,其中包含每个组中的获胜者。 每条记录应包含组的ID和本组获胜者的ID(来自同一组的玩家竞争)。 记录按照组内ID号递增的顺序排列,平局时,ID号小者获胜。
鉴于此架构:
玩家:
+-------------+-------+
| Column Name | Type |
+-------------+-------+
| player_id | int |
| group_id | int |
+-------------+-------+
匹配项:
+---------------+---------+
| Column Name | Type |
+---------------+---------+
| match_id | int |
| first_player | int |
| second_player | int |
| first_score | int |
| second_score | int |
+---------------+---------+
对于下面的例子:
玩家table:
+-----------+------------+
| player_id | group_id |
+-----------+------------+
| 20 | 2 |
| 30 | 1 |
| 40 | 3 |
| 45 | 1 |
| 50 | 2 |
| 40 | 1 |
+-----------+------------+
匹配 table:
+------------+--------------+---------------+-------------+--------------+
| match_id | first_player | second_player | first_score | second_score |
+------------+--------------+---------------+-------------+--------------+
| 1 | 30 | 45 | 10 | 12 |
| 2 | 20 | 50 | 5 | 5 |
| 3 | 65 | 45 | 10 | 10 |
| 4 | 30 | 65 | 3 | 15 |
| 5 | 45 | 65 | 8 | 4 |
+------------+--------------+---------------+-------------+--------------+
您的查询应该 return:
+-----------+------------+------------+
| group_id | winner_id | tot_score |
+-----------+------------+------------+
| 1 | 45 | 30 |
| 2 | 20 | 5 |
| 3 | 40 | 0 |
+-----------+------------+------------+
第一组选手45分得分最高。 在第 2 组中,两个玩家都获得了 5 分,但是玩家 20 的 ID 较低,因此他是赢家。 第三组只有一名选手,虽然他没有参加任何比赛,但他是获胜者。
我到目前为止做的最好的是(在 PostgreSQL 上):
SELECT group_id, player_id, score
FROM
(
SELECT sq2.player_id, p.group_id, sq2.score,
RANK() OVER (PARTITION BY p.group_id ORDER BY score DESC) as position
FROM
(
SELECT player_id, SUM(score) score
FROM (
SELECT first_player as player_id, first_score as score FROM matches
UNION ALL
SELECT second_player as player_id, second_score as score FROM matches
) as sq1
GROUP BY player_id
) as sq2
right join players p
on p.player_id = sq2.player_id
) as sq3
WHERE position = 1 order by group_id, player_id
输出如下:
+-----------+-----------------------+------------+
| group_id | player_id | score |
+-----------+-----------------------+------------+
| 1 | 45 | 30 |
| 2 | 20 | 5 |
| 2 | 50 | 5 |
| 3 | [NULL](instead of 40) | [NULL] (should be 0)|
+-----------+-----------------------+------------+
你能帮忙生成一个结果完全正确的查询吗? (第 3 组玩家的详细信息)
还想知道为什么在 player_id 中查询 returning NULL 用于正确的连接。 感谢您的帮助!
* 这个问题显然也是 Leetcode.com 上的一个问题,叫做 "Tournament winners" *
您可以使用 distinct on
和聚合,如下所示:
select distinct on (p.group_id)
p.group_id,
player_id winner_id,
coalesce(sum(first_score) filter(where p.player_id = m.first_player), 0)
+ coalesce(sum(second_score) filter(where p.player_id = m.second_player), 0)
tot_score
from players p
left join matches m on p.player_id in (m.first_player, m.second_player)
group by p.group_id, p.player_id
order by p.group_id, tot_score desc, p.player_id
这通过加入玩家和具有 in
条件的比赛来实现。然后,我们使用条件聚合来计算每个玩家的总分。最后,distinct on
获得每组的头号玩家。
group_id | winner_id | tot_score -------: | --------: | --------: 1 | 45 | 30 2 | 20 | 5 3 | 40 | 0
注意:感谢您提出的问题!所以可以使用更多。
我将推荐横向连接以及 distinct on
:
select distinct on (p.group_id) p.group_id, p.player_id, sum(v.win_score)
from players p left join
(matches m left join lateral
(values ((case when m.first_score > m.second_score then m.first_player else m.second_player end),
(case when m.first_score > m.second_score then m.first_score else m.second_score end)
)
) v(winner, win_score)
on 1=1
)
on v.winner = p.player_id
group by p.group_id, p.player_id
order by p.group_id, sum(v.win_score) desc nulls last, p.player_id;
Here 是一个 db<>fiddle
这是我的解决方案。基本上我做了什么:
- 将table分成两列
- left join,使用coalesce修复空值
- 创建score_rank,根据分数排名(降序)
- 使用 where 函数 where score_rank = 1(这将首先根据最高分筛选玩家)
- 现在解决 group_id 数字 2 的平分(其中得分是平分,获胜者是基于 winner_id),创建一个 row_number 基于 winner_id, 升序。因为 20 比 50 高,它会得到第 1 位。现在我们可以使用 where winner_rank = 1
问题是,我认为我的代码不太有效,因为我使用了 5 个 ctes。期待知道是否有更简单的解决方案(我不喜欢使用子查询)
这是 dbfiddle link https://www.db-fiddle.com/f/oyTAQZL5bcFARvvjv2ad4X/1
with matches as(
select *
from
(
select first_player as winner_id,first_score as score
from matches
union
select second_player, second_score
from matches) as matches
),
players_1 as(
select group_id,
coalesce(winner_id,player_id) as winner_id,
coalesce(score,0) as score
from players
left join matches on matches.winner_id = players.player_id
),
players_2 as (
select
group_id,
winner_id,
sum(score) as score
from players_1
group by group_id,winner_id
),
players_3 as (
select
group_id,
winner_id,
score,
rank () over (partition by group_id order by score desc) as score_rank
from players_2
),
players_4 as (
select group_id,winner_id,score,
row_number () over (partition by group_id order by winner_id asc) as winner_rank
from players_3
where score_rank =1
)
select group_id, winner_id, score
from players_4
where winner_rank = 1
这里是 MSSQL 的解决方案。
WITH match_winners AS
(
select case
when m1.first_score > m1.second_score THEN m1.first_player
when m1.first_score < m1.second_score THEN m1.second_player
when m1.first_score = m1.second_score AND m1.first_player > m1.second_player THEN m1.second_player
when m1.first_score = m1.second_score AND m1.first_player < m1.second_player THEN m1.first_player
END as 'winner_id',
case WHEN m1.first_score>m1.second_score THEN m1.first_score
ELSE m1.second_score
END AS 'winscore',
match_id
from matches m1
)
SELECT group_id, player_id, COALESCE(winscore,0)
FROM
(
SELECT p.group_id, p.player_id, SUM(mw.winscore) as 'winscore',
rank() OVER (PARTITION BY p.group_id ORDER BY SUM(mw.winscore) DESC) AS result
FROM players p
LEFT JOIN match_winners mw ON mw.winner_id = p.player_id
LEFT JOIN matches m ON m.match_id = mw.match_id
GROUP BY p.group_id, p.player_id
) rank_group_winners
WHERE rank_group_winners.result = 1
可能不是最 eloquent 的解决方案,但我发现首先构造获胜者 table 更直观。显然,欢迎挑战。