sql - STRING_AGG 忽略 PostgreSQL 中的 GROUP BY

标签 sql postgresql sql-order-by string-aggregation postgresql-10

我准备了一个SQL Fiddle对于我的问题 -

在 2 人文字游戏中,我将玩家及其游戏存储在 2 个表中:

CREATE TABLE players (
    uid SERIAL PRIMARY KEY,
    name text NOT NULL
);

CREATE TABLE games (
    gid SERIAL PRIMARY KEY,
    player1 integer NOT NULL REFERENCES players ON DELETE CASCADE,
    player2 integer NOT NULL REFERENCES players ON DELETE CASCADE
);

放置移动的字母 block 以及生成的单词和分数存储在另外 2 个表中:

CREATE TABLE moves (
    mid BIGSERIAL PRIMARY KEY,
    uid integer NOT NULL REFERENCES players ON DELETE CASCADE,
    gid integer NOT NULL REFERENCES games ON DELETE CASCADE,
    played timestamptz NOT NULL,
    tiles jsonb NOT NULL
);

CREATE TABLE scores (
    mid     bigint  NOT NULL REFERENCES moves ON DELETE CASCADE,
    uid     integer NOT NULL REFERENCES players ON DELETE CASCADE,
    gid     integer NOT NULL REFERENCES games ON DELETE CASCADE,
    word    text    NOT NULL CHECK(word ~ '^[A-Z]{2,}$'),
    score   integer NOT NULL CHECK(score >= 0)
);

这里我用包含一个游戏和 2 个玩家(Alice 和 Bob)的测试数据填充上面的表格:

INSERT INTO players (name) VALUES ('Alice'), ('Bob');
INSERT INTO games (player1, player2) VALUES (1, 2);

他们的互换 Action 如下,有时一个 Action 可以产生2个单词:

INSERT INTO moves (uid, gid, played, tiles) VALUES
(1, 1, now() + interval '1 min', '[{"col": 7, "row": 12, "value": 3, "letter": "A"}, {"col": 8, "row": 12, "value": 10, "letter": "B"}, {"col": 9, "row": 12, "value": 1, "letter": "C"}, {"col": 10, "row": 12, "value": 2, "letter": "D"}]
'::jsonb), 
(2, 1, now() + interval '2 min', '[{"col": 7, "row": 12, "value": 3, "letter": "X"}, {"col": 8, "row": 12, "value": 10, "letter": "Y"}, {"col": 9, "row": 12, "value": 1, "letter": "Z"}]
'::jsonb), 
(1, 1, now() + interval '3 min', '[{"col": 7, "row": 12, "value": 3, "letter": "K"}, {"col": 8, "row": 12, "value": 10, "letter": "L"}, {"col": 9, "row": 12, "value": 1, "letter": "M"}, {"col": 10, "row": 12, "value": 2, "letter": "N"}]
'::jsonb), 
(2, 1, now() + interval '4 min', '[]'::jsonb), 
(1, 1, now() + interval '5 min', '[{"col": 7, "row": 12, "value": 3, "letter": "A"}, {"col": 8, "row": 12, "value": 10, "letter": "B"}, {"col": 9, "row": 12, "value": 1, "letter": "C"}, {"col": 10, "row": 12, "value": 2, "letter": "D"}]
'::jsonb), 
(2, 1, now() + interval '6 min', '[{"col": 7, "row": 12, "value": 3, "letter": "P"}, {"col": 8, "row": 12, "value": 10, "letter": "Q"}]
'::jsonb);

INSERT INTO scores (mid, uid, gid, word, score) VALUES
(1, 1, 1, 'ABCD', 40),
(2, 2, 1, 'XYZ', 30),
(2, 2, 1, 'XAB', 30),
(3, 1, 1, 'KLMN', 40),
(3, 1, 1, 'KYZ', 30),
(5, 1, 1, 'ABCD', 40),
(6, 2, 1, 'PQ', 20),
(6, 2, 1, 'PABCD', 50);

正如您在上面所看到的,tiles 列始终是一个 JSON 对象列表。

但我只需要检索对象的单个属性:letter

这是我的 SQL 代码(用于在特定游戏中显示玩家 Action 的 PHP 脚本):

SELECT 
    STRING_AGG(x->>'letter', ''),
    STRING_AGG(y, ', ')
FROM (
    SELECT 
        JSONB_ARRAY_ELEMENTS(m.tiles) AS x,
        FORMAT('%s (%s)', s.word, s.score) AS y
    FROM moves m
    LEFT JOIN scores s
    USING (mid)
    WHERE m.gid = 1
    GROUP BY mid, s.word, s.score
    ORDER BY played ASC
) AS z;

不幸的是,它没有按预期工作。

两者STRING_AGG调用将所有内容放在两个巨大的字符串中,尽管我尝试GROUP BY mid:

screenshot

有没有办法通过mid(又名移动ID)分割结果字符串?

更新:

我的问题不是排序。我的问题是我得到了 2 个巨大的字符串,而我希望有多个字符串,每个移动 id 一对(又名 mid)。

这是我的预期输出,有人对如何实现它有建议吗?

mid   "concatenated 'letter' from JSON"   "concatenated words and scores"
 1                  'ABCD'                       'ABCD (40)'
 2                  'XYZ'                        'XYZ (30), XAB (30)'               
 3                  'KLMN'                       'KLMN (40), KYZ (30)'
 5                  'ABCD'                       'ABCD (40)'
 6                  'PQ'                         'PQ (20), PABCD (50)'

更新#2:

我遵循了 Laurenz 的建议(谢谢!这里是 SQL Fiddle ):

SELECT 
    mid,
    STRING_AGG(x->>'letter', '') AS tiles,
    STRING_AGG(y, ', ') AS words
FROM (
    SELECT 
        mid,
        JSONB_ARRAY_ELEMENTS(m.tiles) AS x,
        FORMAT('%s (%s)', s.word, s.score) AS y
    FROM moves m
    LEFT JOIN scores s
    USING (mid)
    WHERE m.gid = 1
) AS z
GROUP BY mid
ORDER BY mid;

但由于某种原因,“单词(分数)”条目会成倍增加:

screenshot 2

最佳答案

如果要按 mid 分组,则必须将该列添加到内部查询的 SELECT 列表中,并添加 GROUP BY mid 到外部查询。

您可以在聚合内使用DISTINCT来删除重复项:

SELECT 
    mid,
    STRING_AGG(DISTINCT x->>'letter', '') AS tiles,
    STRING_AGG(DISTINCT y, ', ') AS words
FROM (
    SELECT 
        mid,
        JSONB_ARRAY_ELEMENTS(m.tiles) AS x,
        FORMAT('%s (%s)', s.word, s.score) AS y
    FROM moves m
    LEFT JOIN scores s
    USING (mid)
    WHERE m.gid = 1
) AS z
GROUP BY mid;

按中点排序;

关于sql - STRING_AGG 忽略 PostgreSQL 中的 GROUP BY,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49318474/

相关文章:

mysql - 将 yyyymmdd(int 类型) 转换为日期

php - 如何在单击时执行 mysql 请求?

sql - EAV vs 序列化对象 vs SQL with Xpath?

postgresql - 使用 shp2pgsql 转换 shapefile

MySQL:使用另一个表中的两个计数进行排序?

mysql - SELECT 列与 GROUP BY 列不同

MySQL - IN() 中的 ORDER BY 值

python - 如何使用 git 跟踪 Django 模型的变化?

sql - 在 postgresql 中重置自动增量计数器

ruby-on-rails - psql : FATAL: database "db_name" is not currently accepting connections