[英]Postgres query row with dot notaiton to build a nested json as query result
我有一个带有点符号列的平面表,类似于以下内容:
标题.en | 标题.fr | 类别名称.en | 类别名称.fr | 类别.acronym.en | 类别.acronym.fr |
---|---|---|---|---|---|
英文标题 | 法语标题 | 英文类别名称 | 法语类别名称 | 英文分类缩写 | 法语类别首字母缩略词 |
点符号在那里表示嵌套的 object,因此每个点的嵌套级别为 json。据此,我希望能够从 json(b) 中的表中查询此结果:
{
"data": {
"title": {
"en": "English Title",
"fr": "French Title"
},
"category": {
"name": {
"en": "English Category Name",
"fr": "French Category Name"
},
"acronym": {
"en": "English Category Acronym",
"fr": "French Category Acronym"
}
}
}
}
我知道可以使用嵌套的“jsonb_build_object”函数手动执行此操作,但想知道是否可以使用列名中的点符号来简化它。
让我们考虑data
是表名。 下面的解决方案需要几个步骤:
1. 从数据库模式中获取列名:
SELECT table_name, column_name
FROM information_schema.columns
WHERE table_name = 'data'
结果:
表名 | 列名 |
---|---|
数据 | 标题.en |
数据 | 标题.fr |
数据 | 类别名称.en |
数据 | 类别名称.fr |
数据 | 类别.acronym.en |
数据 | 类别.acronym.fr |
2. 将点符号列名转换为目标 jsonb 格式:
SELECT ('{"' || table_name || '": {"' || replace(column_name, '.', '": {"') || '": "' || column_name || '"' || repeat('}', regexp_count(column_name, '\.')+2)) :: jsonb
FROM information_schema.columns
WHERE table_name = 'data'
结果:
jsonb |
---|
{“数据”:{“标题”:{“en”:“title.en”}}} |
{“数据”:{“标题”:{“fr”:“title.fr”}}} |
{“数据”:{“类别”:{“名称”:{“en”:“category.name.en”}}}} |
{“数据”:{“类别”:{“名称”:{“fr”:“category.name.fr”}}}} |
{“数据”:{“类别”:{“首字母缩略词”:{“en”:“category.acronym.en”}}}} |
{“数据”:{“类别”:{“首字母缩略词”:{“fr”:“category.acronym.fr”}}}} |
3. 将 jsonb 列名合并在一起以构建最终的 jsonb 模板:
在这一步中,我们需要创建一个特定的aggregate
function jsonb_merge_agg
:
CREATE OR REPLACE FUNCTION jsonb_merge(x jsonb, y jsonb) RETURNS jsonb LANGUAGE sql AS $$
SELECT jsonb_object_agg
( COALESCE(x1->>'key', y1->>'key')
, CASE
WHEN x1->>'key' IS NULL THEN y1->'value'
WHEN y1->>'key' IS NULL THEN x1->'value'
WHEN jsonb_typeof(x1->'value') = 'object' AND jsonb_typeof(y1->'value') = 'object' THEN jsonb_merge(x1->'value', y1->'value')
ELSE jsonb_build_array(x1->'value', y1->'value')
END
)
FROM jsonb_path_query(x,'$[*].keyvalue()') AS x1
FULL OUTER JOIN jsonb_path_query(y,'$[*].keyvalue()') AS y1
ON y1->>'key' = x1->>'key' ;
$$ ;
CREATE OR REPLACE AGGREGATE jsonb_merge_agg(x jsonb)
(stype = jsonb, sfunc = jsonb_merge) ;
SELECT jsonb_merge_agg(('{"' || table_name || '": {"' || replace(column_name, '.', '": {"') || '": "' || column_name || '"' || repeat('}', regexp_count(column_name, '\.')+2)) :: jsonb) AS jsonb_template
FROM information_schema.columns
WHERE table_name = 'data'
结果:
jsonb_模板 |
---|
{"data": {"title": {"en": "title.en", "fr": "title.fr"}, "category": {"name": {"en": "category.name .en", "fr": "category.name.fr"}, "acronym": {"en": "category.acronym.en", "fr": "category.acronym.fr"}}}} |
4. 将 jsonb_template 中的每个列名替换为相应的值:
在这一步中,我们需要在标准replace
function 的基础上创建另一个特定的聚合 function replace_agg
:
CREATE OR REPLACE FUNCTION replace(x text, y text, old text, new text) RETURNS text LANGUAGE sql AS $$
SELECT replace(COALESCE(x, y), old, new) ; $$ ;
CREATE OR REPLACE AGGREGATE replace_agg(x text, old text, new text)
(stype = text, sfunc = replace) ;
5. 构建最终查询:
WITH list AS (
SELECT jsonb_merge_agg(('{"' || table_name || '": {"' || replace(column_name, '.', '": {"') || '": "' || column_name || '"' || repeat('}', regexp_count(column_name, '\.')+2)) :: jsonb) AS jsonb_template
FROM information_schema.columns
WHERE table_name = 'data'
)
SELECT replace_agg( l.jsonb_template :: text
, content->>'key'
, content->>'value'
) AS final_result
FROM list AS l
CROSS JOIN data AS d
CROSS JOIN LATERAL jsonb_path_query(to_jsonb(d), '$.keyvalue()') AS content
GROUP BY d
最后结果:
最后结果 |
---|
{"data": {"title": {"en": "英文标题", "fr": "法文标题"}, "category": {"name": {"en": "英文类别名称", "fr": "French Category Name"}, "acronym": {"en": "English Category Acronym", "fr": "French Category Acronym"}}}} |
dbfiddle中的所有测试结果
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.