对 SQL 的细微调整
您当前的 SQL,稍微重新格式化,是:
SELECT p.id, g.home, p.user_id, u.username, p.points, p1.points,
ABS(p.points - p1.points) rating_diff
FROM points p
JOIN games g ON p.game_id = p.id
JOIN users u ON p.user_id = u.id
JOIN points p1 ON p.game_id = p1.game_id AND p1.user_id = 1
WHERE u.id != 1
ORDER BY g.id, ABS(p.points - p1.points)
这应该接近你所追求的。您没有包括游戏日期标准。你说:
比较发生在 之后(games.date > now())
。
这意味着当游戏仍然安排在未来时进行比较(因为游戏的时间戳在当前时间戳之后,这通常被称为“在游戏发生之前”而不是“之后”。我认为你应该<
改为的>
。
SELECT p.id, g.home, p.user_id, u.username, p.points, a.points,
ABS(p.points - a.points) rating_diff
FROM points AS p
JOIN games AS g ON p.game_id = p.id
JOIN users AS u ON p.user_id = u.id
JOIN points AS a ON p.game_id = a.game_id AND a.user_id = 1
WHERE u.id != 1
AND g.`datetime` < NOW()
ORDER BY g.id, ABS(p.points - a.points)
我给“admin”记录赋予别名“a”而不是“p1”,保持单字母别名。datetime
鉴于它也是一种类型,我不情愿地使用反引号将其视为分隔标识符。我不确定这是否必要,但可能是。
您还没有根据日期差异计算加分;最好将这些信息记录在一个额外的表格中并对其进行适当的调整。但是,由于您的大纲方案仅建议“两天差,双倍积分”,因此很难知道设计应该是什么。特别是,如果天数差为 0 或 1,会发生什么?乘数会是分数吗?结果是否四舍五入?有附加项吗?等等。
TDQD — 测试驱动的查询设计
如果这没有产生您想要的数据,请进入 TDQD(测试驱动查询设计)模式。从一个简单的查询开始,并证明它获得了正确的信息。然后扩展它,证明每个查询也有效。
非管理员积分记录:
SELECT p.id, p.user_id, p.game_id, p.points
FROM points AS p
WHERE p.user_id != 1;
请注意,与您所拥有的相比,有一个直接的区别;由于我还没有从 Users 表中进行选择,因此该条件必须从 转换u.id != 1
为p.user_id != 1
. 这将发扬光大。它应该不会影响 SQL 的准确性,但是 TDQD 已经给出了稍微不同的查询设计。
具有相应管理员记录的非管理员积分记录:
SELECT p.id, p.user_id, p.game_id, p.points, a.user_id, a.points
FROM points AS p
JOIN points AS a ON p.game_id = a.game_id AND a.user_id = 1
WHERE p.user_id != 1;
也选择游戏信息:
(我很懒惰,没有datetime
用反引号括起来。)
SELECT p.id, p.user_id, p.game_id, p.points, a.points, g.home, g.datetime
FROM points AS p
JOIN points AS a ON p.game_id = a.game_id AND a.user_id = 1
JOIN games AS g ON p.game_id = g.id
WHERE p.user_id != 1;
仅选择历史游戏信息:
SELECT p.id, p.user_id, p.game_id, p.points, a.points, g.home, g.datetime
FROM points AS p
JOIN points AS a ON p.game_id = a.game_id AND a.user_id = 1
JOIN games AS g ON p.game_id = g.id
WHERE p.user_id != 1
AND g.datetime < NOW();
添加用户信息:
SELECT p.id, p.user_id, p.game_id, p.points, a.points, g.home, g.datetime, u.username
FROM points AS p
JOIN points AS a ON p.game_id = a.game_id AND a.user_id = 1
JOIN games AS g ON p.game_id = g.id
JOIN users AS u ON p.user_id = u.id
WHERE p.user_id != 1
AND g.datetime < NOW();
我可以将 WHERE 条件切换回u.id != 1
现在,但这样做没有任何好处。
点数
标准 SQL 支持 INTERVAL 类型,例如 INTERVAL DAY TO MINUTE。这表示经过的时间量,并且会使点表的编码相对简单。请注意,这对于 MySQL 来说是假设的——我们稍后会处理现实情况。
CREATE TABLE Extra
(
LoOffsetFromGameTime INTERVAL DAY TO MINUTE NOT NULL,
HiOffsetFromGameTime INTERVAL DAY TO MINUTE NOT NULL,
Points INTEGER NOT NULL,
CHECK(LoOffsetFromGameTime < HiOffSetFromGameTime)
);
INSERT INTO Extra VALUES ('-99 23:59', '-0 00:45', 0);
INSERT INTO Extra VALUES ('-0 00:45', '-0 00:24', 1);
INSERT INTO Extra VALUES ('-0 00:24', '0 00:00', 2);
INSERT INTO Extra VALUES ('0 00:00', '1 00:00', 4);
INSERT INTO Extra VALUES ('1 00:00', '2 00:00', 6);
INSERT INTO Extra VALUES ('2 00:00', '3 00:00', 8);
...
INSERT INTO Extra VALUES ('15 00:00', '99 23:59', 34);
范围是“开闭”;包含低值,排除高值。这意味着不能使用 BETWEEN AND 运算符;它实现了“开放-开放”范围,包括两个端点。
如果需要,可以增加“-99 天”和“+99 天”的值;INTERVAL DAY TO MINUTE 的日期部分的默认位数为 2,但如果需要更多,可以指定更多。下面的查询假设游戏时间和进入时间之间的差异总是在表中的一行中。
也选择加分
假设我们可以使用这个表,那么加分的查询就变成了:
SELECT p.id, p.user_id, p.game_id, p.points, a.points, g.home, g.datetime, u.username
FROM points AS p
JOIN points AS a ON p.game_id = a.game_id AND a.user_id = 1
JOIN games AS g ON p.game_id = g.id
JOIN users AS u ON p.user_id = u.id
JOIN extra AS x
ON CAST(g.datetime - p.date AS INTERVAL DAY TO MINUTE) >= x.LoOffsetFromGameTime
AND CAST(g.datetime - p.date AS INTERVAL DAY TO MINUTE) < x.HiOffsetFromGameTime
WHERE p.user_id != 1
AND g.datetime < NOW();
那重复的表达相当笨拙;我们应该将之前的主查询变成一个子查询,它选择值并为其命名,然后在与 Extra 表的连接中使用它:
SELECT x.*, d.*
FROM extra AS x
JOIN (SELECT p.id, p.user_id, p.game_id, p.usr_points, a.adm_points, g.home,
g.datetime, u.username,
CAST(g.datetime - NOW() AS INTERVAL DAY TO MINUTE) AS Offset
FROM points AS p
JOIN points AS a ON p.game_id = a.game_id AND a.user_id = 1
JOIN games AS g ON p.game_id = g.id
JOIN users AS u ON p.user_id = u.id
WHERE p.user_id != 1
AND g.datetime < NOW()
) AS d
ON d.Offset >= x.LoOffsetFromGameTime
AND d.Offset < x.HiOffsetFromGameTime;
如果我正确解释了条件并且没有反转任何内容,那么这应该可以为您提供所需的信息。
但是 MySQL 不支持 INTERVAL 类型
我将假设“切换到支持 INTERVAL 类型的 DBMS”不是一个选项。
从逻辑上讲,运行时最简单的机制是将两个日期值之间的差异视为适当的整数分钟数。然后,您可以将 INTERVAL DAY TO MINUTE 编码为适当的分钟数,但表的数据条目更混乱:
CREATE TABLE Extra
(
LoOffsetFromGameTime INTEGER NOT NULL,
HiOffsetFromGameTime INTEGER NOT NULL,
Points INTEGER NOT NULL,
CHECK(LoOffsetFromGameTime < HiOffSetFromGameTime)
);
INSERT INTO Extra VALUES (-2000000, -45, 0);
INSERT INTO Extra VALUES ( -45, -24, 1);
INSERT INTO Extra VALUES ( -24, 0, 2);
INSERT INTO Extra VALUES ( 0, +1440, 4);
INSERT INTO Extra VALUES ( +1440, +2880, 6);
INSERT INTO Extra VALUES ( +2880, +4320, 8);
...
INSERT INTO Extra VALUES ( +21600, +2000000, 34);
我使用 2,000,000 作为方便的“大”数字;您可以指定 ±2,147,483,647 或任何其他合适的范围。
请注意,您应该验证 Extra 表的连续覆盖范围并且数据中没有重叠。我没有指定主键,因为没有特别好的选项可供选择。您确实希望将两个偏移列的开闭范围作为主键(对连续性和非重叠有约束),但这在 SQL 中无法表达(或者,不容易,也不是作为主键约束) .
然后,您只需将游戏时间和 NOW() 之间的差异写为适当的分钟数。实际上,使用秒数可能更容易,尽管它会使表的数据输入更加复杂。OTOH,您不会经常对表格进行编码,并且您始终可以安排使用一个采用方便表示并将其转换为秒的过程。
但是,TIMESTAMPDIFF可能完全符合我们的需要。我还没有研究过它的返回类型的含义。还有许多其他函数可供选择,在其中的某个地方,您将能够获得一个方便的表达式,该表达式返回游戏时间和用户进入时间之间的差异,作为可以干净地连接的适当值额外表,产生所需的答案。
添加额外积分(更新 3)
要添加的点数是x.points
if d.usr_points = d.adm_points
,所以我们可以简单地将其写在选择列表中:
SELECT CASE WHEN d.usr_points = d.adm_points THEN x.points ELSE 0 END AS added_points,
x.*, d.*
FROM ...