Соединение левой и той же таблицы несколько раз - PullRequest
9 голосов
/ 19 декабря 2011

Предположим, у меня есть игра, в которую могут играть 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

Это дает мне все игры, где всечетыре места были заняты (т. е. только 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 строки с игроками в правильном порядке и количестве?

Мне кажется, это должно быть решенной проблемой вдругие контексты.Буду признателен за любую помощь здесь.

Ответы [ 3 ]

18 голосов
/ 19 декабря 2011

Одна из проблем, с которой вы столкнулись, состоит в том, что у вас нет полей, которые описывают пользователя как Игрока 1, 2, 3 или 4. Тем не менее, вам нужно убедиться, что в ЛЕВОЕ СОЕДИНЕНИЕ присоединяется только один игрок.

Если вы добавите поле «player_id» в users_games, оно станет тривиальным ...

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

Есть альтернативы , которые избегают всех ЛЕВЫХ СОЕДИНЕНИЙ, но этот пример служит хорошим основанием для следующего шага ...)

1010 *
*

Если вы не можете добавить это поле, оно становится более сложным. (SQL Server, Oracle и т. Д. Могут проксировать это поле player_id с помощью ROW_NUMBER (), 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)


РЕДАКТИРОВАТЬ Присоединиться к бесплатной версии, при условии наличия поля player_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
4 голосов
/ 19 декабря 2011
SELECT games.*,
IF(min(ifnull(ug1.uid,9999999))=9999999,null,ug1.uid) AS user1,
IF(min(ifnull(ug2.uid,9999999))=9999999,null,ug2.uid) AS user2,
IF(min(ifnull(ug3.uid,9999999))=9999999,null,ug3.uid) AS user3,
IF(min(ifnull(ug4.uid,9999999))=9999999,null,ug4.uid) AS user4
FROM games
LEFT JOIN users_games AS ug1 ON ug1.gid=games.id
LEFT JOIN users_games AS ug2 ON ug2.gid=games.id AND ug2.uid>ug1.uid
LEFT JOIN users_games AS ug3 ON ug3.gid=games.id AND ug3.uid>ug2.uid
LEFT JOIN users_games AS ug4 ON ug4.gid=games.id AND ug4.uid>ug3.uid
GROUP BY games.id

ofcourse 9999999 должен быть максимально возможным идентификатором пользователя -1.При этом подзапросы предыдущего ответа сравниваются с большим групповым запросом.

Протестировано на MySQL 5.1 Ubuntu Lucid с вашими тестовыми данными.

0 голосов
/ 19 декабря 2011

Разве не было бы проще просто .....

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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...