0

Я попытался подготовить SQL Fiddle для моей проблемы -Выберите все записи из таблицы и последние записи из другой «лесозаготовительной» таблицы

в слове игры многопользовательской активные игры хранятся в таблице words_games:

CREATE TABLE words_games (
     gid SERIAL PRIMARY KEY,    /* game id */ 
     created timestamptz NOT NULL, 

     player1 integer REFERENCES words_users(uid) ON DELETE CASCADE NOT NULL, 
     player2 integer REFERENCES words_users(uid) ON DELETE CASCADE, 

     played1 timestamptz, 
     played2 timestamptz, 

     score1 integer NOT NULL CHECK(score1 >= 0), 
     score2 integer NOT NULL CHECK(score2 >= 0), 

     hand1 varchar[7] NOT NULL, 
     hand2 varchar[7] NOT NULL, 
     pile varchar[116] NOT NULL, 

     letters varchar[15][15] NOT NULL, 
     values integer[15][15] NOT NULL, 
     bid integer NOT NULL REFERENCES words_boards ON DELETE CASCADE 
); 

И легко выбрать все игры, в которых, например, игрока с ID 1 участвует:

SELECT * FROM words_games WHERE player1 = 1 OR player2 = 1; 

Но теперь я также добавил таблицу words_moves, которая действует как журнале лесозаготовительного от действий игрока:

CREATE TYPE words_action AS ENUM ('play', 'skip', 'swap', 'resign'); 

CREATE TABLE words_moves (
     mid SERIAL PRIMARY KEY,    /* move id */ 
     action words_action NOT NULL, 
     gid integer NOT NULL REFERENCES words_games ON DELETE CASCADE, 
     uid integer NOT NULL REFERENCES words_users ON DELETE CASCADE, 
     played timestamptz NOT NULL, 
     tiles jsonb NULL, 
     score integer NULL CHECK(score > 0) /* score awarded in that move */ 
); 

Теперь, когда пользователь подключается к моему игровому серверу, я хотел бы не только послать ей всю активные игры, но и последнее действие (с самым высоким mid) для каждой игры.

Как запустить такое соединение (или CTE) в одном запросе, пожалуйста?

Я попытался следующий INNER JOIN, но она возвращает все ходы, в то время как мне нужно только последний ход в каждой игре:

SELECT 
    g.gid, 
    EXTRACT(EPOCH FROM g.created)::int AS created, 
    g.player1, 
    COALESCE(g.player2, 0) AS player2, 
    COALESCE(EXTRACT(EPOCH FROM g.played1)::int, 0) AS played1, 
    COALESCE(EXTRACT(EPOCH FROM g.played2)::int, 0) AS played2, 
    ARRAY_TO_STRING(g.hand1, '') AS hand1, 
    ARRAY_TO_STRING(g.hand2, '') AS hand2, 
    -- g.letters, 
    -- g.values, 
    m.action, 
    m.tiles                                         
FROM words_games g INNER JOIN words_moves m                                   
    ON g.gid = m.gid                                         
    AND (g.player1 = m.uid OR g.player2 = m.uid)                                  
    AND (g.player1 = 1 OR g.player2 = 1)                                    
ORDER BY g.gid; 


    gid | created | player1 | player2 | played1 | played2 | hand1 | hand2 | action |                                           tiles                                           
    -----+------------+---------+---------+------------+------------+---------+---------+--------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 
     1 | 1471794994 |  1 |  2 | 1471868012 | 1471810486 | ПЕАЯСАС | ЖИОБАЯС | play | [{"col": 7, "row": 10, "value": 1, "letter": "Н"}, {"col": 7, "row": 8, "value": 2, "letter": "К"}, {"col": 7, "row": 9, "value": 1, "letter": "И"}, {"col": 7, "row": 7, "value": 2, "letter": "С"}] 
     1 | 1471794994 |  1 |  2 | 1471868012 | 1471810486 | ПЕАЯСАС | ЖИОБАЯС | play | [{"col": 7, "row": 14, "value": 2, "letter": "К"}, {"col": 7, "row": 13, "value": 1, "letter": "Н"}, {"col": 7, "row": 11, "value": 3, "letter": "У"}, {"col": 7, "row": 12, "value": 2, "letter": "П"}] 
     1 | 1471794994 |  1 |  2 | 1471868012 | 1471810486 | ПЕАЯСАС | ЖИОБАЯС | play | [{"col": 6, "row": 2, "value": 2, "letter": "П"}, {"col": 6, "row": 3, "value": 1, "letter": "О"}, {"col": 6, "row": 4, "value": 1, "letter": "Е"}, {"col": 6, "row": 5, "value": 5, "letter": "Ж"}, {"col": 6, "row": 6, "value": 5, "letter": "Ы"}, {"col": 6, "row": 7, "value": 2, "letter": "П"}, {"col": 6, "row": 8, "value": 5, "letter": "Ы"}] 
     2 | 1471795037 |  1 |  2 | 1471806484 | 1471865696 | КЙВГКСМ | ЯРХЖИМН | swap | "А" 
     2 | 1471795037 |  1 |  2 | 1471806484 | 1471865696 | КЙВГКСМ | ЯРХЖИМН | play | [{"col": 7, "row": 10, "value": 5, "letter": "Ы"}, {"col": 7, "row": 9, "value": 2, "letter": "Д"}, {"col": 7, "row": 8, "value": 1, "letter": "А"}, {"col": 7, "row": 7, "value": 2, "letter": "Л"}] 
    (5 rows) 

UPDATE:

На самом деле я бы нужно ЛЕВОЕ ПРИСОЕДИНЕНИЕ, потому что могут быть игры без каких-либо движений игроков ...

+0

Чтобы обнаружить последний ход, вам нужно разместить временную метку в файле регистрации. (серийный номер 'mid' * может также функционировать как таковой, но для меня это выглядит как плохая привычка) – joop

ответ

1

Хорошо, давайте создадим sql. Во-первых, нам нужно выяснить самый последний ход для всех игр. Есть много способов сделать это, но давайте попробуем это один:

SELECT * 
FROM words_moves wm1 
WHERE 
    played = (SELECT max(played) 
      FROM words_moves wm2 
      WHERE wm1.gid = wm2.gid); 

Это не самый быстрый способ сделать это, но это одна из тем легче понять - получить каждый шаг от words_moves, где временная отметка Самый последний.

Теперь, когда у нас есть, что мы можем построить запрос с ним, чтобы получить игры плюс шаги:

WITH last_moves AS (
    SELECT * 
    FROM words_moves wm1 
    WHERE 
    played = (SELECT max(played) 
       FROM words_moves wm2 
       WHERE wm1.gid = wm2.gid)) 
SELECT * 
FROM words_games wg 
    LEFT JOIN last_moves lm 
    ON (wg.gid = lm.gid) 
WHERE 
    player1 = 1 OR 
    player2 = 1; 

Если вы не знакомы, то WITH есть указывает на common table expression, который является очень удобным рода подзапрос. Между прочим, это означает, что если вы в конечном итоге используете другой метод для получения последнего хода за игру (this question имеет хороший набор альтернатив, чтобы попробовать), тогда легко переключаться без особых проблем.

Надеюсь, что это поможет!

+0

Благодарим вас за полезное понимание. После некоторого размышления я решил добавить столбец 'last_mid' в' words_games' - и обновлять его всякий раз, когда я 'INSERT' записываю новую запись« log »в таблицу' words_moves' ('INSERT .... RETURNING mid') –

+0

Также Я думаю, вы имеете в виду 'WHERE wm1.gid = wm2.gid и wm1.uid = 1' –

+1

Я не хотел ограничивать слова' words_moves.uid', потому что я предполагаю, что вы хотите показать последний ход, даже если он был противником. Вы могли бы добавить что-то вроде 'AND (wm1.uid = 1 ИЛИ wm2.uid = 1)', если вы хотите пойти в этом направлении. – jmelesky

1
SELECT g.gid 
    , EXTRACT(EPOCH FROM g.created)::int AS created 
    , g.player1 
    , COALESCE(g.player2, 0) AS player2 
    , COALESCE(EXTRACT(EPOCH FROM g.played1)::int, 0) AS played1 
    , COALESCE(EXTRACT(EPOCH FROM g.played2)::int, 0) AS played2 
    , ARRAY_TO_STRING(g.hand1, '') AS hand1 
    , ARRAY_TO_STRING(g.hand2, '') AS hand2 
    , m.action 
    , m.tiles 
FROM words_games g 
LEFT JOIN words_moves m 
    ON g.gid = m.gid 
     -- this is redundant: m.gid is a FK 
     -- AND (g.player1 = m.uid OR g.player2 = m.uid) 
    AND NOT EXISTS (-- suppress all-but-the-last 
     SELECT * FROM words_moves nx 
     WHERE nx.gid = g.gid -- Same game 
      -- AND nx.mid > m.mid -- but a higher moveid 
            -- (assuming ascending move_ids) 
      -- or: you could use m.played, if that is ascending 
     AND nx.played > m.played 
     ) 
WHERE (g.player1 = 1 OR g.player2 = 1) 
ORDER BY g.gid; 
Смежные вопросы