【问题标题】:STRING_AGG ignores GROUP BY in PostgreSQLSTRING_AGG 忽略 PostgreSQL 中的 GROUP BY
【发布时间】:2018-03-16 10:28:09
【问题描述】:

我已经为我的问题准备了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
);

并且放置移动的字母块以及结果单词和分数存储在另外 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);

他们的交换招式如下,有时一个招式可以产生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 代码(用于在某个游戏中显示玩家移动的 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

有没有办法通过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;

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

【问题讨论】:

  • 你能提供一个你想要的输出的例子吗?

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


【解决方案1】:

如果要按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;

按中间排序;

【讨论】:

【解决方案2】:

如果您希望结果按特定顺序排列,请在聚合调用中使用 order by 子句,如文档中所述:

SELECT STRING_AGG(x->>'letter', '' ORDER BY played),
       STRING_AGG(y, ', ' ORDER BY played)
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
     ) z;

至于使用子查询,注意documentation:

默认情况下未指定此顺序,但可以通过以下方式控制 在聚合调用中编写 ORDER BY 子句,如下所示 第 4.2.7 节。或者,从排序的 子查询通常会起作用。

我猜你发现了一个“通常”不适用的情况。更安全的方法是使用显式语法。

编辑:

您的外部查询是返回一行的聚合查询。所以一切都汇集在一起​​。

如果您希望每个 mid 有一行,则需要在外部查询中使用 GROUP BY

SELECT STRING_AGG(x->>'letter', '' ORDER BY played),
       STRING_AGG(y, ', ' ORDER BY played)
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
     ) z
GROUP BY mid;

【讨论】:

【解决方案3】:

我已经能够通过使用 CTE(此处为 SQL Fiddle)摆脱 DISTINCT:

WITH cte1 AS (
SELECT 
    mid,
    STRING_AGG(x->>'letter', '') AS tiles
FROM (
        SELECT 
            mid,
            JSONB_ARRAY_ELEMENTS(tiles) AS x
        FROM moves
        WHERE gid = 1
) AS z
GROUP BY mid),
cte2 AS (
        SELECT 
        mid,
        STRING_AGG(y, ', ') AS words
    FROM (
        SELECT 
            mid,
            FORMAT('%s (%s)', word, score) AS y
        FROM scores
        WHERE gid = 1
) AS z
GROUP BY mid)
SELECT 
    mid, 
    tiles, 
    words 
FROM cte1 
JOIN cte2 using (mid) 
ORDER BY mid ASC;

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-10-30
    • 2019-03-10
    相关资源
    最近更新 更多