2011-12-19 36 views
7

Giả sử tôi có một trò chơi có thể chơi bởi 2, 3 hoặc 4 người chơi. Tôi theo dõi một trò chơi như vậy trong cơ sở dữ liệu của tôi (MySQL 5.1) trong ba bảng, được đưa ra dưới đây. Tôi hy vọng rằng các lĩnh vực là tự giải thích:Còn lại cùng một bàn nhiều lần

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

[Hai lần được theo dõi trong bảng trò chơi được bắt đầu và kết thúc thời gian]

Dưới đây là một số dữ liệu giả để cư trú trong bảng:

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

Bây giờ, tôi cần phải tạo ra một báo cáo theo định dạng sau:

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] 

Đó là, một báo cáo hiển thị tất cả những người chơi đã chơi trò chơi trong cùng một hàng. Tôi cũng cần điểm số của họ và một số thông tin khác từ bảng người dùng, nhưng đó là giai đoạn 2. :-)

tôi bắt đầu với điều này:

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 

này mang lại cho tôi tất cả các trò chơi mà tất cả bốn chỗ ngồi đã bị chiếm đóng (nghĩa là chỉ có ID trò chơi 3 trong dữ liệu giả ở trên). Nhưng đó chỉ là một tập con của dữ liệu tôi cần.

Đây là nỗ lực thứ hai của tôi:

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 

này mang lại cho tôi 14 hàng với dữ liệu giả trên. Tôi cố gắng để loại bỏ một nguồn lỗi bằng cách neo ug1 đến việc giao cho các cầu thủ thấp nhất-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 

Bây giờ tôi xuống đến 9 dòng, nhưng tôi vẫn còn có rất nhiều dữ liệu giả mạo. Tôi có thể thấy vấn đề - ví dụ trong trò chơi 3, với ug1 được neo vào người dùng 102, vẫn có ba người chơi mà ug2 có thể được neo. Và cứ thế. Nhưng tôi không thể tìm ra cách để giải quyết câu hỏi hóc búa này - làm thế nào để cuối cùng tôi có thể đạt được một truy vấn sẽ xuất ra 4 hàng với người chơi theo đúng thứ tự và số?

Điều này dường như với tôi phải là vấn đề được giải quyết trong các ngữ cảnh khác. Sẽ đánh giá cao tất cả sự giúp đỡ ở đây.

+1

tôi khuyên bạn * không * trộn cú pháp ',' và 'JOIN'. Chỉ cần sử dụng 'JOIN', nó không phải là 20 năm lỗi thời ... – MatBailie

Trả lời

16

Một vấn đề bạn gặp phải là bạn không có trường mô tả người dùng là Người chơi 1, 2, 3 hoặc 4. Tuy nhiên, bạn cần đảm bảo rằng chỉ một người chơi được tham gia mỗi LEFT JOIN.

Nếu bạn thêm một lĩnh vực "player_id" để users_games, nó trở nên tầm thường ...

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 

lựa chọn thay thế mà tránh tất cả các LEFT JOIN, nhưng ví dụ này phục vụ cũng như nó là cơ sở cho bước tiếp theo ...)


Nếu bạn không thể thêm lĩnh vực này, nó trở nên phức tạp hơn. (SQL Server, Oracle, v.v., có thể ủy quyền trường player_id này bằng ROW_NUMBER(), MySQL không thể.)

Thay vào đó, bạn cần truy vấn con tương quan để xác định 'trình phát tiếp theo'.

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 THAM GIA phiên bản miễn phí, giả sử có mặt của lĩnh vực 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, tuyệt vời. Điều đó chắc chắn giải quyết vấn đề của tôi :-) Nếu bạn có thể cung cấp cho các phương pháp để tránh tất cả các tham gia trái, giáo dục của tôi cho ngày hôm nay sẽ được hoàn thành. – ObiObi

+0

@ObiObi - Kiểm tra câu trả lời của EugenRieck. Nó có thể nhanh hơn phiên bản truy vấn phụ tương quan. – 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 

ofcourse 9999999 nên là có thể id người dùng tối đa -1. Thao tác truy vấn con của câu trả lời trước đó với truy vấn nhóm lớn.

Thử nghiệm trên MySQL 5.1 Ubuntu Lucid với dữ liệu thử nghiệm của bạn.

+0

+1: Tôi đoán rằng điều này làm việc, cá nhân tôi tránh xa nó bởi vì bạn làm một sản phẩm một nửa Cartesian. (Với 4 người chơi, bạn sẽ nhận được 4 * 3 * 2 * 1 = 24 bản ghi, sau đó bạn xử lý trong một nhóm để lấy một bản ghi.) Sau đó, bạn cũng cần phải tham gia lại vào bảng 'users_games' 4 lần để có được mỗi người chơi ghi bàn. BAO GIỜ, các truy vấn con tương quan trong câu trả lời của tôi cũng ít hơn một chút so với lý tưởng. Nó sẽ được quan tâm của bạn để kiểm tra cả hai phương pháp tiếp cận để xem mà bạn thích về hiệu suất và sang trọng. – MatBailie

+0

Bạn có thực sự cần IF() không? Tôi không sử dụng MySQL, nhưng tôi sẽ có mặc dù nó là như nhau, trong đó MIN không trả về NULL, trừ khi tất cả các giá trị là NULL? Điều đó có nghĩa là 'MIN (ugX.uid)' phải đủ tự do do thuộc tính '>' trong 'LEFT JOIN' của bạn? – MatBailie

+0

trong khi rủi ro một downvote: Nếu tôi cần điểm số, tôi muốn sử dụng một cái gì đó như 'concat (ugx.uid,'. ', Ugx.score'), đúc này cho một phao cho họ min và sau đó phân hủy nó một lần nữa - trên hầu hết các máy chủ DB một IO đắt hơn nhiều so với một số chu kỳ CPU –

0

Nó sẽ không thể đơn giản hơn chỉ cần .....

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 

Và nếu bạn muốn điểm số, chỉ cần thêm một chức năng, sau đó ...

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

Và sau đó nếu bạn muốn tham gia vào các bảng 'Người dùng' khác để nhận siêu dữ liệu Người dùng, v.v ...? Trừ khi một số người có thể chứng minh rằng các lựa chọn thay thế không phù hợp, tôi sẽ không bao giờ * đề xuất * ghép nối nhiều giá trị vào một trường đơn lẻ. – MatBailie

Các vấn đề liên quan