2011-12-19 20 views
7

Załóżmy, że mam grę, w którą może grać 2, 3 lub 4 graczy. Śledzę taką grę w mojej bazie danych (MySQL 5.1) w trzech tabelach, podanych poniżej. Mam nadzieję, że pola są oczywiste:Lewe łączenie tego samego stołu wiele razy

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); 

[Dwa razy śledzone w tabeli gry są czas rozpoczęcia i zakończenia]

Oto niektóre fikcyjne dane do wypełnienia tabel:

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); 

teraz muszę przedstawić sprawozdanie w następującym formacie:

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] 

Oznacza to, raport, który pokazuje wszystkich graczy, którzy grali w grę w tym samym rzędzie. Muszę również swoje wyniki i niektóre inne informacje z tabeli użytkowników, ale to jest faza 2. :-)

zacząłem z tym:

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 

To daje mi wszystkie gry, w których zostały zajęte wszystkie cztery miejsca (tj. tylko identyfikator gry 3 w powyższych danych fikcyjnych). Ale to tylko podzbiór danych, których potrzebuję.

To jest moja druga próba:

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 

To daje mi 14 wiersze z powyższych danych manekina. Starałem się wyeliminować jedno źródło błędu poprzez zakotwiczenie UG1 wejściem do najniższego UID gracza:

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 

Teraz jestem w dół do 9 wierszy, ale mam jeszcze wiele fałszywych danych. Widzę problem - na przykład w grze 3, z ug1 zakotwiczonym dla użytkownika 102, wciąż jest trzech graczy, na których można zakotwić ug2. I tak dalej. Ale nie mogę znaleźć sposobu na rozwiązanie tej zagadki - w jaki sposób mogę ostatecznie uzyskać zapytanie, które wyświetli 4 wiersze z graczami we właściwej kolejności i liczbie?

To wydaje mi się, że powinienem rozwiązać problem w innych kontekstach. Doceniam wszelką pomoc tutaj.

+1

Zdecydowanie zalecamy, aby * nie * miesza składnię ',' i 'JOIN'. Po prostu użyj 'JOIN', to nie jest już 20 lat nieaktualne ... – MatBailie

Odpowiedz

16

Masz jeden problem, że nie masz pól, które opisują użytkownika jako Gracza 1, 2, 3 lub 4. Jednak musisz upewnić się, że tylko jeden gracz jest połączony na LEWY DOŁĄCZ.

Jeżeli „player_id” pole dodasz do users_games, staje się trywialne ...

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 

Nie alternatywy, które trzeba unikać wszystkich LEFT JOIN, ale służy także przykłady to podstawa do następnego etapu ...)


Jeśli nie możesz dodać tego pola, staje się bardziej złożona. (SQL Server, Oracle itp. Może pośredniczyć w tym polu player_id za pomocą ROW_NUMBER(), MySQL nie może.)

Zamiast tego potrzebne są skorelowane zapytania dodatkowe w celu identyfikacji "następnego gracza".

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) 


EDIT DARMOWA wersji zakładając obecności pola 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 
+0

Wow, fantastycznie. To z pewnością rozwiązuje mój problem :-) Jeśli możesz podać metodę unikania wszystkich lewych złączeń, moja edukacja na dzisiaj byłaby kompletna. – ObiObi

+0

@ObiObi - Sprawdź także odpowiedź EugenRiecka. Może być szybszy niż skorelowana wersja pod zapytania. – MatBailie

4
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 

oczywiście 9999999 powinna być maksymalna możliwa identyfikator użytkownika 1. Umożliwia to podzapytanie poprzedniej odpowiedzi na duże zapytanie grupujące.

Testowany na MySQL 5.1 Ubuntu Lucid z danymi testowymi.

+0

+1: Domyślam się, że to działa, osobiście odrzuciłem go, ponieważ robisz pół kartezjański produkt. (Z 4 graczami otrzymujesz 4 * 3 * 2 * 1 = 24 rekordy, które następnie przetwarzasz w grupie, aby zdobyć jedną płytę.) Następnie musisz ponownie dołączyć do stołu 'users_games', 4 razy, aby uzyskać dostęp do każdego gracza. wynik. JEDNAK, skorelowane pod-zapytania w mojej odpowiedzi są również nieco mniej niż idealne. W twoim interesie jest przetestowanie obu podejść, aby zobaczyć, które preferujesz pod względem wydajności i elegancji. – MatBailie

+0

Czy naprawdę potrzebujesz IF()? Nie używam MySQL, ale byłbym jednak taki sam, w tym, że MIN nie zwraca wartości NULL, chyba że wszystkie wartości są NULL? Co oznaczałoby, że 'MIN (ugX.uid)' powinno wystarczyć na jego podstawie z '' 'predykatem w twoim' LEFT JOIN's? – MatBailie

+0

ryzykując downvote: Jeśli potrzebuję wyników, użyłbym czegoś takiego jak "concat (ugx.uid,". ', Ugx.score'), odrzuć to do float dla nich min, a następnie ponownie rozpakuj - na większość serwerów DB IO jest znacznie droższa niż niektóre cykle CPU. –

0

Czy nie byłoby prościej po prostu .....

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 

A jeśli chcesz wyniki, wystarczy dodać funkcję, a następnie ...

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

A następnie, jeśli chcesz dołączyć do innych tabel "Użytkownicy", aby uzyskać metadane użytkownika, itp.? Dopóki ktoś nie będzie w stanie zademonstrować, że alternatywy są nieodpowiednie, nigdy nie * polecam * łączenia wielu wartości w jedno pole. – MatBailie

Powiązane problemy