【发布时间】:2021-07-27 14:02:36
【问题描述】:
在给定的简化示例中,我想用参与该事件的用户的分数总和来更新事件表中每个事件的“total_score”。
为此使用游标很容易理解和实现,但我想将其重构为基于集合的方法,在整个列上使用 SELECT / UPDATE 因为游标太慢了,在这种情况下可能是一种不好的做法。
如果您不仅可以提供所需的查询,还可以解释或链接到解释如何以“基于集合”的方式而不是程序游标方式思考,我将不胜感激。
POSTGRESQL 版本 = 13
设置:
尽可能简化表格,没有 PK 等。 有用户可以参与的用户、用户活动和事件。每个事件都是独一无二的,发生在特定世界的特定时间。 如果用户在某个世界的活动时间与整个事件的持续时间一致,则认为该用户参与了该事件。对于每个事件,我需要对所有参与的用户的分数求和,然后更新 total_score(作为计算的总和)。
表格:
- 已分析用户表(包含从整个用户群中选择的满足特定条件(例如年龄等)的用户。插入下方的用户已经满足这些要求。
CREATE TABLE analyzed_user
(
user_id bigint NOT NULL,
score numeric
);
INSERT INTO analyzed_user VALUES(100, 400);
INSERT INTO analyzed_user VALUES(200, 800);
INSERT INTO analyzed_user VALUES(300, 1500);
- 事件表 - 用户可以参与的事件。活动总是在 23:59 之前结束(永远不会超过第二天)
CREATE TABLE event
(
event_id bigint NOT NULL,
date date,
start_time time without time zone,
end_time time without time zone,
world varchar,
total_score numeric NOT NULL DEFAULT 0
);
INSERT INTO event VALUES (1, '2021-07-27', '08:00:00', '09:00:00', 'Earth', 0);
INSERT INTO event VALUES (2, '2021-07-27', '12:00:00', '13:00:00', 'Earth', 0);
INSERT INTO event VALUES (3, '2021-07-27', '14:00:00', '15:00:00', 'Mars', 0);
INSERT INTO event VALUES (4, '2021-07-27', '20:00:00', '21:00:00', 'Mars', 0);
- 用户活动表(为简单起见,也在 23:59 之前结束(服务器在午夜重新启动并将所有人踢出)。
CREATE TABLE activity
(
user_id bigint NOT NULL,
date date,
start_time time without time zone,
end_time time without time zone,
world varchar
);
INSERT INTO activity VALUES (100, '2021-07-27', '07:00:00', '14:00:00', 'Earth');
INSERT INTO activity VALUES (100, '2021-07-27', '23:00:00', '23:30:00', 'Earth');
INSERT INTO activity VALUES (100, '2021-07-27', '15:00:00', '22:00:00', 'Mars');
INSERT INTO activity VALUES (200, '2021-07-27', '7:30:00', '9:30:00', 'Earth');
INSERT INTO activity VALUES (200, '2021-07-27', '13:00:00', '16:30:00', 'Mars');
INSERT INTO activity VALUES (200, '2021-07-27', '18:00:00', '20:20:00', 'Mars');
INSERT INTO activity VALUES (300, '2021-07-27', '11:30:00', '14:30:00', 'Earth');
INSERT INTO activity VALUES (300, '2021-07-27', '17:00:00', '18:30:00', 'Mars');
INSERT INTO activity VALUES (300, '2021-07-27', '19:30:00', '22:30:00', 'Mars');
简而言之,光标方法(这里的做法不好?)如下:
-
创建临时表qualified_user (id, score) – 跟踪参与当前行事件的用户。
-
从事件中选择 * 打开光标
-
获取行
-
截断表qualified_user
-
插入qualified_user(user_id, score)
选择 user_id,从活动 a 中得分
左加入analyzed_user u ON a.user_id = u.user_id
在哪里
a.world = 记录.世界与
a.start_time
a.end_time >= 记录.end_time
即收集其活动与当前光标记录的(事件)时间和世界一致的用户及其分数)。
- 从qualified_user 表中选择总分
- 用计算的总和更新当前光标。
- 只要还有下一行,就重复第 3 步到第 7 步。
所以基本上它是逐个事件进行的,对于每个事件(它的开始、结束时间和世界)选择活动一致的用户并将他们的分数相加。逻辑很好,只是光标的计算时间很糟糕。这是我无法以基于集合的方式正确查询的内容。
我的尝试是以下的多种组合(也包括 WITH 子句等),但总是以我认为问题所在的 group 结尾(?)(如果没有 group by 子句,则不允许使用像 SUM 这样的聚合函数):
UPDATE event ee
SET total_score = gg.total
FROM (SELECT SUM(uu.score) AS total, aa.user_id, aa.world, aa.start_time, aa.end_time, aa.date
FROM activity aa
LEFT JOIN qualified_user uu ON aa.user_id = uu.user_id
GROUP BY aa.user_id, aa.world, aa.start_time, aa.end_time, aa.date
) AS gg
WHERE
gg.world = ee.world AND
gg.start_time <= ee.start_time AND
gg.end_time >= ee.end_time AND
gg.date = ee.date;
正确的结果是:
事件 #1 总分 = 400 + 800 = 1200
事件 #2 总分 = 400+1500 = 1900
事件 #3 总分 = 800
赛事 #4 总分 = 400+1500 = 1900
但如您所见,上述查询的结果是错误的 (800/1500/800/400)
对我来说,将用户活动分组是不合逻辑的,尽管没有它我无法完成并收到错误。
如果您能解释上述查询的问题并提供正确的查询,我将不胜感激。
【问题讨论】:
标签: postgresql