将 CTE 和聚合函数与 UPDATE 一起使用

Using CTE and aggregate functions with UPDATE

在 PostgreSQL 10.6 中,我试图将 2 个聚合函数调用的结果存储到 words_users table 的 avg_scoreavg_time 列中,但不幸的是得到了语法错误:

WITH last_week_moves AS (
    SELECT
            m.gid,
            m.uid,
            m.played - LAG(m.played) OVER(PARTITION BY m.gid ORDER BY played) AS diff
    FROM words_moves m
    JOIN words_games g ON (m.gid = g.gid AND 5 IN (g.player1, g.player2))
    WHERE m.played > CURRENT_TIMESTAMP - INTERVAL '1 week'
)
UPDATE words_users SET
        avg_score = (SELECT ROUND(AVG(score), 1) FROM words_moves WHERE uid = 5),
        avg_time = TO_CHAR(AVG(diff), 'HH24:MI')
FROM last_week_moves
WHERE uid = 5
GROUP BY uid;

(我在上面的语句中使用了硬编码的 uid = 5,但在现实生活中后者被包装在 PL/PgSQL 存储函数中并使用参数 uid = in_uid ).

ERROR:  42601: syntax error at or near "GROUP"
LINE 15: GROUP BY uid
         ^
LOCATION:  scanner_yyerror, scan.l:1128

数据库似乎对 GROUP BY 不满意,但我需要 AVG(diff),因为 CTE 始终为游戏中的 2 位玩家提供移动之间的时间:

SELECT
        m.gid,
        m.uid,
        m.played - LAG(m.played) OVER(PARTITION BY m.gid ORDER BY played) AS diff
FROM words_moves m
JOIN words_games g ON (m.gid = g.gid AND 5 IN (g.player1, g.player2))
WHERE m.played > CURRENT_TIMESTAMP - INTERVAL '1 week';

  gid  |  uid  |         diff
-------+-------+-----------------------
 50399 |   774 | ¤
 50608 |  8977 | ¤
 50608 |     5 | 00:39:48.121149
 50608 |  8977 | 00:09:46.221235
 50608 |     5 | 01:35:23.524209
 50608 |  8977 | 09:26:40.794061
 50697 |     5 | ¤
 50697 | 10322 | 02:13:16.502079
 50697 |     5 | 01:47:44.681788
 50697 | 10322 | 00:01:31.597973
 50697 |     5 | 12:11:24.54716
 50697 | 10322 | 12:01:15.078243
 50697 |     5 | 11:52:39.60056
 50697 | 10322 | 00:11:30.491137
 50697 |     5 | 00:14:53.612513
 50697 | 10322 | 01:45:23.940957
 ...
 52469 |     5 | 02:46:29.768655
 52469 |  8550 | 01:16:45.169882
 52469 |     5 | 08:38:00.691552
(59 rows)

有人知道如何更改我的 UPDATE 查询吗?

下面是有问题的 3 table:

 # \d words_users
                                         Table "public.words_users"
    Column     |           Type           | Collation | Nullable |                 Default
---------------+--------------------------+-----------+----------+------------------------------------------
 uid           | integer                  |           | not null | nextval('words_users_uid_seq'::regclass)
 created       | timestamp with time zone |           | not null |
 visited       | timestamp with time zone |           | not null |
 ip            | inet                     |           | not null |
 fcm           | text                     |           |          |
 apns          | text                     |           |          |
 adm           | text                     |           |          |
 motto         | text                     |           |          |
 vip_until     | timestamp with time zone |           |          |
 grand_until   | timestamp with time zone |           |          |
 banned_until  | timestamp with time zone |           |          |
 banned_reason | text                     |           |          |
 elo           | integer                  |           | not null |
 medals        | integer                  |           | not null |
 coins         | integer                  |           | not null |
 avg_score     | double precision         |           |          |
 avg_time      | text                     |           |          |
Indexes:
    "words_users_pkey" PRIMARY KEY, btree (uid)
Check constraints:
    "words_users_banned_reason_check" CHECK (length(banned_reason) > 0)
    "words_users_elo_check" CHECK (elo >= 0)
    "words_users_medals_check" CHECK (medals >= 0)
Referenced by:
    TABLE "words_chat" CONSTRAINT "words_chat_uid_fkey" FOREIGN KEY (uid) REFERENCES words_users(uid) ON DELETE CASCADE
    TABLE "words_games" CONSTRAINT "words_games_player1_fkey" FOREIGN KEY (player1) REFERENCES words_users(uid) ON DELETE CASCADE
    TABLE "words_games" CONSTRAINT "words_games_player2_fkey" FOREIGN KEY (player2) REFERENCES words_users(uid) ON DELETE CASCADE
    TABLE "words_moves" CONSTRAINT "words_moves_uid_fkey" FOREIGN KEY (uid) REFERENCES words_users(uid) ON DELETE CASCADE
    TABLE "words_reviews" CONSTRAINT "words_reviews_author_fkey" FOREIGN KEY (author) REFERENCES words_users(uid) ON DELETE CASCADE
    TABLE "words_reviews" CONSTRAINT "words_reviews_uid_fkey" FOREIGN KEY (uid) REFERENCES words_users(uid) ON DELETE CASCADE
    TABLE "words_scores" CONSTRAINT "words_scores_uid_fkey" FOREIGN KEY (uid) REFERENCES words_users(uid) ON DELETE CASCADE
    TABLE "words_social" CONSTRAINT "words_social_uid_fkey" FOREIGN KEY (uid) REFERENCES words_users(uid) ON DELETE CASCADE
    TABLE "words_stats" CONSTRAINT "words_stats_uid_fkey" FOREIGN KEY (uid) REFERENCES words_users(uid) ON DELETE CASCADE

# \d words_moves
                                      Table "public.words_moves"
 Column  |           Type           | Collation | Nullable |                 Default
---------+--------------------------+-----------+----------+------------------------------------------
 mid     | bigint                   |           | not null | nextval('words_moves_mid_seq'::regclass)
 action  | text                     |           | not null |
 gid     | integer                  |           | not null |
 uid     | integer                  |           | not null |
 played  | timestamp with time zone |           | not null |
 tiles   | jsonb                    |           |          |
 score   | integer                  |           |          |
 letters | text                     |           |          |
 hand    | text                     |           |          |
 puzzle  | boolean                  |           | not null | false
Indexes:
    "words_moves_pkey" PRIMARY KEY, btree (mid)
    "words_moves_gid_played_idx" btree (gid, played DESC)
    "words_moves_uid_idx" btree (uid)
Check constraints:
    "words_moves_score_check" CHECK (score >= 0)
Foreign-key constraints:
    "words_moves_gid_fkey" FOREIGN KEY (gid) REFERENCES words_games(gid) ON DELETE CASCADE
    "words_moves_uid_fkey" FOREIGN KEY (uid) REFERENCES words_users(uid) ON DELETE CASCADE
Referenced by:
    TABLE "words_scores" CONSTRAINT "words_scores_mid_fkey" FOREIGN KEY (mid) REFERENCES words_moves(mid) ON DELETE CASCADE

# \d words_games
                                      Table "public.words_games"
  Column  |           Type           | Collation | Nullable |                 Default
----------+--------------------------+-----------+----------+------------------------------------------
 gid      | integer                  |           | not null | nextval('words_games_gid_seq'::regclass)
 created  | timestamp with time zone |           | not null |
 finished | timestamp with time zone |           |          |
 player1  | integer                  |           | not null |
 player2  | integer                  |           |          |
 played1  | timestamp with time zone |           |          |
 played2  | timestamp with time zone |           |          |
 state1   | text                     |           |          |
 state2   | text                     |           |          |
 reason   | text                     |           |          |
 hint1    | text                     |           |          |
 hint2    | text                     |           |          |
 score1   | integer                  |           | not null |
 score2   | integer                  |           | not null |
 chat1    | integer                  |           | not null |
 chat2    | integer                  |           | not null |
 hand1    | character(1)[]           |           | not null |
 hand2    | character(1)[]           |           | not null |
 pile     | character(1)[]           |           | not null |
 letters  | character(1)[]           |           | not null |
 values   | integer[]                |           | not null |
 bid      | integer                  |           | not null |
 friendly | boolean                  |           |          |
Indexes:
    "words_games_pkey" PRIMARY KEY, btree (gid)
    "words_games_player1_coalesce_idx" btree (player1, COALESCE(finished, 'infinity'::timestamp with time zone))
    "words_games_player2_coalesce_idx" btree (player2, COALESCE(finished, 'infinity'::timestamp with time zone))
Check constraints:
    "words_games_chat1_check" CHECK (chat1 >= 0)
    "words_games_chat2_check" CHECK (chat2 >= 0)
    "words_games_check" CHECK (player1 <> player2)
    "words_games_score1_check" CHECK (score1 >= 0)
    "words_games_score2_check" CHECK (score2 >= 0)
Foreign-key constraints:
    "words_games_bid_fkey" FOREIGN KEY (bid) REFERENCES words_boards(bid) ON DELETE CASCADE
    "words_games_player1_fkey" FOREIGN KEY (player1) REFERENCES words_users(uid) ON DELETE CASCADE
    "words_games_player2_fkey" FOREIGN KEY (player2) REFERENCES words_users(uid) ON DELETE CASCADE
Referenced by:
    TABLE "words_chat" CONSTRAINT "words_chat_gid_fkey" FOREIGN KEY (gid) REFERENCES words_games(gid) ON DELETE CASCADE
    TABLE "words_moves" CONSTRAINT "words_moves_gid_fkey" FOREIGN KEY (gid) REFERENCES words_games(gid) ON DELETE CASCADE
    TABLE "words_scores" CONSTRAINT "words_scores_gid_fkey" FOREIGN KEY (gid) REFERENCES words_games(gid) ON DELETE CASCADE

更新:

按照@lau 的建议,我尝试将 AVG 移动到 CTE 中,但出现另一个语法错误:

WITH last_week_moves AS (
                SELECT
                        m.gid,
                        m.uid,
                        TO_CHAR(AVG(m.played - LAG(m.played) OVER(PARTITION BY m.gid ORDER BY played)), 'HH24:MI') AS diff
                FROM words_moves m
                JOIN words_games g ON (m.gid = g.gid AND 5 IN (g.player1, g.player2))
                WHERE m.played > CURRENT_TIMESTAMP - INTERVAL '1 week'
                GROUP BY uid
)
UPDATE words_users SET
        avg_score = (SELECT ROUND(AVG(score), 1) FROM words_moves WHERE uid = 5),
        avg_time = diff
FROM last_week_moves
WHERE uid = 5;

ERROR:  42803: aggregate function calls cannot contain window function calls
LINE 5:                         TO_CHAR(AVG(m.played - LAG(m.played)...
                                                       ^
LOCATION:  check_agg_arguments_walker, parse_agg.c:728

您似乎想要:

WITH last_week_moves AS (
      SELECT m.uid,
             (MAX(m.played) - MIN(m.played)) / NULLIF(COUNT(*) - 1, 0) as avg_diff,
             AVG(score) as avg_score
      FROM words_moves m JOIN
           words_games g
           ON m.gid = g.gid AND 5 IN (g.player1, g.player2)
      WHERE m.played > CURRENT_TIMESTAMP - INTERVAL '1 week'
      GROUP BY m.uid
     )
UPDATE words_users wu
    SET avg_score = lwm.avg_score,
        avg_time = TO_CHAR(avg_diff, 'HH24:MI')
FROM last_week_moves lwm
WHERE wu.uid = lwm.uid AND
      wu.uid = 5;

请注意,这简化了 diff 计算,因此不需要 lag()

你可以看到它们是等价的: 价值差异 1个 4 3 9 5

diff的平均值明显是4,这是((4 - 1) + (9 - 4)) / 2。你看“4”被取消了,所以真的是(9 - 1) / 2。这个观察概括了。