在一列上的SQL聚合给出另一列的结果

问题描述 投票:1回答:2

我正在尝试(并且失败)加入SQLite数据库中的一些表。数据本身很复杂,但我想我已经把它归结为一个说明性的例子。

这是我要加入的三个表。

表:事件

+----+---------+-------+-----------+
| id | user_id | class | timestamp |
+----+---------+-------+-----------+
|  1 | 'user1' |     6 |       100 |
|  2 | 'user1' |    12 |       400 |
|  3 | 'user1' |     4 |       900 |
|  4 | 'user2' |     6 |       400 |
|  5 | 'user2' |     3 |       800 |
|  6 | 'user2' |     8 |       900 |
+----+---------+-------+-----------+

表:游戏

+---------+---------+------------+-----------+
| user_id | game_id | game_class | timestamp |
+---------+---------+------------+-----------+
| 'user1' |       1 | 'A'        |       200 |
| 'user2' |       2 | 'A'        |       300 |
| 'user1' |       3 | 'B'        |       500 |
| 'user1' |       4 | 'A'        |       600 |
| 'user1' |       5 | 'A'        |       700 |
+---------+---------+------------+-----------+

表:分数

+---------+-------+
| game_id | score |
+---------+-------+
|       1 |     8 |
|       2 |     2 |
|       4 |     9 |
|       5 |     6 |
+---------+-------+

我想加入这些内容,在第一个表格中提供一个附加列,其中包含活动时游戏类A中用户的当前得分。即我希望联接的结果看起来像这样:

期望的结果

+----+----------+-------+-----------+-----------------+
| id | user_id  | class | timestamp | current_a_score |
+----+----------+-------+-----------+-----------------+
|  1 |  'user1' |     6 |       100 | (null)          |
|  2 |  'user1' |    12 |       400 | 8               |
|  3 |  'user1' |     4 |       900 | 6               |
|  4 |  'user2' |     6 |       400 | 2               |
|  5 |  'user2' |     3 |       800 | 2               |
|  6 |  'user2' |     8 |       900 | 2               |
+----+----------+-------+-----------+-----------------+

以下简单连接汇总了两个表AScores和Games。

SELECT * FROM AScores
INNER JOIN Games
ON AScores.game_id = Games.game_id

所以我希望将它作为子查询加入到Events表中。像这样的东西:

SELECT Events.*, AScoredGames.time_stamp AS game_time_stamp, AScoredGames.score
FROM Events
LEFT OUTER JOIN (
    SELECT AScores.score, Games.* FROM AScores
    INNER JOIN Games
    ON AScores.game_id = Games.game_id
) AS AScoredGames
ON Events.user_id = AScoredGames.user_id 
AND Events.time_stamp >= AScoredGames.time_stamp
ORDER BY Events.time_stamp ASC

这导致以下结果:

+----+---------+-------+------------+-----------------+-------+
| id | user_id | class | time_stamp | game_time_stamp | score |
+----+---------+-------+------------+-----------------+-------+
|  1 | user1   |     6 | 100        | NULL            | NULL  |
|  2 | user1   |    12 | 400        | 200             | 8     |
|  4 | user2   |     6 | 400        | 300             | 2     |
|  5 | user2   |     3 | 800        | 300             | 2     |
|  6 | user2   |     8 | 900        | 300             | 2     |
|  3 | user1   |     4 | 900        | 200             | 8     |
|  3 | user1   |     4 | 900        | 600             | 9     |
|  3 | user1   |     4 | 900        | 700             | 6     |
+----+---------+-------+------------+-----------------+-------+

因此,我需要通过Events.id进行分组,以使用Events.id 3来删除三重行。但我想要做的是选择具有最大game_time_stamp的行,然后使用行的分数。如果我做MAX(game_time_stamp)作为我的聚合,我仍然必须独立地聚合得分。有没有办法将得分列的聚合函数中的行选择与game_time_stamp列的聚合函数的结果联系起来?

(NB Select first record in a One-to-Many relation using left joinSQL Server: How to Join to first row等问题的现有答案似乎表明我不能并且说必须在子查询中使用WHERE子句。但是我正在努力解决这个问题(我会发布另一个问题)我可以想到至少有一个解决方案,我希望有更好的解决方案。)

sql sqlite tsql
2个回答
1
投票

以下查询应该这样做。它使用带有相关子查询的NOT EXISTS条件来定位每个事件的相关游戏记录。

SELECT e.*, s.score current_a_score
FROM 
    events e
    LEFT JOIN games g 
        ON  g.user_id = e .user_id
        AND g.timestamp < e.timestamp
        AND NOT EXISTS (
            SELECT 1 
            FROM games g1
            WHERE 
                g1.user_id = e .user_id
                AND g1.timestamp < e.timestamp 
                AND g1.timestamp > g.timestamp
        )
    LEFT JOIN ascores s 
        ON  s.game_id = g.game_id
ORDER BY e.id

这个带有测试数据的DB Fiddle demo返回:

| id  | user_id | class | timestamp | current_a_score |
| --- | ------- | ----- | --------- | --------------- |
| 1   | user1   | 6     | 100       |                 |
| 2   | user1   | 12    | 400       | 8               |
| 3   | user1   | 4     | 900       | 6               |
| 4   | user2   | 6     | 400       | 2               |
| 5   | user2   | 3     | 800       | 2               |
| 6   | user2   | 8     | 900       | 2               |

0
投票

我有一个解决方法,但它感觉hacky并依赖于我的数据的细节。首先请注意,time_stamps都是100的倍数,而得分都低于10.我可以用不会干扰我的比较的方式对它们进行组合,但这意味着它们都在一个数字列中编码。此查询提供了所需的结果:

SELECT Events.id, MIN(Events.user_id) AS user_id, MIN(Events.class) AS class, MIN(Events.time_stamp) AS time_stamp, MAX(AScoredGames.combination) % 10 AS current_a_score
FROM Events
LEFT OUTER JOIN (
        SELECT AScores.score, AScores.score + (Games.time_stamp - 10) AS combination, Games.* FROM AScores
        INNER JOIN Games
        ON AScores.game_id = Games.game_id) AS AScoredGames
ON Events.user_id = AScoredGames.user_id AND Events.time_stamp >= AScoredGames.time_stamp
GROUP BY Events.id
ORDER BY id ASC

(组合在AScores.score + (Games.time_stamp - 10)完成,因此聚合函数变为MAX(AScoredGames.combination) % 10。)

实际结果

+----+---------+-------+------------+-----------------+
| id | user_id | class | time_stamp | current_a_score |
+----+---------+-------+------------+-----------------+
|  1 | user1   |     6 |        100 | NULL            |
|  2 | user1   |    12 |        400 | 8               |
|  3 | user1   |     4 |        900 | 6               |
|  4 | user2   |     6 |        400 | 2               |
|  5 | user2   |     3 |        800 | 2               |
|  6 | user2   |     8 |        900 | 2               |
+----+---------+-------+------------+-----------------+
© www.soinside.com 2019 - 2024. All rights reserved.