PostgreSQL递归函数

时间:2019-03-10 12:22:45

标签: 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'

这就是我的用法。

  

从build_hierarchey(null :: int)中选择jsonb_agg(build_hierarchey)

结果为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
  }
]

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

  

从“位置”中选择“ ID”,“名称”,“ ParentLocationId”

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"    

1 个答案:

答案 0 :(得分:1)

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

首先,我们有一个基本情况-在给定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对象,然后将它们组合为所需的结构。

现在,您的问题:

  

执行顺序(先由每个根节点进行深层次化,或者先列出所有根节点,然后再进行下一级。)

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

通常来说,唯一可以保证的是结果将按照查询所请求的顺序显示。在这种情况下,没有ORDER BY子句,因此每个级别的结果的实际顺序也是完全不确定的-DBMS将选择运行查询时最方便的顺序。 / p>

  

如何从此功能中断。

实际上不能保证退出此功能;其退出条件取决于您的数据不包含周期的假设,也就是说,它最终将到达没有“子项”(即,一个从未出现在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)就是这样),即使表中存在一个节点也永远不会达到周期