如何创建一个结构连接行作为嵌套文档 PostgreSQL

Posted

技术标签:

【中文标题】如何创建一个结构连接行作为嵌套文档 PostgreSQL【英文标题】:How to create a structure joined rows as nested documents PostgreSQL 【发布时间】:2021-07-23 00:12:21 【问题描述】:

我在 MongoDB 方面有经验,我正在学习 PostgreSQL。在这种情况下,我将 Node.js 与 pg 库一起使用。

我有 2 个表格:postscmets。我需要做的是进行一个查询,它将返回一个 posts 数组,并且每个返回的 post 都应该有一个嵌套的 cmets 数组。

现在,在 Mongo 中,我只需将帖子存储为 comments 字段,该字段将是对象的嵌套数组。在 PostgreSQL 中,我所做的是让每条评论都有一个 post_id 列,该列引用它的父帖子。我的问题是,如何检索结构类似于 mongo 示例的数组?也就是说,我如何返回一个帖子数组,每个帖子都有一个名为 comments 的字段,该字段将是相应评论行的数组?

现在我正在对 2 个表进行 JOIN,但最终发生的是我得到一个对象数组,其中包含帖子的所有字段以及 cmets 的所有字段。

现在发生了什么:

[
  
     id: 1,
     post_title: "something",
     posted_at: "some date",
     comment_text: "nice post",
     author_id: 31
  ,
  
     id: 1,
     post_title: "something",
     posted_at: "some date",
     comment_text: "i dont like this post",
     author_id: 4
  
]

我想要发生的事情

[
  
     id: 1,
     post_title: "something",
     posted_at: "some date",
     comments: [
       
         comment_text: "nice post",
         author_id: 31
       ,
       
         comment_text: "i dont like this post",
         author_id: 4
       
  ,
]

【问题讨论】:

SQL 集自然不支持嵌套结构。所以,问题变成了为什么需要数据库提供第二个结构?是什么阻止了您的应用程序层将记录集(根据第一个结构)转换为嵌套结构? 我不想这样做的原因是,至少在 mongo 中,在服务器(它是用 C++ 编写的)上做所有关于数据结构的事情要比在客户端(javascript)。我不知道相同的逻辑是否适用于 PostgreSQL,但如果不是,我绝对可以通过手动转换结构来解决我最初的问题。 【参考方案1】:

我猜你的表结构在这里,但它应该足够接近。所以假设这是你的设置:

CREATE TABLE posts (id INTEGER, post_title TEXT, posted_at TEXT);

INSERT INTO posts
VALUES
(1, 'title 1', 'date 1'),
(2, 'title 2', 'date 2'),
(3, 'title 3', 'date 3');

CREATE TABLE comments (post_id INTEGER, comment_text TEXT, author_id INTEGER);

INSERT INTO comments
VALUES
(1, 'comment text 1', 34),
(3, 'comment text 3 a', 45),
(3, 'comment text 3 b', 67);

最简单的方法是这样的:

SELECT JSON_AGG(x ORDER BY id)
FROM (
  SELECT p.id, p.post_title, p.posted_at, JSON_AGG(c) AS comments
  FROM posts p
  LEFT JOIN comments c
      ON p.id = c.post_id
  GROUP BY p.id, p.post_title, p.posted_at
) x;

返回近似你想要的结构:

[
    "id": 1,
    "post_title": "title 1",
    "posted_at": "date 1",
    "comments": [
        "post_id": 1,
        "comment_text": "comment text 1",
        "author_id": 34
    ]
, 
    "id": 2,
    "post_title": "title 2",
    "posted_at": "date 2",
    "comments": [null]
, 
    "id": 3,
    "post_title": "title 3",
    "posted_at": "date 3",
    "comments": [
        "post_id": 3,
        "comment_text": "comment text 3 a",
        "author_id": 45
    , 
        "post_id": 3,
        "comment_text": "comment text 3 b",
        "author_id": 67
    ]
]

我注意到您的 cmets 数组不包含对父帖子 ID 的引用。仅选择字段子集的最佳方式取决于您的用例,但这应该是一种足够高效的方式:

SELECT JSON_AGG(x ORDER BY id)
FROM (
  SELECT
    p.id,
    p.post_title,
    p.posted_at,
    (
      SELECT JSON_AGG(c)
      FROM (
        SELECT c.comment_text, c.author_id
        FROM comments c
        WHERE c.post_id = p.id
      ) c
    ) AS comments
  FROM posts p
) x;

返回

[
    "id": 1,
    "post_title": "title 1",
    "posted_at": "date 1",
    "comments": [
        "comment_text": "comment text 1",
        "author_id": 34
    ]
, 
    "id": 2,
    "post_title": "title 2",
    "posted_at": "date 2",
    "comments": null
, 
    "id": 3,
    "post_title": "title 3",
    "posted_at": "date 3",
    "comments": [
        "comment_text": "comment text 3 a",
        "author_id": 45
    , 
        "comment_text": "comment text 3 b",
        "author_id": 67
    ]
]

显然,post 2 的 cmets 在它们之间有所不同,您必须使用一些逻辑来决定您希望 no cmets 的样子。

【讨论】:

非常感谢!我忘了添加它(抱歉),但 cmets 表确实包含一个引用父帖子的post_id col。 应该注意的是,这样做会将结果从二进制表示形式转换为字符串。这增加了数据库的工作量,增加了网络流量,并且需要在本地解析字符串以创建可用的数据结构。这并不是说这是错误的做法,只是明确指出涉及许多间接费用;它不是免费的。

以上是关于如何创建一个结构连接行作为嵌套文档 PostgreSQL的主要内容,如果未能解决你的问题,请参考以下文章

Elasticsearch & X-Pack:如何从嵌套文档中获取顶点/连接

嵌套结构如何影响 DocumentDB 查询性能?

在 BigQuery 中展平嵌套层次结构

mongodb 命令行用啥命令查询Collection文档结构

在不知道结构的情况下解组嵌套的 json

如何以角度动态创建 n 级嵌套展开/折叠组件