简体   繁体   English

带点符号的 Postgres 查询行构建一个嵌套的 json 作为查询结果

[英]Postgres query row with dot notaiton to build a nested json as query result

I have a flat table with dot-notationed columns, similar to the following:我有一个带有点符号列的平面表,类似于以下内容:

title.en标题.en title.fr标题.fr category.name.en类别名称.en category.name.fr类别名称.fr category.acronym.en类别.acronym.en category.acronym.fr类别.acronym.fr
English Title英文标题 French Title法语标题 English Category Name英文类别名称 French Category Name法语类别名称 English Category Acronym英文分类缩写 French Category Acronym法语类别首字母缩略词

the dot notations are there to inidicate a nested object, so each dot makes a nesting level of json. According to this, I want to be able to query this result from the table in json(b):点符号在那里表示嵌套的 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"
      }
    }
  }
}

I know it's possible to manually do this using nested "jsonb_build_object" functions, but want to know if it's possible to shortcut it using dot notations in column names.我知道可以使用嵌套的“jsonb_build_object”函数手动执行此操作,但想知道是否可以使用列名中的点符号来简化它。

Let's consider that data is the table name.让我们考虑data是表名。 The solution below requires several steps:下面的解决方案需要几个步骤:

1. Get the column names from the database schema: 1. 从数据库模式中获取列名:

SELECT table_name, column_name
  FROM information_schema.columns
 WHERE table_name = 'data'

Result:结果:

table_name表名 column_name列名
data数据 title.en标题.en
data数据 title.fr标题.fr
data数据 category.name.en类别名称.en
data数据 category.name.fr类别名称.fr
data数据 category.acronym.en类别.acronym.en
data数据 category.acronym.fr类别.acronym.fr

2. Convert the dot-notationed column names into the target jsonb format: 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'

Result:结果:

jsonb jsonb
{"data": {"title": {"en": "title.en"}}} {“数据”:{“标题”:{“en”:“title.en”}}}
{"data": {"title": {"fr": "title.fr"}}} {“数据”:{“标题”:{“fr”:“title.fr”}}}
{"data": {"category": {"name": {"en": "category.name.en"}}}} {“数据”:{“类别”:{“名称”:{“en”:“category.name.en”}}}}
{"data": {"category": {"name": {"fr": "category.name.fr"}}}} {“数据”:{“类别”:{“名称”:{“fr”:“category.name.fr”}}}}
{"data": {"category": {"acronym": {"en": "category.acronym.en"}}}} {“数据”:{“类别”:{“首字母缩略词”:{“en”:“category.acronym.en”}}}}
{"data": {"category": {"acronym": {"fr": "category.acronym.fr"}}}} {“数据”:{“类别”:{“首字母缩略词”:{“fr”:“category.acronym.fr”}}}}

3. Merge the jsonb column names all together to build the final jsonb template: 3. 将 jsonb 列名合并在一起以构建最终的 jsonb 模板:

In this step, we need to create a specific aggregate function jsonb_merge_agg :在这一步中,我们需要创建一个特定的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'

Result:结果:

jsonb_template 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"}}}} {"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. Replace in the jsonb_template every column name by the corresponding value: 4. 将 jsonb_template 中的每个列名替换为相应的值:

In this step, we need to create another specific aggregate function replace_agg based on the standard replace function:在这一步中,我们需要在标准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. Build the final query: 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

Final Result:最后结果:

final_result最后结果
{"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"}}}} {"data": {"title": {"en": "英文标题", "fr": "法文标题"}, "category": {"name": {"en": "英文类别名称", "fr": "French Category Name"}, "acronym": {"en": "English Category Acronym", "fr": "French Category Acronym"}}}}

all the test results in dbfiddle dbfiddle中的所有测试结果

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM