Postgres 从查询或函数返回具有两层嵌套的 json

问题描述 投票:0回答:1

我有这三张桌子:

“课程表”

id 课程标题 章节数
1 课程1 5
2 课程2 3

“章节表”

id 章节标题 子章节数 course_id --参考课程(id)
1 第 1 章 2 1
2 第2章 4 1
3 第3章 1 1
4 第4章 3 1
5 第5章 0 1
6 第 1 章 4 2
7 第2章 5 2
8 第3章 0 2

“子章节表”

id 子章节标题 chapter_id --参考章节(id)
1 第 1 章 1
2 第2章 1
3 第 1 章 2
4 第2章 2
5 第3章 2
6 第 4 章 2
7 第 1 章 3
... ........................ ...

我希望结果是这样的 JSON 格式:

{
  "course": [
    {
      "course_id": 1,
      "course_title": "course 1",
      "chapters_count": 5,
      "chapters": [
        {
          "chapter_id": 1,
          "chapter_title": "chapter 1",
          "sub_chapters": [
            {
              "sub_chapter_id": 1,
              "sub_chapter_title": "sub chapter 1"
            },
            {
              "sub_chapter_id": 2,
              "sub_chapter_title": "sub chapter 2"
            },
            {
              "sub_chapter_id": 3,
              "sub_chapter_title": "sub chapter 3"
            }
          ]
        },
        {
          "chapter_id": 2,
          "chapter_title": "chapter 2",
          "sub_chapters": [
            {
              "sub_chapter_id": 1,
              "sub_chapter_title": "sub chapter 1"
            },
            {
              "sub_chapter_id": 2,
              "sub_chapter_title": "sub chapter 2"
            }
          ]
        }
      ]
    }
  ]
}
json postgresql
1个回答
0
投票

此类查询最复杂的部分是确定如何构建嵌套聚合,因为 PostgreSQL 不支持嵌套聚合函数。

运行以下 SQL 设置演示环境(注意表不包括下级表关联行的计数):

CREATE TABLE courses (
id integer GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
course_title text NOT NULL UNIQUE
);

CREATE TABLE chapters (
id integer GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
chapter_title text NOT NULL,
course_id integer NOT NULL REFERENCES courses(id),
UNIQUE (course_id, chapter_title)
);

CREATE TABLE sub_chapters (
  id integer GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  sub_chapter_title text NOT NULL,
  chapter_id integer NOT NULL REFERENCES chapters(id),
  UNIQUE (chapter_id, sub_chapter_title)
);

INSERT INTO courses (id, course_title)
OVERRIDING SYSTEM VALUE
VALUES (1, 'course 1'),
       (2, 'course 2');

INSERT INTO chapters (id, chapter_title, course_id)
OVERRIDING SYSTEM VALUE
VALUES (1, 'chapter 1', 1),
       (2, 'chapter 2', 1),
       (3, 'chapter 3', 1),
       (4, 'chapter 4', 1),
       (5, 'chapter 5', 1),
       (6, 'chapter 1', 2),
       (7, 'chapter 2', 2),
       (8, 'chapter 3', 2);

INSERT INTO sub_chapters (id, sub_chapter_title, chapter_id)
OVERRIDING SYSTEM VALUE
VALUES (1, 'sub chapter 1', 1),
       (2, 'sub chapter 2', 1),
       (3, 'sub chapter 1', 2),
       (4, 'sub chapter 2', 2),
       (5, 'sub chapter 3', 2),
       (6, 'sub chapter 4', 2),
       (7, 'sub chapter 1', 3);

--- synchronize ID sequences with inserted rows
SELECT
  c.table_schema,
  c.table_name,
  c.column_name,
  x.sequence_name,
  x.last_value,
  x.max_value,
  SETVAL(x.sequence_name, GREATEST(x.last_value, x.max_value))
FROM
  information_schema.columns c
  CROSS JOIN LATERAL XMLTABLE(
    '/table/row'
    PASSING QUERY_TO_XML(
      FORMAT(
        $$SELECT %1$L AS sequence_name, last_value, (SELECT MAX(%2$I) FROM %3$I.%4$I) AS max_value FROM %1$s$$,
        PG_GET_SERIAL_SEQUENCE(c.table_name, c.column_name),
        c.column_name,
        c.table_schema,
        c.table_name
      ),
      FALSE,
      FALSE,
      ''
    )
    COLUMNS
      sequence_name TEXT,
      LAST_VALUE BIGINT,
      max_value bigint
  ) x
WHERE
  c.table_schema = CURRENT_SCHEMA()
  AND c.table_name IN ('courses', 'chapters', 'sub_chapters')
  AND c.column_name = 'id';

以下查询使用

LATERAL
子查询来获取嵌套聚合:

SELECT
  JSON_STRIP_NULLS(
    JSON_BUILD_OBJECT(
      'course',
      JSON_AGG(
        JSON_BUILD_OBJECT(
          'course_id',
          courses.id,
          'course_title',
          courses.course_title,
          'chapters_count',
          chaps.chapters_count,
          'chapters',
          chaps.chapters
        )
      )
    )
  ) AS courses
FROM
  courses
  CROSS JOIN LATERAL (
    SELECT
      COUNT(*) AS chapters_count,
      JSON_AGG(
        JSON_BUILD_OBJECT(
          'chapter_id',
          chapters.id,
          'chapter_title',
          chapters.chapter_title,
          'sub_chapters_count',
          sc.sub_chapters_count,
          'sub_chapters',
          sc.sub_chapters
        )
      ) AS chapters
    FROM
      chapters
      CROSS JOIN LATERAL (
        SELECT
          COUNT(*) AS sub_chapters_count,
          JSON_AGG(
            JSON_BUILD_OBJECT(
              'sub_chapter_id',
              sub_chapters.id,
              'sub_chapter_title',
              sub_chapters.sub_chapter_title
            )
            ORDER BY
              sub_chapters.id
          ) AS sub_chapters
        FROM
          sub_chapters
        WHERE
          sub_chapters.chapter_id = chapters.id
      ) sc
    WHERE
      chapters.course_id = courses.id
  ) chaps;

结果输出为:

{
    "course": [
        {
            "course_id": 1,
            "course_title": "course 1",
            "chapters_count": 5,
            "chapters": [
                {
                    "chapter_id": 1,
                    "chapter_title": "chapter 1",
                    "sub_chapters_count": 2,
                    "sub_chapters": [
                        {
                            "sub_chapter_id": 1,
                            "sub_chapter_title": "sub chapter 1"
                        },
                        {
                            "sub_chapter_id": 2,
                            "sub_chapter_title": "sub chapter 2"
                        }
                    ]
                },
                {
                    "chapter_id": 2,
                    "chapter_title": "chapter 2",
                    "sub_chapters_count": 4,
                    "sub_chapters": [
                        {
                            "sub_chapter_id": 3,
                            "sub_chapter_title": "sub chapter 1"
                        },
                        {
                            "sub_chapter_id": 4,
                            "sub_chapter_title": "sub chapter 2"
                        },
                        {
                            "sub_chapter_id": 5,
                            "sub_chapter_title": "sub chapter 3"
                        },
                        {
                            "sub_chapter_id": 6,
                            "sub_chapter_title": "sub chapter 4"
                        }
                    ]
                },
                {
                    "chapter_id": 3,
                    "chapter_title": "chapter 3",
                    "sub_chapters_count": 1,
                    "sub_chapters": [
                        {
                            "sub_chapter_id": 7,
                            "sub_chapter_title": "sub chapter 1"
                        }
                    ]
                },
                {
                    "chapter_id": 4,
                    "chapter_title": "chapter 4",
                    "sub_chapters_count": 0
                },
                {
                    "chapter_id": 5,
                    "chapter_title": "chapter 5",
                    "sub_chapters_count": 0
                }
            ]
        },
        {
            "course_id": 2,
            "course_title": "course 2",
            "chapters_count": 3,
            "chapters": [
                {
                    "chapter_id": 6,
                    "chapter_title": "chapter 1",
                    "sub_chapters_count": 0
                },
                {
                    "chapter_id": 7,
                    "chapter_title": "chapter 2",
                    "sub_chapters_count": 0
                },
                {
                    "chapter_id": 8,
                    "chapter_title": "chapter 3",
                    "sub_chapters_count": 0
                }
            ]
        }
    ]
}

JSON_*
功能可以更改为其
JSONB_*
等效项;但是,JSON 输出的元素顺序可能不同。由于可以轻松确定数组中的元素数量,因此也可以删除计数而不会丢失任何信息。

© www.soinside.com 2019 - 2024. All rights reserved.