从 PostgreSQL 中的类别数组创建类别树表

Posted

技术标签:

【中文标题】从 PostgreSQL 中的类别数组创建类别树表【英文标题】:Creating a category tree table from an array of categories in PostgreSQL 【发布时间】:2021-02-25 17:03:47 【问题描述】:

如何从类别数组中生成 ids 和 parent_ids。子类别的数量或深度可以是 1-10 级之间的任何值。

示例 PostgreSQL 列。数据类型字符可变数组。

data_column
character varying[]             |               
----------------------------------
[root_1, child_1, childchild_1] |
[root_1, child_1, childchild_2] | 
[root_2, child_2]               | 

我想将数组列转换为如下所示的表格,我假设它称为邻接列表模型。我知道还有嵌套树集模型和物化路径模型。

最终输出表

id | title        | parent_id
------------------------------
1  | root_1       | null
2  | root_2       | null  
3  | child_1      | 1
4  | child_2      | 2 
5  | childchild_1 | 3  
6  | childchild_2 | 3   

最终输出树层次结构

root_1
--child_1
----childchild_1
----childchild_2
root_2
--child_2

【问题讨论】:

【参考方案1】:

step-by-step demo: db<>fiddle

您可以通过recursive CTE 完成此操作

WITH RECURSIVE cte AS 
(  SELECT data[1] as title, 2 as idx, null as parent, data FROM t   -- 1

   UNION

   SELECT data[idx], idx + 1, title, data                           -- 2
   FROM cte
   WHERE idx <= cardinality(data)
)
SELECT DISTINCT                                                     -- 3 
    title,
    parent
FROM cte
    递归的起始查询:获取递归中需要的所有根元素和数据 递归部分:获取新索引的元素并增加索引 递归后:查询最终需要的列。 DISTINCT 删除绑定元素(例如两次相同的 root_1)。

现在您已经创建了层次结构。现在你需要 id。 您可以通过多种不同方式生成它们,例如使用row_number() window function:

WITH RECURSIVE cte AS (...)
SELECT 
    *,
    row_number() OVER ()
FROM (
    SELECT DISTINCT
        title,
        parent
    FROM cte
) s

现在,每一行都有自己的 ID。顺序标准可能会稍作调整。在没有任何进一步信息的情况下,我们几乎没有机会改变这一点。但算法保持不变。

有了每列的id,我们可以创建一个self join,通过parenttitle列加入父id。因为自连接是选择查询的重复,所以将其封装到第二个 CTE 中以避免代码复制是有意义的。最终结果是:

WITH RECURSIVE cte AS 
(  SELECT data[1] as title, 2 as idx, null as parent, data FROM t

   UNION

   SELECT data[idx], idx + 1, title, data
   FROM cte
   WHERE idx <=  cardinality(data)
), numbered AS (
   SELECT 
       *,
       row_number() OVER ()
   FROM (
       SELECT DISTINCT
           title,
           parent
       FROM cte
   ) s
)
SELECT 
    n1.row_number as id,
    n1.title,
    n2.row_number as parent_id
FROM numbered n1
LEFT JOIN numbered n2 ON n1.parent = n2.title

【讨论】:

感谢您提供的非常好的示例和实现,非常感谢。我使用更大的产品类别数据集进行了快速测试,并且由于某种原因得到了一些重复的 id。知道为什么会发生这种情况吗?现在我也有了另一个想法,你知道是否可以使用 sql cte 一次将一个路径/行插入到现有的邻接树表中,以便它一次循环通过一个路径并插入每个节点和父节点如果节点不存在并相应地生成 id 和 parent_id? 我做了更多测试,如果父节点名称在不同路径中相同,则查询似乎给出了重复的子节点 ID,请参阅 dbfiddle。你知道如何解决这个问题吗? link 唷,我猜,这让事情比你上面提到的要复杂得多。据我所知,你没有一棵经典的树。 “附件”有许多可能的父母,而不仅仅是一个。那么,查询应该如何确定它是否是同一个元素。重复来自名称上的合并,这总是一个坏主意。您应该使用唯一 ID,而不是使用您所做的一切。 是的,我同意该示例在这方面有点不完整,但在使用更大的数据集之前没有意识到这一点,您可以在不同的路径中拥有具有相同标题的节点。我认为如果算法会为标题和父级生成另外两列完整路径,那么您可以自行加入路径吗?在最终表中,id 以及 title + parent_id 组合确实是唯一约束,因此兄弟节点不能具有相同的标题,但您可以在不同的路径中拥有具有相同标题的节点。 为了避免同名问题,使用ids即可。永远不要使用名称。他们可以更改可能会破坏您的查询。自加入路径可能会起作用,但听起来不太高效。但是试试看!

以上是关于从 PostgreSQL 中的类别数组创建类别树表的主要内容,如果未能解决你的问题,请参考以下文章

如何从数组中的对象获取帖子类别

从类别树中清除空类别

如何为postgresql中的每个用户返回具有最大值的类别?

PostgreSQL - 树组织

从变量中的MULTIPLE类别创建摘要统计表

如何在 PostgreSQL 中按类别选择具有最大日期组的 id?