【问题标题】:Finding winner of each player group in a tournament - PostgreSQL在锦标赛中找到每个玩家组的获胜者 - PostgreSQL
【发布时间】:2020-05-08 19:42:02
【问题描述】:

关于 PostgreSQL 的这个问题困扰了我很长一段时间。 我尝试了自己并搜索了所有可能的地方,但最终无法得出将第 3 组的玩家考虑在内的结果。 所以即使这个问题可能是重复的,也没有找到正确的答案。 希望得到一些帮助。

问题如下: 编写一个 SQL 查询,返回一个包含每个组中获胜者的表。 每条记录都应包含该组的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     |
+---------------+---------+

以下示例:

球员表:

+-----------+------------+
| player_id | group_id   |
+-----------+------------+
| 20        | 2          |
| 30        | 1          |
| 40        | 3          |
| 45        | 1          |
| 50        | 2          |
| 40        | 1          |
+-----------+------------+

匹配表:

+------------+--------------+---------------+-------------+--------------+
| 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            |
+------------+--------------+---------------+-------------+--------------+

您的查询应该返回:

+-----------+------------+------------+
| group_id  | winner_id  | tot_score  |
+-----------+------------+------------+ 
| 1         | 45         | 30         |
| 2         | 20         | 5          |
| 3         | 40         | 0          |
+-----------+------------+------------+

在第 1 组中,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 中为正确的连接返回 NULL。 感谢您的帮助!

* 这个问题显然也是 Leetcode.com 上的一个问题,叫做“Tournament Winners” *

【问题讨论】:

  • 如果出现平局,即一个小组有两个不同的玩家得分相同,那么预期的结果是什么?
  • 也许这回答了你的问题? stackoverflow.com/questions/51487670/…
  • @jarlh 最低 ID 获胜
  • 这是一个小提琴,所以所有帮助者都很容易帮助:dbfiddle.uk/…
  • @JaimeS 谢谢,但此解决方案将玩家排除在第 3 组之外

标签: sql postgresql subquery left-join greatest-n-per-group


【解决方案1】:

您可以使用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 |获胜者ID | tot_score --------: | --------: | --------: 1 | 45 | 30 2 | 20 | 5 3 | 40 | 0

注意:感谢您提出的问题! SO 可以使用更多。

【讨论】:

  • 感谢您的回答!你使用了一些我不熟悉的语法(distinct on、coalesce、filter),所以我需要对这些进行一些研究(如果你想详细说明那也很好),但它似乎有效完美!
【解决方案2】:

我将建议与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 是一个数据库小提琴

【讨论】:

  • 您能否展示一个演示并详细说明横向连接的使用?我无法在我的 PostgreSQL 引擎上编译此查询
  • @VcMr 。 . .我修复了错误并添加了一个 dbfiddle.
【解决方案3】:

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

  1. 将表格分成两列
  2. 左连接,使用合并来修复空值
  3. 创建 score_rank,根据分数排名(降序)
  4. 使用 where 函数 where score_rank = 1(这将首先根据玩家的最高分过滤玩家)
  5. 现在要解决 group_id 编号 2 的平局分数(其中分数是平局,获胜者基于获胜者 ID),根据获胜者 ID 创建一个 row_number,升序。因为 20 比 50 高,所以它会获得 1 级。现在我们可以使用 where Winner_rank = 1

关键是,我认为我的代码不太有效,因为我使用了 5 个 ctes。期待知道是否有更简单的解决方案(我不喜欢使用子查询)

这是 dbfiddle 链接 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

【讨论】:

    【解决方案4】:

    这是 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
    

    可能不是最有说服力的解决方案,但我发现首先构建获胜者表更直观。显然,欢迎挑战。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-10-17
      • 1970-01-01
      • 2018-07-23
      • 2019-10-14
      • 2021-08-04
      • 1970-01-01
      • 1970-01-01
      • 2021-07-18
      相关资源
      最近更新 更多