Mysql 左键多次加入同一个表

Mysql 左键多次加入同一个表,mysql,sql,Mysql,Sql,假设我有一个可以由2人、3人或4人玩的游戏。我在数据库MySQL 5.1中跟踪这样一个游戏,分为三个表,如下所示。我希望这些字段是不言自明的: create table users (id int, login char(8)); create table games (id int, stime datetime, etime datetime); create table users_games (uid int, gid int, score int); [游戏表中跟踪的两个时间是开始和

假设我有一个可以由2人、3人或4人玩的游戏。我在数据库MySQL 5.1中跟踪这样一个游戏,分为三个表,如下所示。我希望这些字段是不言自明的:

create table users (id int, login char(8));
create table games (id int, stime datetime, etime datetime);
create table users_games (uid int, gid int, score int);
[游戏表中跟踪的两个时间是开始和结束时间]

下面是一些填充表格的虚拟数据:

insert into games values
(1, '2011-12-01 10:00:00', '2011-12-01 13:00:00'),
(2, '2011-12-02 11:00:00', '2011-12-01 14:00:00'),
(3, '2011-12-03 12:00:00', '2011-12-01 15:00:00'),
(4, '2011-12-04 13:00:00', '2011-12-01 16:00:00');

insert into users_games values
(101, 1, 10),
(102, 1, 11),
(101, 2, 12),
(103, 2, 13),
(104, 2, 14),
(102, 3, 15),
(103, 3, 16),
(104, 3, 17),
(105, 3, 18),
(102, 4, 19),
(104, 4, 20),
(105, 4, 21);
现在,我需要以以下格式生成一份报告:

gid     p1    p2    p3    p4  started ended
1      101   102               [g1]    [g1]
2      101   103   104         [g2]    [g2]
3      102   103   104   105   [g3]    [g3]
4      102   104   105         [g4]    [g4]
也就是说,一份报告显示了在同一排玩游戏的所有玩家。我还需要他们的分数和用户表中的一些其他信息,但这是第2阶段:-

我从这个开始:

select g.id, g.stime, g.etime, ug1.uid, ug2.uid, ug3.uid, ug4.uid
from games g, users_games ug1, users_games ug2, users_games ug3, users_games ug4
where
g.id = ug1.gid and
ug1.gid = ug2.gid and
ug1.uid < ug2.uid and
ug2.gid = ug3.gid and
ug2.uid < ug3.uid and
ug3.gid = ug4.gid and
ug3.uid < ug4.uid
这给了我所有四个座位都被占据的游戏,即上面虚拟数据中只有游戏ID 3。但这只是我需要的数据的一个子集

这是我的第二次尝试:

select g.id, g.stime, g.etime, ug1.uid, ug2.uid,
    ifnull(ug3.uid, ''), ifnull(ug4.uid, '')
from ( games g, users_games ug1, users_games ug2 )
left join users_games ug3 on ug2.gid = ug3.gid and ug2.uid < ug3.uid
left join users_games ug4 on ug3.gid = ug4.gid and ug3.uid < ug4.uid
where
g.id = ug1.gid and
ug1.gid = ug2.gid and
ug1.uid < ug2.uid
这给了我14行上面的虚拟数据。我试图通过将ug1锚定到最低UID播放器的条目来消除一个错误源:

select g.id, g.stime, g.etime, ug1.uid, ug2.uid,
    ifnull(ug3.uid, ''), ifnull(ug4.uid, '')
from
( games g, users_games ug1, users_games ug2,
    (select gid as g, min(uid) as u from users_games group by g) as xx
)
left join users_games ug3 on ug2.gid = ug3.gid and ug2.uid < ug3.uid
left join users_games ug4 on ug3.gid = ug4.gid and ug3.uid < ug4.uid
where
g.id = xx.g and
ug1.uid = xx.u and
g.id = ug1.gid and
ug1.gid = ug2.gid and
ug1.uid < ug2.uid
现在我只剩下9行了,但仍然有很多虚假数据。我可以看出问题所在——例如在游戏3中,ug1锚定在用户102上,仍然有三名玩家可以锚定ug2。等等但我无法找到解决这个难题的方法——我如何最终实现一个查询,以正确的顺序和数字输出4行玩家


在我看来,在其他情况下,这应该是一个已解决的问题。非常感谢您的帮助。

您遇到的一个问题是,您没有将用户描述为玩家1、2、3或4的字段。然而,您需要确保每次左键加入时只加入一个玩家

如果您将玩家id字段添加到用户游戏中,它将变得微不足道

SELECT
  *
FROM
  games
LEFT JOIN
  users_games      AS p1
    ON  p1.gid = games.id
    AND p1.player_id = 1
LEFT JOIN
  users_games      AS p2
    ON  p2.gid = games.id
    AND p2.player_id = 2
LEFT JOIN
  users_games      AS p3
    ON  p3.gid = games.id
    AND p3.player_id = 3
LEFT JOIN
  users_games      AS p4
    ON  p4.gid = games.id
    AND p4.player_id = 4
有一些替代方案可以避免所有的左连接,但是这个例子很好地服务,因为它是下一步的基础

如果无法添加此字段,它将变得更复杂。SQL Server、Oracle等可以使用行号代理此播放器id字段,MySQL不能

相反,您需要相关的子查询来识别“下一个玩家”

SELECT
  *
FROM
  games
LEFT JOIN
  users_games      AS p1
    ON  p1.gid = games.id
    AND p1.uid = (SELECT MIN(uid) FROM users_games WHERE gid = games.id)
LEFT JOIN
  users_games      AS p2
    ON  p2.gid = games.id
    AND p2.uid = (SELECT MIN(uid) FROM users_games WHERE gid = games.id AND uid > p1.uid)
LEFT JOIN
  users_games      AS p3
    ON  p3.gid = games.id
    AND p3.uid = (SELECT MIN(uid) FROM users_games WHERE gid = games.id AND uid > p2.uid)
LEFT JOIN
  users_games      AS p4
    ON  p4.gid = games.id
    AND p4.uid = (SELECT MIN(uid) FROM users_games WHERE gid = games.id AND uid > p3.uid)
编辑加入免费版本,假设存在玩家id字段

SELECT
  games.id,
  MAX(CASE WHEN users_games.player_id = 1 THEN users_games.uid END)   AS p1_id,
  MAX(CASE WHEN users_games.player_id = 2 THEN users_games.uid END)   AS p2_id,
  MAX(CASE WHEN users_games.player_id = 3 THEN users_games.uid END)   AS p3_id,
  MAX(CASE WHEN users_games.player_id = 4 THEN users_games.uid END)   AS p4_id
FROM
  games
LEFT JOIN
  users_games
    ON users_games.gid = games.id
GROUP BY
  games.id
当然,9999999应该是最大可能的用户id-1。 这将前一个答案的子查询与一个大的分组查询进行交换


使用您的测试数据在MySQL 5.1 Ubuntu Lucid上进行测试。

仅仅

SELECT g.id, GROUP_CONCAT(u.login ORDER BY u.login), g.stime, g.etime
FROM games g,
users u,
users_games ug
WHERE ug.gid=g.id
AND ug.uid=u.id
GROUP BY g.id, g.stime, g.etime
如果你想要分数,只需添加一个函数,然后

SELECT g.id, GROUP_CONCAT(
     CONCAT(u.login, '=', get_score(u.login, g.id)) ORDER BY 1
     ), g.stime, g.etime
FROM games g,
users u,
users_games ug
WHERE ug.gid=g.id
AND ug.uid=u.id
GROUP BY g.id, g.stime, g.etime

我强烈建议您不要混用和加入语法。只需使用JOIN,它并没有过时20年…+1:我想这是可行的,就我个人而言,我回避它,因为你做的是半笛卡尔积。有4个玩家,你可以得到4*3*2*1=24条记录,然后你可以分组处理,得到一条记录。然后,您还需要重新加入users_games表4次,以获得每位玩家的分数。然而,我的答案中的相关子查询也有点不理想。测试这两种方法,看看您在性能和优雅性方面更喜欢哪一种,这符合您的兴趣。您真的需要IF吗?我不使用MySQL,但我也会这么做,除非所有值都为NULL,否则MIN不会返回NULL?这意味着MINugX.uid本身就足够了,因为您的左连接中有>谓词?冒着被否决的风险:如果我需要分数,我会使用类似“concatugx.uid”,ugx.score,将其转换为浮点,然后再次分解-在大多数DB主机上,IO比某些CPU周期要昂贵得多-我没有完全开发此功能-可能IF是不必要的,但这只是一个快速的攻击,我不会对串联建议投反对票。我会跑一英里然后哭。这是一个选择,但这是一个技术债务,如果可能的话,我真的会尽量避免。哇,太棒了。这当然解决了我的问题:-如果你能给出避免所有左连接的方法,我今天的教育就完成了。@obi-也测试一下尤金瑞克的答案。它可能比相关子查询版本更快。然后,如果您想加入其他用户表以获取用户元数据,等等?除非有人能证明备选方案不合适,否则我决不建议将多个值连接到单个字段中。
SELECT g.id, GROUP_CONCAT(
     CONCAT(u.login, '=', get_score(u.login, g.id)) ORDER BY 1
     ), g.stime, g.etime
FROM games g,
users u,
users_games ug
WHERE ug.gid=g.id
AND ug.uid=u.id
GROUP BY g.id, g.stime, g.etime