透视和聚合 1..n 重复记录 Postgres psql

Pivot and aggregate 1..n duplicate records Postgres psql

我有一个 table tbl_action 这样的:

game_id action action_datetime
1 start 2022-04-05T10:30+00
1 attack 2022-04-05T10:45+00
1 defend 2022-04-05T11:30+00
1 attack 2022-04-05T11:45+00
1 defend 2022-04-05T12:00+00
1 stop 2022-04-05T12:10+00
create table if not exists tblaction;

insert into "tblaction" (game_id, action_name, action_time) values (1,'start','2022-04-05T10:30+00'),
(2,'attack','2022-04-05T10:45+00'),
(3,'defend','2022-04-05T11:30+00'),
(4,'attack','2022-04-05T11:45+00'),
(5,'defend','2022-04-05T12:00+00'),
(6,'stop','2022-04-05T12:10+00'); 

我想像这样转动它:

game_id start attack1 defend1 ... attackn defendn stop
1 2022-04-05T10:30+00 2022-04-05T10:45+00 2022-04-05T11:30+00 ... 2022-04-05T11:45+00 2022-04-05T12:00+00 2022-04-05T12:10+00

我的问题是如何聚合操作 1..n,而不是将所有每个唯一 action_name 聚合成一个数字。我正在使用 Postgres。

此 MySQL 示例代码在每个 action_name 出现 <2 次时有效,但我想要 1..n 有效的代码。我不想删除重复项。我不限MySQL我可以用最新版的Postgres

SELECT

 game_id, 

MAX( CASE WHEN action_name = 'start' THEN action_time ELSE NULL END ) AS "start",

MIN( CASE WHEN action_name = 'attack' THEN action_time ELSE NULL END ) AS "attack",

MIN( CASE WHEN action_name = 'defend' THEN action_time ELSE NULL END ) AS "defend",

MAX( CASE WHEN action_name = 'attack' THEN action_time ELSE NULL END ) AS "attack",

MAX( CASE WHEN action_name = 'defend' THEN action_time ELSE NULL END ) AS "defend",

MAX( CASE WHEN action_name = 'stop' THEN action_time ELSE NULL END ) AS "stop"

FROM

 tblaction

GROUP BY

 game_id

ORDER BY

 game_id ASC;

我阅读了 Postgres tablefunc 文档并在 pgadmin4 中尝试了 \crosstabview 并得到了错误 \crosstabview: query result contains multiple data values for row "1", column "attack"


SELECT game_id, action_name, action_time FROM tblaction \crosstabview

你可以尝试使用带有ROW_NUMER window 函数的子查询,它可以帮助你通过 game_id, action_name 列生成行号,然后你可以使用条件聚合函数为此 rn

SELECT
    game_id, 
    MAX(CASE WHEN action_name = 'start'  THEN action_time   END ) AS "start",
    MAX(CASE WHEN action_name = 'attack' AND rn = 1 THEN action_time  END ) AS "attack1",
    MAX(CASE WHEN action_name = 'defend' AND rn = 1 THEN action_time  END ) AS "defend1",
    MAX(CASE WHEN action_name = 'attack' AND rn = 2 THEN action_time  END ) AS "attack",
    MAX(CASE WHEN action_name = 'defend' AND rn = 2 THEN action_time  END ) AS "defend",
    MAX(CASE WHEN action_name = 'stop'   THEN action_time   END ) AS "stop"
FROM
(
    SELECT *,ROW_NUMBER() OVER(PARTITION BY game_id,action_name ORDER BY action_time) rn
    FROM tblaction
) t1
GROUP BY
 game_id
ORDER BY
 game_id ASC;

sqlfiddle

我建议用GROUP_CONCAT会更简单灵活。
注意 该问题据说适用于 mySQL 或 Postgres。请参阅 https://dbfiddle.uk/?rdbms=postgres_10&fiddle=8719aedb7ab5736caf79fe86e76d6f40 以了解使用 STRING_AGG( ~ ,',') 而不是 GROUP_CONCAT 的 Postgres 版本,否则相同

create table tblaction (
game_id int, 
action_name varchar(20), 
action_time varchar(25)
);

insert into tblaction 
(game_id, action_name, action_time) values 
(1,'start','2022-04-05T10:30+00'),
(1,'attack','2022-04-05T10:45+00'),
(1,'defend','2022-04-05T11:30+00'),
(1,'attack','2022-04-05T11:45+00'),
(1,'defend','2022-04-05T12:00+00'),
(1,'stop','2022-04-05T12:10+00'); 
✓

✓
SELECT
  game_id,
  GROUP_CONCAT(case when action_name='start' then action_time end) start,
  GROUP_CONCAT(case when action_name='stop' then action_time  end) stop,
  COUNT(action_time) "number",
  GROUP_CONCAT(case when action_name='attack' then action_time  end) "attacks",
  GROUP_CONCAT(case when action_name='defend' then action_time  end) "defends"
FROM tblaction
GROUP BY
  game_id;
game_id | start               | stop                | number | attacks                                 | defends                                
------: | :------------------ | :------------------ | -----: | :-------------------------------------- | :--------------------------------------
      1 | 2022-04-05T10:30+00 | 2022-04-05T12:10+00 |      6 | 2022-04-05T10:45+00,2022-04-05T11:45+00 | 2022-04-05T11:30+00,2022-04-05T12:00+00

db<>fiddle here