2

我需要创建非常困难的 SQL 查询,但我自己无法处理。也许有人可以帮助我或提出一些不同的方法。

我有三张桌子: 表结构

我需要在页面上显示需要下一点的表格:

  • 将每个用户积分与管理员积分进行比较 (points.user_id=1)
  • 如果积分相等,则用户根据积分保存日期与比赛举办日期之间的差值获得积分。如果分数不同,则不添加分数。
  • 我可以设置当上面提到的两个日期之间的差异例如 2 天、3 天等时,用户可以获得多少积分(如果(games.date - points.date = 2days{points=points*2} 或类似)
  • 比较发生在 (games.date > now())

更新:

$points = array();

$all_ifo = mysql_query("SELECT p.id, p.user_id, p.game_id, p.points, 
ABS(p.points - a.points) rating_diff, 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();") or die(mysql_error());

while ($info = mysql_fetch_assoc($all_ifo)){
    $points['' . $info['username'] . ''] += $info['rating_diff'];
    echo $info['p.id'];
}
asort($points);

echo "<table class='tabelid'><th>Test</th>";
foreach ($points as $key => $value) {
    $i++;
    echo "<tr><td>".$i.".</td><td style='width:250px;'>" . 
    ucfirst($key) . "</td><td>" . $value . "</td></tr>";
}
echo "</table>";

更新 2:

所以有人建议,为了计分,我需要一个单独的表格。但这很复杂:

Game saved in 25-45 min after game start - 1 point
Game saved in 0 - 24 min after game start - 2 points
Game saved in on the same day but before the game - 4 points 
Game saved in 1 day before - 6 points
Game saved in 2 days before - 8 points
..
Game saved in 15 days before - 34 points

那么有什么建议应该如何完成表格以便之后轻松地将其实施到系统中?以及这个 rating_diff 如何获得正确的点数?

更新 3:

$points = array();

$all_ifo = mysql_query("SELECT x.*, d.*
  FROM Extra AS x
  JOIN (SELECT p.id, p.user_id, p.game_id, p.points, p.date, a.points adminpoints, g.home,
               g.datetime, u.username, TIMESTAMPDIFF(MINUTE, g.datetime, p.date) 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;") or die(mysql_error());

while ($info = mysql_fetch_assoc($all_ifo)){
    $points['' . $info['username'] . ''] += $info['Offset'];
    echo $info['p.id'];
    }

arsort($points);

echo "<table class='tabelid'><th>Test</th>";
foreach ($points as $key => $value) {
    $i++;
    echo "<tr><td>".$i.".</td><td style='width:250px;'>" . ucfirst($key) . "</td><td>" . $value . "</td></tr>";

}
echo "</table>";

对于输出,我得到保存时间和实际游戏时间之间的时间差:

Test
1.  User1   1851
2.  User2   -502

但我不知道如何将它与 Extra table 进行比较,从而获得正确的分数。当然,只有当相同 game_id 的积分(0、1 或 2)与管理员的积分(0、1 或 2)相同时,才应添加积分。

4

1 回答 1

3

对 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 != 1p.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.pointsif 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 ...
于 2012-06-03T14:50:37.960 回答