简体   繁体   English

从一个表中选择所有条目,并从另一个“日志”表中选择“最新”条目

[英]Select all entries from a table and the LATEST entries from another “logging” table

I have tried to prepare an SQL Fiddle for my problem - 我试图为我的问题准备一个SQL Fiddle-

In a multiplayer word game active games are stored in the table words_games : 在多人文字游戏中,活动游戏存储在表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
);

And it is easy to select all games in which for example a player with id 1 participates: 选择所有ID为1的玩家参与的游戏很容易:

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

But now I have also added a table words_moves , which acts as a logging journal of player actions: 但是现在我还添加了一个表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 */
);

Now, when a user connects to my game server, I would like not only to send her all active games, but also the latest action (with the highest mid ) for each game. 现在,当用户连接到我的游戏服务器时,我不仅希望向她发送所有活动的游戏,而且还希望向她发送每个游戏的最新动作( mid最高)。

How to run such a join (or CTE) in one query please? 请问如何在一个查询中运行这样的联接(或CTE)?

I have tried the following INNER JOIN but it returns all moves, while I only need the latest move in each game: 我尝试了以下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: 更新:

Actually I would need a LEFT JOIN, because there can be games without any player moves yet... 实际上,我需要左加入,因为有些游戏可能还没有任何玩家移动。

Okay, let's build up the sql. 好的,让我们构建sql。 First, we need to figure out the most recent move for all of the games. 首先,我们需要找出所有游戏的最新动作。 There are lots of ways to do this, but let's try this one: 有很多方法可以做到这一点,但让我们尝试一下:

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

It's not the fastest way of doing it, but it's one of the easier to understand -- get every move from words_moves where the timestamp is the most recent. 这不是最快的方法,但是它是更容易理解的方法之一-从时间戳最近的words_moves中获取所有动作。

Now that we have that, we can build a query with it to get games plus 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;

If you're not familiar, the WITH there indicates a common table expression which is a very handy sort of subquery. 如果您不熟悉,则WITH表示公共表表达式 ,这是一种非常方便的子查询。 Among other things, it means if you end up using a different method for getting the most recent move per game ( this question has a good set of alternatives to try), then it's easy to swap in without too much trouble. 除其他外,这意味着如果您最终使用不同的方法来获取每场比赛的最新举动( 此问题有很多可供选择的替代方法可供尝试),那么很容易进行调入而不会带来太多麻烦。

Hope that helps! 希望有帮助!

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;

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM