繁体   English   中英

SQL 多个 JOIN 或子查询,但避免笛卡尔积

[英]SQL multiple JOINs or subqueries but avoid cartesian product

我想为游戏实现一个 SQL 数据库。 有许多玩家参加不同的锦标赛。 对于每场比赛,玩家都有一个单独的帐户。 所有游戏都列在一张大表中,其中锦标赛帐户用于描述赢家、输家以及游戏得分。

架构在http://sqlfiddle.com/#!9/55378a或这里再次给出


    CREATE TABLE `players` (
      `id` int NOT NULL,
      `name` varchar(5),
      PRIMARY KEY (`id`)
    );
    CREATE TABLE `tournamentAccounts` (
      `tId` int NOT NULL,
      `playerId` int NOT NULL,
      `handicap` int NOT NULL DEFAULT 10,
      PRIMARY KEY (`tId`)
    );
    CREATE TABLE `games` (
      `gameId` int NOT NULL,
      `winnerTId` int NOT NULL,
      `loserTId` int NOT NULL,
      `score` int NOT NULL DEFAULT 0,
      PRIMARY KEY (`gameId`)
    );
    INSERT INTO `players` (`id`, `name`) VALUES
    (1, 'a'), (2, 'b'), (3, 'c');
    INSERT INTO `tournamentAccounts` (`tId`, `playerId`, `handicap`) VALUES
    (1, 1, 10), (2, 1, 2), (3, 2, 0);
    INSERT INTO `games` (`gameId`, `winnerTId`, `loserTId`, `score`) VALUES
    (1, 1, 3, 3), (2, 1, 3, 2), (3, 3, 1, 6);

我想要实现的目标:列出特定玩家的所有锦标赛得分,即让分handicap + scorepoints of won games - scorepoints of lost games得分handicap + scorepoints of won games - scorepoints of lost games 对于给定的输入,结果集应包含总分分别为 9(tId=1)和 2(tId=2)的两行。 这里的例子是简化的,因为在我的例子中,在锦标赛账户和游戏表之间有更多的条件匹配(例如时间段等),但我想一旦我理解了基本方法,我就可以自己扩展它:-)

我的方法直到现在都失败了,因为我无法获得一个很好的 JOIN 或子查询来工作(我想避免存储过程)。

尝试 1:直接加入


    SELECT t.*, (t.handicap +COALESCE(SUM(w.score),0) -COALESCE(SUM(l.score),0)) AS score
    FROM tournamentAccounts t
    LEFT JOIN games w ON w.winnerTId = t.tId
    LEFT JOIN games l ON l.loserTId = t.tId
    WHERE playerId = 1
    GROUP BY t.tId

虽然这返回了正确的行数,但双 LEFT JOIN 导致了一个笛卡尔积,就像看起来一样:两场赢的比赛与输的比赛合并成两个数据集,因此 10 + 3 - 6 + 2 - 6。这种效果显然变成更糟糕的是我在游戏表中的匹配行越多。

尝试 2 :UNION 与 JOIN(类似于sql 避免笛卡尔积


    SELECT SUM(COALESCE(x.aa,0))
    FROM
    ((SELECT -l.score AS aa FROM games l LEFT JOIN tournamentAccounts t ON l.loserTId = t.tId WHERE t.playerId = 1)
    UNION
    (SELECT w.score AS aa FROM games w LEFT JOIN tournamentAccounts t ON w.winnerTId = t.tId WHERE t.playerId = 1)) x

有了这个,我得到了适当的得分值,但是它还没有与相应的让球值相结合,而且我不知道如何从这里扩展到该玩家的所有锦标赛账户(这里,我只是拿了一个数据的小快照)以 SQL 方式。

我只会将您查询的游戏部分变成一个联合,而不是整个事情:

SELECT t.*, (t.handicap +COALESCE(SUM(win_score),0) -COALESCE(SUM(loss_score),0)) AS score
FROM tournamentAccounts t
LEFT JOIN (
    SELECT w.winnerTId AS tId, w.score AS win_score, 0 AS loss_score FROM games w
    UNION ALL
    SELECT l.loserTId, 0, l.score FROM games l
) games_won_or_lost ON games_won_or_lost.tId=t.tId
WHERE playerId = 1
GROUP BY t.tId

另一种选择是取消笛卡尔积的影响。 由于输掉比赛的次数,您知道获胜分数过高,因此将SUM(w.score)替换为ROUND(SUM(w.score)/GREATEST(COUNT(DISTINCT l.gameId),1)) 同样, SUM(l.score)变成ROUND(SUM(l.score)/GREATEST(COUNT(DISTINCT w.gameId),1))

小提琴

下面怎么样:-

SELECT t.*, (t.handicap + coalesce(wscore,0) - coalesce(lscore,0)) AS score
    FROM tournamentAccounts t 
    LEFT JOIN (
         select sum(score) wscore, winnerTId wid 
          from games 
          group by winnerTid
      ) as w ON w.wid = t.tid
    left join (
        select sum(score) lscore, loserTid lid 
         from games 
          group by loserTid
    ) as l ON l.lid = t.tid
    where playerId = 1

我得到的结果是

tId playerId    handicap    score
1   1           10          9 
2   1           2           2

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM