PostgreSQL - 左计数输出错误

时间:2015-02-12 12:53:33

标签: sql postgresql aggregate-filter

我试图设置一组简单的表来显示锦标赛的结果 - 我有以下结构:

CREATE TABLE players(
    id SERIAL PRIMARY KEY,
    name TEXT);

CREATE TABLE matches(
    id SERIAL PRIMARY KEY,
    player_one_id INTEGER REFERENCES players,
    player_two_id INTEGER REFERENCES players,
    winner_id INTEGER REFERENCES players);

我输入了一些测试数据,如下:

INSERT INTO players (name) VALUES ('Mike Jones');
INSERT INTO players (name) VALUES ('Albert Awesome');
INSERT INTO players (name) VALUES ('Sad Sally');
INSERT INTO players (name) VALUES ('Lonely Lenny');

INSERT INTO matches (player_one_id, player_two_id, winner_id) VALUES (1,2,1);
INSERT INTO matches (player_one_id, player_two_id, winner_id) VALUES (3,4,4);

我试图执行查询,为每个玩家提供以下结果:

id,name,matched_won,matches_played。

到目前为止,我有以下查询:

SELECT players.id, players.name, count(matches.winner_id) as matches_won
                               , count(matches.id) as matches_played
    FROM players left join matches
    ON players.id = matches.winner_id
GROUP BY players.id
ORDER BY matches_won DESC

而且,不幸的是,我得到了如下错误的输出(每个玩家应该有1个match_played):

 id |      name      | matches_won | matches_played 
----+----------------+-------------+----------------
  4 | Lonely Lenny   |           1 |              1
  1 | Mike Jones     |           1 |              1
  2 | Albert Awesome |           0 |              0
  3 | Sad Sally      |           0 |              0
(4 rows)

现在,我知道输出错误的原因是因为加入了players.id = matches.winner_id,但我的问题是:

是否可以通过一个左连接查询获得这些结果?如果是这样,怎么样?如果可能的话,我想避免多次查询。

4 个答案:

答案 0 :(得分:3)

是。首先,您需要了解count()只计算具有非NULL值的行数,因此您的两个计数应该相同。

要获得胜利者,请使用条件聚合:

SELECT p.id, p.name,
       sum(case when m.winner_id = p.id then 1 else 0 end) as matches_won,
       count(m.id) as matches_played
FROM players p left join
     matches m
     ON p.id in (m.player_one_id, m.player_two_id)
GROUP BY p.id
ORDER BY matches_won DESC;

您还需要修复join条件。你不能只是加入胜利者并期望获得所有比赛的数量。

答案 1 :(得分:1)

子选择解决方案:

SELECT players.id, players.name,
       (select count(*)
        from matches
        where matches.winner_id = players.id) as matches_won,
       (select count(*)
        from matches
        where players.id in (player_one_id, player_two_id)) as matches_played
FROM players
ORDER BY matches_won DESC

答案 2 :(得分:1)

除了Gordon的回答,你可以使用COIF()而不是SUM(),使用NULLIF或FILTER(从PostgreSQL 9.4开始过滤):

使用NULLIF(),因为使用列名时NULL不计算:

SELECT p.id, p.name,
       count(nullif(m.winner_id <> p.id, false)) as matches_won,
       count(m.id) as matches_played
FROM players p 
    left join matches m ON p.id in (m.player_one_id, m.player_two_id)
GROUP BY p.id
ORDER BY 
    matches_won DESC;

使用FILTER:

SELECT p.id, p.name,
       count(*) filter (WHERE m.winner_id = p.id) as matches_won,
       count(m.id) as matches_played
FROM players p 
    left join matches m ON p.id in (m.player_one_id, m.player_two_id)
GROUP BY p.id
ORDER BY 
    matches_won DESC;

答案 3 :(得分:0)

SELECT p.name,COUNT(m.player_one_id)+ COUNT(m1.player_two_id) AS num_of_matches_played
,COUNT(m2.winner_id) AS num_of_matches_won FROM players p 
LEFT OUTER JOIN matches m ON p.id = m.player_one_id
LEFT OUTER JOIN matches m1 ON p.id = m1.player_two_id
LEFT OUTER JOIN matches m2 ON p.id = m2.winner_id
GROUP BY p.name