Postgresql递归函数

标签 postgresql

我在 PostgreSQL 中有以下递归函数。但我想知道它是如何工作的

  1. 执行顺序(先按每个根节点深入层次结构,或先列出所有根节点,然后转到下一层。)
  2. 它是如何从这个函数中分离出来的。

谁能帮我解释一下?

CREATE OR REPLACE FUNCTION build_hierarchey(location_id int) RETURNS SETOF jsonb 
AS $BODY$
    BEGIN
        RETURN QUERY 
        SELECT
            CASE WHEN COUNT(x) > 0 
                THEN ((to_jsonb(t) || jsonb_build_object('Children', jsonb_agg(f.x)))
                ELSE to_jsonb(t)
            END
        FROM "Locations" t
        LEFT JOIN build_hierarchey(t."Id") AS f(x) ON true
        WHERE t."ParentLocationId" = location_id OR (location_id IS null AND t."ParentLocationId" IS null)
        GROUP BY t."Id", t."Name";                                                       
    END;
$BODY$ LANGUAGE 'plpgsql'

这就是我使用它的方式。

select jsonb_agg(build_hierarchey) from build_hierarchey(null::int)

结果为 json (hirarchey)

[
  {
    "Id": 11,
    "Name": "Zone-C",
    "Children": [
      {
        "Id": 23,
        "Name": "01-C",
        "CategoryId": null
      },
      {
        "Id": 20,
        "Name": "01-A",
        "CategoryId": null
      }
    ],
    "CategoryId": null
  },
  {
    "Id": 19,
    "Name": "Zone-K",
    "CategoryId": null
  },
  {
    "Id": 1,
    "Name": "ccc",
    "Children": [
      {
        "Id": 3,
        "Name": "01-A",
        "CategoryId": null
      },
      {
        "Id": 5,
        "Name": "01-C",
        "Children": [
          {
            "Id": 8,
            "Name": "01-C-03",
            "CategoryId": null
          },
          {
            "Id": 7,
            "Name": "01-C-02",
            "CategoryId": null
          },
          {
            "Id": 6,
            "Name": "01-C-01",
            "CategoryId": null
          }
        ],
        "CategoryId": null
      },
      {
        "Id": 4,
        "Name": "01-B",
        "CategoryId": null
      }
    ],
    "CategoryId": null
  },
  {
    "Id": 18,
    "Name": "Zone-J",
    "CategoryId": null
  },
  {
    "Id": 2,
    "Name": "Zone-B",
    "Children": [
      {
        "Id": 10,
        "Name": "02-A",
        "CategoryId": null
      },
      {
        "Id": 9,
        "Name": "01-A",
        "CategoryId": null
      }
    ],
    "CategoryId": null
  },
  {
    "Id": 16,
    "Name": "Zone-H",
    "CategoryId": null
  },
  {
    "Id": 15,
    "Name": "Zone-G",
    "CategoryId": null
  },
  {
    "Id": 14,
    "Name": "Zone-F",
    "CategoryId": null
  },
  {
    "Id": 17,
    "Name": "Zone-I",
    "CategoryId": null
  },
  {
    "Id": 22,
    "Name": "Zone-AA",
    "CategoryId": null
  }
]

这是“位置”表。 (自引用)

SELECT "Id", "Name", "ParentLocationId" FROM "Locations"

ID  Name      ParentLocationId
1   "ccc"       
2   "Zone-B"    
3   "01-A"      1
4   "01-B"      1
5   "01-C"      1
6   "01-C-01"   5
7   "01-C-02"   5
8   "01-C-03"   5
9   "01-A"      2
10  "02-A"      2
11  "Zone-C"    
14  "Zone-F"    
15  "Zone-G"    

最佳答案

与许多递归函数一样,我们可以概念上将其分解为几个步骤。

首先,我们有基本情况 - 当给定 null 作为输入时,查询选择所有没有父级的 locations:

SELECT
    ...
FROM "Locations" t
WHERE location_id IS null AND t."ParentLocationId" IS null

接下来,我们有重复的情况 - 当给定一个 ID 作为输入时,查询选择所有具有该 ID 作为其父级的 locations:

SELECT
    ...
FROM "Locations" t
WHERE t."ParentLocationId" = location_id

然后我们添加递归 - 当返回上述任一列表时,也再次调用该函数,找到每个 ID:

SELECT
    ...
FROM "Locations" t
LEFT JOIN build_hierarchey(t."Id") AS f(x) ON true

存在 ON true 子句是因为必须有一些条件,但要连接的实际行由传入函数的 t."Id" 决定.它需要是 LEFT JOIN,因为该函数有时不会生成任何行;例如使用您的示例数据 build_hierarchey(3) 将不返回任何行,但我们不希望从 build_hierarchey(1) 的结果中排除位置 3。

现在我们需要将当前查询的结果(t)与递归函数调用的结果(f)结合起来:

SELECT
    CASE WHEN COUNT(x) > 0 
        THEN ((to_jsonb(t) || jsonb_build_object('Children', jsonb_agg(f.x)))
        ELSE to_jsonb(t)
    END
FROM ... as t
LEFT JOIN ... as f(x)
GROUP BY t."Id", t."Name";

同样,这必须处理函数未返回任何行的情况。剩下的只是格式化:将每组结果扁平化为一个 JSON 对象,并将它们组合成所需的结构。

现在,回答您的问题:

the sequence of execution (it goes deep hierarchy by each root node first or list all root node first then go to next level.)

在 SQL 中,谈论执行顺序 很少有用,因为 DBMS 不会一次生成一行并吐出结果,它会尝试尽可能高效地生成请求的数据尽可能。据我所知,DBMS 可以自由选择是否将某一级别的所有行都加载到内存中,并在每个行上运行该函数;或者函数是否在遇到每一行时深度优先运行。

一般来说,唯一能保证的就是结果会按照查询请求的顺序出来。在这种情况下,没有 ORDER BY 子句,因此每个级别的实际结果顺序也完全未定义 - DBMS 将在运行时选择最方便的顺序查询。

how it breaks from this function.

这个函数实际上并不能保证退出;它的退出条件依赖于您的数据不包含循环的假设 - 也就是说,它最终将到达一个没有“子节点”的节点(即,一个从未出现在 ParentLocationId 列中的 ID)。

为了演示一个循环,请考虑以下数据:

ID  Name      ParentLocationId
1   "A"       3
2   "B"       1
3   "C"       2

build_hierarchey(1) 的调用将按如下方式进行:

  • 获取父节点为 1 的节点:找到 ID=2
  • 获取父节点 2 的节点:找到 ID=3
  • 获取父节点为 3 的节点:找到 ID=1
  • 获取父节点 1 的节点:无限循环

这可能在应用程序的其他地方被阻止了,但我们还有一个额外的保护:通过调用 build_hierarchey(null) 我们永远不会遇到这样的循环。我们可以证明如下:

  • 每个节点要么只有一个父节点,要么没有父节点
  • 一个循环由跟随父节点和到达同一个节点组成
  • 要使一个节点成为循环的一部分,它必须有一个父节点
  • 因此以null为父节点的节点不是循环的一部分
  • 如果一个节点在一个循环中,它的父节点必须在同一个循环中(循环 A-B-C-A 可以扩展到 A-B-C-A-B,依此类推)
  • 因此,循环中的节点不能有其父级为null 的父级,因为该父级不能成为循环的一部分
  • 因此,从父节点为 null 的节点开始(build_hierarchey(null) 所做的)永远不会达到循环,即使表中存在循环

关于Postgresql递归函数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55087629/

相关文章:

python - 俄语中的postgresql psycopg2搜索不起作用

postgresql - 基准 Amazon Redshift JSON_EXTRACT_PATH_TEXT

postgresql - jOOQ 是否支持 PostgreSQL 的 C 风格转义字符串?

sql - 返回数据立方体的 PostgreSQL 函数

json - PostgreSQL:如何检查 JSON 数组中的任何元素是否符合条件

PostgreSQL 大小差异

string - PostgreSQL : Find the string with the closest substring match

ruby-on-rails - 生产/heroku 失败:WHERE a.attrelid = '"schools"'::regclass

postgresql - 当我第二次使用完全相同的参数调用 IMMUTABLE 函数时,为什么计划时间会加倍?

sql - PostgresQL 外键语法错误