Postgres returns [null] 而不是 [] for array_agg of join table
Postgres returns [null] instead of [] for array_agg of join table
我正在 Postgres 中选择一些对象及其标签。架构相当简单,三个表:
对象 id
标签 id | object_id | tag_id
标签 id | tag
我正在加入这样的表格,使用 array_agg
将标签聚合到一个字段中:
SELECT objects.*,
array_agg(tags.tag) AS tags,
FROM objects
LEFT JOIN taggings ON objects.id = taggings.object_id
LEFT JOIN tags ON tags.id = taggings.tag_id
然而,如果对象没有标签,Postgres returns this:
[ null ]
而不是一个空数组。 如果没有标签,我如何 return 一个空数组? 我已经仔细检查过我没有 returned 的空标签。
aggregate docs 说 "The coalesce function can be used to substitute zero or an empty array for null when necessary"。我试过 COALESCE(ARRAY_AGG(tags.tag)) as tags
但它仍然 return 是一个空数组。我试过使第二个参数有很多东西(例如COALESCE(ARRAY_AGG(tags.tag), ARRAY())
,但它们都会导致语法错误。
文档说包含 NULL
的数组是 returned。如果你想把它转换成一个空数组,那么你需要做一些小魔术:
SELECT objects.id,
CASE WHEN length((array_agg(tags.tag))[1]) > 0
THEN array_agg(tags.tag)
ELSE ARRAY[]::text[] END AS tags
FROM objects
LEFT JOIN taggings ON objects.id = taggings.object_id
LEFT JOIN tags ON tags.id = taggings.tag_id
GROUP BY 1;
这假设标签是 text
类型(或其任何变体);根据需要修改演员表。
这里的技巧是 [NULL]
数组中的第一个(也是唯一一个)元素的长度为 0,因此如果任何数据是 return 从 tags
编辑的,您 return聚合,否则构造一个正确类型的空数组。
顺便说一句,文档中关于使用 coalesce()
的声明有点拙劣:意思是如果您不想 NULL
作为结果,您可以使用 coalesce()
将其转换为 0
或您选择的其他输出。但是您需要将其应用于 数组元素 而不是数组,在您的情况下,数组不会提供解决方案。
文档说,当您聚合零行时,您会得到一个空值,关于使用 COALESCE
的说明是针对这种特定情况。
这不适用于您的查询,因为 LEFT JOIN
的行为方式 - 当它找到 零 匹配行时,它 returns 一个行,用空值填充(一个空行的集合是一个包含一个空元素的数组)。
您可能会想在输出中盲目地将 [NULL]
替换为 []
,但这样您就无法区分 没有标签的对象 和 标记的对象,其中 tags.tag
为 null。您的应用程序逻辑 and/or 完整性约束可能不允许第二种情况,但如果它确实设法潜入,那就更有理由不抑制空标记。
您可以通过检查连接条件另一端的字段是否为 null 来识别没有标签的对象(或者一般来说,告诉 LEFT JOIN
没有找到匹配项)。所以在你的情况下,只需替换
array_agg(tags.tag)
和
CASE
WHEN taggings.object_id IS NULL
THEN ARRAY[]::text[]
ELSE array_agg(tags.tag)
END
另一个选项可能是 array_remove(..., NULL)
(introduced in 9.3) 如果 tags.tag
是 NOT NULL
(否则您可能希望在数组中保留 NULL
值,但在那种情况下,由于 LEFT JOIN
):
,您无法区分单个现有的 NULL
标签和 NULL
标签
SELECT objects.*,
array_remove(array_agg(tags.tag), NULL) AS tags,
FROM objects
LEFT JOIN taggings ON objects.id = taggings.object_id
LEFT JOIN tags ON tags.id = taggings.tag_id
如果没有找到标签,则返回一个空数组。
从 9.4 开始,可以限制聚合函数调用只处理符合特定条件的行:array_agg(tags.tag) filter (where tags.tag is not null)
也许这个答案有点晚了,但我想与您分享另一种查询策略也是可能的:在单独的(公共)table 表达式中执行聚合。
WITH cte_tags AS (
SELECT
taggings.object_id,
array_agg(tags.tag) AS tags
FROM
taggings
INNER JOIN tags ON tags.id = taggings.tag_id
GROUP BY
taggings.object_id
)
SELECT
objects.*,
cte_tags.tags
FROM
objects
LEFT JOIN cte_tags ON cte_tags.object_id = objects.id
您现在将得到 NULL 而不是数组,而不是包含单个元素 NULL 的数组。
如果你真的想要一个空数组而不是结果中的 NULL,你可以使用 COALESCE
函数...:[=14=]
WITH cte_tags AS (
SELECT
taggings.object_id,
array_agg(tags.tag) AS tags
FROM
taggings
INNER JOIN tags ON tags.id = taggings.tag_id
GROUP BY
taggings.object_id
)
SELECT
objects.*,
COALESCE(cte_tags.tags, '{}') AS tags
FROM
objects
LEFT JOIN cte_tags ON cte_tags.object_id = objects.id
...或使用数组到数组的串联:
WITH cte_tags AS (
SELECT
taggings.object_id,
array_agg(tags.tag) AS tags
FROM
taggings
INNER JOIN tags ON tags.id = taggings.tag_id
GROUP BY
taggings.object_id
)
SELECT
objects.*,
cte_tags.tags || '{}' AS tags
FROM
objects
LEFT JOIN cte_tags ON cte_tags.object_id = objects.id
这个怎么样:
COALESCE(NULLIF(array_agg(tags.tag), '{NULL}'), '{}') AS tags,
似乎有效。
我换了
array_to_json(array_agg(col_name))
和
array_to_json(coalesce(array_agg(col_name), ARRAY[]::record[]))
所以我没有返回空 JSON 值,而是得到一个空的 JSON 数组
我正在 Postgres 中选择一些对象及其标签。架构相当简单,三个表:
对象 id
标签 id | object_id | tag_id
标签 id | tag
我正在加入这样的表格,使用 array_agg
将标签聚合到一个字段中:
SELECT objects.*,
array_agg(tags.tag) AS tags,
FROM objects
LEFT JOIN taggings ON objects.id = taggings.object_id
LEFT JOIN tags ON tags.id = taggings.tag_id
然而,如果对象没有标签,Postgres returns this:
[ null ]
而不是一个空数组。 如果没有标签,我如何 return 一个空数组? 我已经仔细检查过我没有 returned 的空标签。
aggregate docs 说 "The coalesce function can be used to substitute zero or an empty array for null when necessary"。我试过 COALESCE(ARRAY_AGG(tags.tag)) as tags
但它仍然 return 是一个空数组。我试过使第二个参数有很多东西(例如COALESCE(ARRAY_AGG(tags.tag), ARRAY())
,但它们都会导致语法错误。
文档说包含 NULL
的数组是 returned。如果你想把它转换成一个空数组,那么你需要做一些小魔术:
SELECT objects.id,
CASE WHEN length((array_agg(tags.tag))[1]) > 0
THEN array_agg(tags.tag)
ELSE ARRAY[]::text[] END AS tags
FROM objects
LEFT JOIN taggings ON objects.id = taggings.object_id
LEFT JOIN tags ON tags.id = taggings.tag_id
GROUP BY 1;
这假设标签是 text
类型(或其任何变体);根据需要修改演员表。
这里的技巧是 [NULL]
数组中的第一个(也是唯一一个)元素的长度为 0,因此如果任何数据是 return 从 tags
编辑的,您 return聚合,否则构造一个正确类型的空数组。
顺便说一句,文档中关于使用 coalesce()
的声明有点拙劣:意思是如果您不想 NULL
作为结果,您可以使用 coalesce()
将其转换为 0
或您选择的其他输出。但是您需要将其应用于 数组元素 而不是数组,在您的情况下,数组不会提供解决方案。
文档说,当您聚合零行时,您会得到一个空值,关于使用 COALESCE
的说明是针对这种特定情况。
这不适用于您的查询,因为 LEFT JOIN
的行为方式 - 当它找到 零 匹配行时,它 returns 一个行,用空值填充(一个空行的集合是一个包含一个空元素的数组)。
您可能会想在输出中盲目地将 [NULL]
替换为 []
,但这样您就无法区分 没有标签的对象 和 标记的对象,其中 tags.tag
为 null。您的应用程序逻辑 and/or 完整性约束可能不允许第二种情况,但如果它确实设法潜入,那就更有理由不抑制空标记。
您可以通过检查连接条件另一端的字段是否为 null 来识别没有标签的对象(或者一般来说,告诉 LEFT JOIN
没有找到匹配项)。所以在你的情况下,只需替换
array_agg(tags.tag)
和
CASE
WHEN taggings.object_id IS NULL
THEN ARRAY[]::text[]
ELSE array_agg(tags.tag)
END
另一个选项可能是 array_remove(..., NULL)
(introduced in 9.3) 如果 tags.tag
是 NOT NULL
(否则您可能希望在数组中保留 NULL
值,但在那种情况下,由于 LEFT JOIN
):
NULL
标签和 NULL
标签
SELECT objects.*,
array_remove(array_agg(tags.tag), NULL) AS tags,
FROM objects
LEFT JOIN taggings ON objects.id = taggings.object_id
LEFT JOIN tags ON tags.id = taggings.tag_id
如果没有找到标签,则返回一个空数组。
从 9.4 开始,可以限制聚合函数调用只处理符合特定条件的行:array_agg(tags.tag) filter (where tags.tag is not null)
也许这个答案有点晚了,但我想与您分享另一种查询策略也是可能的:在单独的(公共)table 表达式中执行聚合。
WITH cte_tags AS (
SELECT
taggings.object_id,
array_agg(tags.tag) AS tags
FROM
taggings
INNER JOIN tags ON tags.id = taggings.tag_id
GROUP BY
taggings.object_id
)
SELECT
objects.*,
cte_tags.tags
FROM
objects
LEFT JOIN cte_tags ON cte_tags.object_id = objects.id
您现在将得到 NULL 而不是数组,而不是包含单个元素 NULL 的数组。
如果你真的想要一个空数组而不是结果中的 NULL,你可以使用 COALESCE
函数...:[=14=]
WITH cte_tags AS (
SELECT
taggings.object_id,
array_agg(tags.tag) AS tags
FROM
taggings
INNER JOIN tags ON tags.id = taggings.tag_id
GROUP BY
taggings.object_id
)
SELECT
objects.*,
COALESCE(cte_tags.tags, '{}') AS tags
FROM
objects
LEFT JOIN cte_tags ON cte_tags.object_id = objects.id
...或使用数组到数组的串联:
WITH cte_tags AS (
SELECT
taggings.object_id,
array_agg(tags.tag) AS tags
FROM
taggings
INNER JOIN tags ON tags.id = taggings.tag_id
GROUP BY
taggings.object_id
)
SELECT
objects.*,
cte_tags.tags || '{}' AS tags
FROM
objects
LEFT JOIN cte_tags ON cte_tags.object_id = objects.id
这个怎么样:
COALESCE(NULLIF(array_agg(tags.tag), '{NULL}'), '{}') AS tags,
似乎有效。
我换了
array_to_json(array_agg(col_name))
和
array_to_json(coalesce(array_agg(col_name), ARRAY[]::record[]))
所以我没有返回空 JSON 值,而是得到一个空的 JSON 数组