Select 来自 table 的所有条目和来自另一个 "logging" table 的最新条目
Select all entries from a table and the LATEST entries from another "logging" table
我已经尝试为我的问题准备一个 SQL Fiddle -
在多人文字游戏中,活动游戏存储在 table 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
);
并且很容易 select 所有游戏,例如 ID 为 1
的玩家参与的游戏:
SELECT * FROM words_games WHERE player1 = 1 OR player2 = 1;
但现在我还添加了一个 table 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 但它 returns all 移动,而我只需要 latest 移动每场比赛:
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)
更新:
实际上我需要一个 LEFT JOIN,因为有些游戏还没有玩家移动...
好的,让我们建立 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 which is a very handy sort of subquery. Among other things, it means if you end up using a different method for getting the most recent move per game (this question 有一组很好的替代品可供尝试),那么很容易换入,没有太多麻烦。
希望对您有所帮助!
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;
我已经尝试为我的问题准备一个 SQL Fiddle -
在多人文字游戏中,活动游戏存储在 table 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
);
并且很容易 select 所有游戏,例如 ID 为 1
的玩家参与的游戏:
SELECT * FROM words_games WHERE player1 = 1 OR player2 = 1;
但现在我还添加了一个 table 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 但它 returns all 移动,而我只需要 latest 移动每场比赛:
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)
更新:
实际上我需要一个 LEFT JOIN,因为有些游戏还没有玩家移动...
好的,让我们建立 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 which is a very handy sort of subquery. Among other things, it means if you end up using a different method for getting the most recent move per game (this question 有一组很好的替代品可供尝试),那么很容易换入,没有太多麻烦。
希望对您有所帮助!
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;