在锦标赛中寻找每个玩家组的获胜者 - 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 获得每组的头号玩家。

Demo on DB Fiddle:

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

这是我的解决方案。基本上我做了什么:

  1. 将table分成两列
  2. left join,使用coalesce修复空值
  3. 创建score_rank,根据分数排名(降序)
  4. 使用 where 函数 where score_rank = 1(这将首先根据最高分筛选玩家)
  5. 现在解决 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 更直观。显然,欢迎挑战。