仅向自己显示受处罚用户的聊天消息
Displaying chat messages of punished users only to themselves
我已经准备好了 a simple test case 我的问题。
在基于 PostgreSQL 14.2 的 an online word game for 2 players 中,当有人行为不端时,我将他们的“静音”列设置为“真”。
然后来自受罚用户的聊天消息应该对其他人隐藏。
除了被惩罚的用户自己 - 他们应该看到所有的聊天消息,这样他们就不会注意到自己被静音并且不会创建新的游戏帐户。
我已经为他们的头像做了这个技巧
所以我准备了一个简单的测试用例,这里是我的 4 个表:
CREATE TABLE words_users (
uid SERIAL PRIMARY KEY,
muted BOOLEAN NOT NULL DEFAULT false
);
CREATE TABLE words_social (
-- social network id
sid text NOT NULL CHECK (sid ~ '\S'),
-- social network type: 100 = Facebook, 200 = Google, etc.
social integer NOT NULL CHECK (0 < social AND social <= 256),
given text NOT NULL CHECK (given ~ '\S'),
uid integer NOT NULL REFERENCES words_users ON DELETE CASCADE,
PRIMARY KEY(sid, social)
);
CREATE TABLE words_games (
gid SERIAL PRIMARY KEY,
player1 integer REFERENCES words_users(uid) ON DELETE CASCADE NOT NULL CHECK (player1 <> player2),
player2 integer REFERENCES words_users(uid) ON DELETE CASCADE
);
CREATE TABLE words_chat (
cid BIGSERIAL PRIMARY KEY,
created timestamptz NOT NULL,
gid integer NOT NULL REFERENCES words_games ON DELETE CASCADE,
uid integer NOT NULL REFERENCES words_users ON DELETE CASCADE,
msg text NOT NULL
);
然后我用测试数据填充表格:
-- create 2 users: one is ok, while the other is muted (punished)
INSERT INTO words_users (uid, muted) VALUES (1, false), (2, true);
INSERT INTO words_social (sid, social, given, uid) VALUES ('abc', 100, 'Nice user', 1), ('def', 200, 'Bad user', 2);
-- put these 2 users into a game number 10
INSERT INTO words_games (gid, player1, player2) VALUES (10, 1, 2);
-- start chatting
INSERT INTO words_chat (gid, uid, created, msg) VALUES
(10, 1, CURRENT_TIMESTAMP + INTERVAL '1 min', 'Hi how are you doing?'),
(10, 1, CURRENT_TIMESTAMP + INTERVAL '2 min', 'I am a nice user'),
(10, 2, CURRENT_TIMESTAMP + INTERVAL '3 min', 'F*** ***!!'),
(10, 2, CURRENT_TIMESTAMP + INTERVAL '4 min', 'I am a bad user'),
(10, 1, CURRENT_TIMESTAMP + INTERVAL '5 min','Are you there??');
最后是我要改进的 SQL 功能:
CREATE OR REPLACE FUNCTION words_get_chat(
in_gid integer,
in_social integer,
in_sid text
) RETURNS TABLE (
out_mine integer,
out_msg text
) AS
$func$
-- TODO display messages by muted users only to themselves
SELECT
CASE WHEN c.uid = s.uid THEN 1 ELSE 0 END,
c.msg
FROM words_chat c
JOIN words_games g USING (gid)
JOIN words_social s ON s.uid IN (g.player1, g.player2)
WHERE c.gid = in_gid
AND s.social = in_social
AND s.sid = in_sid
ORDER BY c.CREATED ASC;
$func$ LANGUAGE sql;
SELECT words_get_chat(10, 100, 'abc') AS nice_user;
SELECT words_get_chat(10, 200, 'def') AS muted_user;
目前存储的功能显示所有聊天消息,但我想为其他所有人隐藏来自静音玩家的消息(在下面的屏幕截图中以红线显示):
请帮助我改进 SQL 功能,请注意,出于性能原因,我不想切换到 PL/pgSQL。
我理解正确,你可以尝试使用EXISTS
子查询来判断words_get_chat
功能中的静音用户按你的逻辑。
- 如果是nice用户我们需要判断他们想看哪条消息
- 如果用户不当,我们可以显示所有消息。
函数可能如下所示。
CREATE OR REPLACE FUNCTION words_get_chat(
in_gid integer,
in_social integer,
in_sid text
) RETURNS TABLE (
out_msg text
) AS
$func$
-- TODO display messages by muted users only to themselves
SELECT
c.msg
FROM words_chat c
JOIN words_games g USING (gid)
JOIN words_social s ON s.uid IN (g.player1, g.player2)
WHERE c.gid = in_gid
AND s.social = in_social
AND s.sid = in_sid
AND EXISTS (
SELECT 1
FROM words_users wu
WHERE wu.uid = s.uid AND
((muted = true) OR (muted = false AND c.uid = s.uid))
)
ORDER BY c.CREATED ASC;
受 Daniel 在 PostgreSQL 邮件列表中的子查询答案和帮助的启发,我开发了 a CTE and JOIN solution:
CREATE OR REPLACE FUNCTION words_get_chat(
in_gid integer,
in_social integer,
in_sid text
) RETURNS TABLE (
out_mine integer,
out_msg text
) AS
$func$
WITH myself AS (
SELECT uid
FROM words_social
WHERE social = in_social
AND sid = in_sid
)
SELECT
CASE WHEN c.uid = myself.uid THEN 1 ELSE 0 END,
c.msg
FROM words_chat c
JOIN myself ON TRUE
JOIN words_games g USING (gid)
JOIN words_users opponent ON (opponent.uid IN (g.player1, g.player2) AND opponent.uid <> myself.uid)
WHERE c.gid = in_gid
-- always show myself my own chat messages
AND (c.uid = myself.uid
-- otherwise only show messages by not muted opponents
OR NOT opponent.muted)
ORDER BY c.created ASC;
$func$ LANGUAGE sql;
另外,我从我的游戏逻辑中收到了很好的推荐to separate auth logic:
CREATE OR REPLACE FUNCTION words_get_uid(
in_social integer,
in_sid text
) RETURNS integer AS
$func$
SELECT uid
FROM words_social
WHERE social = in_social
AND sid = in_sid;
$func$ LANGUAGE sql IMMUTABLE;
CREATE OR REPLACE FUNCTION words_get_chat(
in_gid integer,
in_uid integer
) RETURNS TABLE (
out_mine integer,
out_msg text
) AS
$func$
SELECT
CASE WHEN c.uid = in_uid THEN 1 ELSE 0 END,
c.msg
FROM words_chat c
JOIN words_games g USING (gid)
JOIN words_users opponent ON (opponent.uid IN (g.player1, g.player2) AND opponent.uid <> in_uid)
WHERE c.gid = in_gid
-- always show myself my own chat messages
AND (c.uid = in_uid
-- otherwise only show messages by not muted opponents
OR NOT opponent.muted)
ORDER BY c.created ASC;
$func$ LANGUAGE sql;
SELECT words_get_chat(10, words_get_uid(100, 'abc')) AS nice_user;
SELECT words_get_chat(10, words_get_uid(200, 'def')) AS muted_user;
我已经准备好了 a simple test case 我的问题。
在基于 PostgreSQL 14.2 的 an online word game for 2 players 中,当有人行为不端时,我将他们的“静音”列设置为“真”。
然后来自受罚用户的聊天消息应该对其他人隐藏。
除了被惩罚的用户自己 - 他们应该看到所有的聊天消息,这样他们就不会注意到自己被静音并且不会创建新的游戏帐户。
我已经为他们的头像做了这个技巧
所以我准备了一个简单的测试用例,这里是我的 4 个表:
CREATE TABLE words_users (
uid SERIAL PRIMARY KEY,
muted BOOLEAN NOT NULL DEFAULT false
);
CREATE TABLE words_social (
-- social network id
sid text NOT NULL CHECK (sid ~ '\S'),
-- social network type: 100 = Facebook, 200 = Google, etc.
social integer NOT NULL CHECK (0 < social AND social <= 256),
given text NOT NULL CHECK (given ~ '\S'),
uid integer NOT NULL REFERENCES words_users ON DELETE CASCADE,
PRIMARY KEY(sid, social)
);
CREATE TABLE words_games (
gid SERIAL PRIMARY KEY,
player1 integer REFERENCES words_users(uid) ON DELETE CASCADE NOT NULL CHECK (player1 <> player2),
player2 integer REFERENCES words_users(uid) ON DELETE CASCADE
);
CREATE TABLE words_chat (
cid BIGSERIAL PRIMARY KEY,
created timestamptz NOT NULL,
gid integer NOT NULL REFERENCES words_games ON DELETE CASCADE,
uid integer NOT NULL REFERENCES words_users ON DELETE CASCADE,
msg text NOT NULL
);
然后我用测试数据填充表格:
-- create 2 users: one is ok, while the other is muted (punished)
INSERT INTO words_users (uid, muted) VALUES (1, false), (2, true);
INSERT INTO words_social (sid, social, given, uid) VALUES ('abc', 100, 'Nice user', 1), ('def', 200, 'Bad user', 2);
-- put these 2 users into a game number 10
INSERT INTO words_games (gid, player1, player2) VALUES (10, 1, 2);
-- start chatting
INSERT INTO words_chat (gid, uid, created, msg) VALUES
(10, 1, CURRENT_TIMESTAMP + INTERVAL '1 min', 'Hi how are you doing?'),
(10, 1, CURRENT_TIMESTAMP + INTERVAL '2 min', 'I am a nice user'),
(10, 2, CURRENT_TIMESTAMP + INTERVAL '3 min', 'F*** ***!!'),
(10, 2, CURRENT_TIMESTAMP + INTERVAL '4 min', 'I am a bad user'),
(10, 1, CURRENT_TIMESTAMP + INTERVAL '5 min','Are you there??');
最后是我要改进的 SQL 功能:
CREATE OR REPLACE FUNCTION words_get_chat(
in_gid integer,
in_social integer,
in_sid text
) RETURNS TABLE (
out_mine integer,
out_msg text
) AS
$func$
-- TODO display messages by muted users only to themselves
SELECT
CASE WHEN c.uid = s.uid THEN 1 ELSE 0 END,
c.msg
FROM words_chat c
JOIN words_games g USING (gid)
JOIN words_social s ON s.uid IN (g.player1, g.player2)
WHERE c.gid = in_gid
AND s.social = in_social
AND s.sid = in_sid
ORDER BY c.CREATED ASC;
$func$ LANGUAGE sql;
SELECT words_get_chat(10, 100, 'abc') AS nice_user;
SELECT words_get_chat(10, 200, 'def') AS muted_user;
目前存储的功能显示所有聊天消息,但我想为其他所有人隐藏来自静音玩家的消息(在下面的屏幕截图中以红线显示):
请帮助我改进 SQL 功能,请注意,出于性能原因,我不想切换到 PL/pgSQL。
我理解正确,你可以尝试使用EXISTS
子查询来判断words_get_chat
功能中的静音用户按你的逻辑。
- 如果是nice用户我们需要判断他们想看哪条消息
- 如果用户不当,我们可以显示所有消息。
函数可能如下所示。
CREATE OR REPLACE FUNCTION words_get_chat(
in_gid integer,
in_social integer,
in_sid text
) RETURNS TABLE (
out_msg text
) AS
$func$
-- TODO display messages by muted users only to themselves
SELECT
c.msg
FROM words_chat c
JOIN words_games g USING (gid)
JOIN words_social s ON s.uid IN (g.player1, g.player2)
WHERE c.gid = in_gid
AND s.social = in_social
AND s.sid = in_sid
AND EXISTS (
SELECT 1
FROM words_users wu
WHERE wu.uid = s.uid AND
((muted = true) OR (muted = false AND c.uid = s.uid))
)
ORDER BY c.CREATED ASC;
受 Daniel 在 PostgreSQL 邮件列表中的子查询答案和帮助的启发,我开发了 a CTE and JOIN solution:
CREATE OR REPLACE FUNCTION words_get_chat(
in_gid integer,
in_social integer,
in_sid text
) RETURNS TABLE (
out_mine integer,
out_msg text
) AS
$func$
WITH myself AS (
SELECT uid
FROM words_social
WHERE social = in_social
AND sid = in_sid
)
SELECT
CASE WHEN c.uid = myself.uid THEN 1 ELSE 0 END,
c.msg
FROM words_chat c
JOIN myself ON TRUE
JOIN words_games g USING (gid)
JOIN words_users opponent ON (opponent.uid IN (g.player1, g.player2) AND opponent.uid <> myself.uid)
WHERE c.gid = in_gid
-- always show myself my own chat messages
AND (c.uid = myself.uid
-- otherwise only show messages by not muted opponents
OR NOT opponent.muted)
ORDER BY c.created ASC;
$func$ LANGUAGE sql;
另外,我从我的游戏逻辑中收到了很好的推荐to separate auth logic:
CREATE OR REPLACE FUNCTION words_get_uid(
in_social integer,
in_sid text
) RETURNS integer AS
$func$
SELECT uid
FROM words_social
WHERE social = in_social
AND sid = in_sid;
$func$ LANGUAGE sql IMMUTABLE;
CREATE OR REPLACE FUNCTION words_get_chat(
in_gid integer,
in_uid integer
) RETURNS TABLE (
out_mine integer,
out_msg text
) AS
$func$
SELECT
CASE WHEN c.uid = in_uid THEN 1 ELSE 0 END,
c.msg
FROM words_chat c
JOIN words_games g USING (gid)
JOIN words_users opponent ON (opponent.uid IN (g.player1, g.player2) AND opponent.uid <> in_uid)
WHERE c.gid = in_gid
-- always show myself my own chat messages
AND (c.uid = in_uid
-- otherwise only show messages by not muted opponents
OR NOT opponent.muted)
ORDER BY c.created ASC;
$func$ LANGUAGE sql;
SELECT words_get_chat(10, words_get_uid(100, 'abc')) AS nice_user;
SELECT words_get_chat(10, words_get_uid(200, 'def')) AS muted_user;