【问题标题】:postgresql Update with SUM使用 SUM 更新 postgresql
【发布时间】:2021-07-27 14:02:36
【问题描述】:

在给定的简化示例中,我想用参与该事件的用户的分数总和来更新事件表中每个事件的“total_score”。

为此使用游标很容易理解和实现,但我想将其重构为基于集合的方法,在整个列上使用 SELECT / UPDATE 因为游标太慢了,在这种情况下可能是一种不好的做法。 如果您不仅可以提供所需的查询,还可以解释或链接到解释如何以“基于集合”的方式而不是程序游标方式思考,我将不胜感激。
POSTGRESQL 版本 = 13

设置:

尽可能简化表格,没有 PK 等。 有用户可以参与的用户、用户活动和事件。每个事件都是独一无二的,发生在特定世界的特定时间。 如果用户在某个世界的活动时间与整个事件的持续时间一致,则认为该用户参与了该事件。对于每个事件,我需要对所有参与的用户的分数求和,然后更新 total_score(作为计算的总和)。

表格:

  1. 已分析用户表(包含从整个用户群中选择的满足特定条件(例如年龄等)的用户。插入下方的用户已经满足这些要求。
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);
  1. 事件表 - 用户可以参与的事件。活动总是在 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);
  1. 用户活动表(为简单起见,也在 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');

简而言之,光标方法(这里的做法不好?)如下:

  1. 创建临时表qualified_user (id, score) – 跟踪参与当前行事件的用户。

  2. 从事件中选择 * 打开光标

  3. 获取行

  4. 截断表qualified_user

  5. 插入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

即收集其活动与当前光标记录的(事件)时间和世界一致的用户及其分数)。

  1. 从qualified_user 表中选择总分
  2. 用计算的总和更新当前光标。
  3. 只要还有下一行,就重复第 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


    【解决方案1】:

    我在这里处理的两个主要问题是:

    • 更新语句中不允许使用聚合函数
    • 从内部到外部查询的引用问题

    在失败、挖掘和学习 CTE 之后,我想出了以下解决方案。

    WITH cte AS(
    SELECT ee.event_id, ee.date, ee.start_time, ee.end_time, ee.world, 
     (SELECT SUM(score) FROM
      activity aa
      LEFT JOIN analyzed_user uu ON aa.user_id = uu.user_id
      WHERE 
        aa.world = ee.world AND
        aa.start_time <= ee.start_time AND
        aa.end_time >= ee.end_time AND
        aa.date = ee.date) AS total
    FROM event ee)
    UPDATE event eee
    SET total_score = cte.total
    FROM cte
    WHERE eee.event_id = cte.event_id
    

    【讨论】:

      猜你喜欢
      • 2021-01-08
      • 1970-01-01
      • 2021-01-08
      • 2013-02-05
      • 1970-01-01
      • 1970-01-01
      • 2017-05-20
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多