2011-12-19 17 views
7

2,3,4人のプレイヤーでプレイできるゲームがあるとします。私は自分のデータベース(MySQL 5.1)でこのようなゲームを以下の3つのテーブルで追跡します。私は、フィールドは自明であることを期待しています:同じテーブルを複数回左に結合する

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回は開始時刻と終了時刻です]ここ

が表に移入するためにいくつかのダミーデータである。

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 

これは私にすべての4つの座席が占有されたすべてのゲームを提供します(すなわち、上記ダミーデータ中のゲームID3のみ)。しかし、それは私が必要とするデータの部分集合に過ぎません。これは私に上記のダミーデータと14行を与える

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 

これは私の第二の試みです。

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行にしていますが、私はまだスプリアス多くのデータを持っている:私は最低UIDのプレーヤーのためのエントリにUG1を固定することで、エラーの一つのソースを排除しようとしました。私は問題を見ることができます。たとえば、ゲーム3では、ug1がユーザー102に固定されていて、まだug2をアンカーできるプレイヤーは3人います。等々。しかし、私はこの難解を解決する方法を見つけることができません - どのように私は最終的に正しい順序と数で選手と4行を出力するクエリを達成することができますか?

これは、私には他の状況で解決された問題である必要があります。ここですべての助けを感謝します。

+1

は、私は強くあなたをアドバイスします* ''と '' JOIN'の構文を混ぜてはいけません。ちょうど 'JOIN'を使って、それは20年古いですね... – MatBailie

答えて

16

1つの問題は、ユーザーをプレイヤー1,2,3または4と記述するフィールドがないことです。ただし、LEFT JOINごとに1人のプレーヤーしか参加しないようにする必要があります。そこあなたがusers_gamesに「player_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 

はLEFTなJOINすべて避ける選択肢ですが、それは根拠があるとして、この例ではうまく機能しますあなたはこのフィールドを追加することができない場合は、次のステップ...)


のために、それはより複雑になります。 (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) 


EDITは... player_idフィールドの存在を仮定し、9999999勿論

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

うわー、素晴らしい。それは確かに私の問題を解決します:-)すべての左の結合を避けるための方法を与えることができれば、今日の私の教育は完了します。 – ObiObi

+0

@ObiObi - EugenRieckの答えもテストしてください。相関サブクエリバージョンよりも高速かもしれません。 – 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 

を無料版に参加可能な最大ユーザIDであるべきである-1。 これは、大きなグループクエリに対する前の回答のサブクエリを交換します。

MySQL 5.1でテスト済みUbuntu Lucidがテストデータを使用しています。

+0

+1:これはうまくいくと思いますが、個人的には、デカルト製品を半分にしているので、私はそれから離れています。 (4人のプレーヤーでは、4 * 3 * 2 * 1 = 24のレコードが得られ、1レコードを得るためにグループで処理します。)また、 'users_games'テーブルに4回参加して各プレーヤーのスコア。しかし、私の答えでは相関関係のあるサブクエリも理想より少し劣っています。両方のアプローチをテストして、どちらがパフォーマンスと優雅さの面で好きであるかを確認することは、あなたの興味のためにあります。 – MatBailie

+0

本当にIF()が必要ですか?私はMySQLを使用していませんが、同じ値ですが、すべての値がNULLでない限り、MINはNULLを返しません。これは、 'LEFT JOIN'の'> '述語のために 'MIN(ugX.uid)'がそれ自身で十分であるべきことを意味しますか? – MatBailie

+0

downvoteを危険にさらしながら:もし私がスコアを必要とするなら、私は 'concat(ugx.uid、'。 '、ugx.score')のようなものを使って、これをfloatにキャストしてから、ほとんどのDBホストIOはいくつかのCPUサイクルよりもはるかに高価です –

0

が、それは単純ではないでしょうにちょうど.....

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

そして、他の 'Users'テーブルに参加してUserメタデータなどを取得したいのですか?誰かが選択肢が不適切だと実証できない限り、複数の値を1つのフィールドに連結することを決して推奨しません。 – MatBailie

関連する問題