PostgreSQL中的递归JSON生成

PostgreSQL中的递归JSON生成,json,postgresql,recursion,postgresql-9.5,Json,Postgresql,Recursion,Postgresql 9.5,我在PostgreSQL 9.5服务器中有以下表格: 值得注意的结构性问题是,位置在理论上是无限递归的。我需要从根位置生成一条JSON消息,递归到所有子位置;每个位置都有一些属性,一组库存项目和一组子位置 如何为此创建性能查询?我正在查看各种PostgreSQL JSON函数、横向关键字、CTE,并且有点困惑。我用非递归查询完成了JSON输出,但不确定如何干净地处理递归 以下是一个示例输出: { "id": 1000, "name": "By Location", "type":

我在PostgreSQL 9.5服务器中有以下表格:

值得注意的结构性问题是,
位置
在理论上是无限递归的。我需要从根
位置
生成一条JSON消息,递归到所有子位置;每个
位置
都有一些属性,一组
库存
项目和一组子
位置

如何为此创建性能查询?我正在查看各种PostgreSQL JSON函数、横向关键字、CTE,并且有点困惑。我用非递归查询完成了JSON输出,但不确定如何干净地处理递归

以下是一个示例输出:

{
  "id": 1000,
  "name": "By Location",
  "type": "SITE",
  "locations": [
    {
      "id": 1005,
      "name": "Storage A",
      "type": "STOR",
      "locations": [ ...(same schema as parent)... ],
      "inventories": [ ...(see below for schema)... ]
    },
    {
      "id": 1017,
      "name": "Storage B",
      "name": "COLD",
      "locations": [ ...(same schema as parent)... ],
      "inventories": [...(see below for schema)... ]
    }
  ],
  "inventories": [
    {
      "id": 5340,
      "product_id": 9120,
      "name": "Product X",
      "thumb": "https://example.com/api/images/nnnn.jpg",
      "sort_order": 1,
      "par_level": 3.5,
      "created": 1452898800,
      "updated": 1453071600,
      "measures": [
        {"id": 3498, "quantity": 2.25, "created": 1453071600, "updated": 1453071600},
        {"id": 3456, "quantity": 3.25, "created": 1452898800, "updated": 1452898800}
      ]
    }
  ]
}

让我们把它打碎。首先,您将使用嵌套子查询来创建嵌套数组。通用表表达式可能会有所帮助

其他技巧是row_to_json和json_agg

第一个问题是,row_to_json需要表作为参数来返回正确的标签

select json_agg(locations) from locations
将为每一行返回一个json对象。要仅使用某些字段,您需要创建一个类型并对其进行强制转换,或者使用CTE和上面的语法。在大多数情况下我都会使用CTE

因此,您将得到如下结果:

WITH lowlevel1 AS 
( 
       SELECT a, 
          b, 
          c 
       FROM   tab1) ,lowlevel2 AS 
( 
       SELECT b, 
          c, 
          d 
       FROM   tab2) ,midlevel1 AS 
( 
        SELECT          e, 
                f, 
                g, 
                json_agg(lowlevel1) AS lab1, 
                json_agg(lowlevel2) AS lab2 
        FROM            tab3 
        LEFT OUTER JOIN lowlevel1 
        ON              tab3.id = lowlevel1.parent 
        LEFT OUTER JOIN lowlevel2 
        ON              tab3.id = lovlevel2.parent)
SELECT row_to_json(midlevel1) from midlevel1
或者在最后一行使用
json\u agg(midlevel1)
而不是
row\u to_json(midlevel1)
返回所有行的一个数组

CTE还支持使用
递归
修饰符进行回归。但是,它返回一个包含回归结果的表,而不是嵌套的JSON结构。因此,您可能需要显式地编写所需嵌套级别的代码


如果元素不存在,Postgres将返回null。例如,没有子位置的列表将返回
“位置”:[null]
。为了用更有意义的结果来代替它,可以使用
case when then else'[]end
if then else'[]end
。第一个是“搜索案例”,其中每个测试都是布尔表达式

我最终创建了三个函数。也许可以做得更少,但是这些函数可以在其他查询中重用。基本上,JSON输出中的任何位置都应该有一个值数组,该数组由一个函数处理,该函数返回一个记录集,该记录集获取
JSON\u agg()
'ed

CREATE OR REPLACE FUNCTION get_measures_by_inventory_as_json(invid UUID, del TIMESTAMP WITH TIME ZONE DEFAULT now())
RETURNS TABLE(inventory_id UUID, measure_json JSON)
AS $$
  -- returns a JSONified record per measure tied to an inventory record
  SELECT m.inventory_id, json_build_object(
    'id', m.id, 
    'quantity', m.quantity,
    'read', TRUNC(EXTRACT(EPOCH FROM m.read_date)),
    'created', TRUNC(EXTRACT(EPOCH FROM m.created)), 
    'updated', TRUNC(EXTRACT(EPOCH FROM m.updated)),
    'deleted', TRUNC(EXTRACT(EPOCH FROM m.deleted))
  )
  FROM measure m
  WHERE m.inventory_id = invid
  AND (m.deleted >= del);
$$
LANGUAGE sql;



CREATE OR REPLACE FUNCTION get_inventories_by_location_as_json(locid UUID, del TIMESTAMP WITH TIME ZONE DEFAULT now())
RETURNS TABLE(location_id UUID, inventory_json JSON)
AS $$
  -- returns a JSONified set of inventory items, with product info and measures, given a location
  SELECT i.location_id, json_build_object(
    'id', i.id,
    'product_id', p.id,
    'name', p.name,
    'mass_quantity', p.mass_quantity,
    'mass_unit', um.code,
    'count_unit', uc.code,
    'thumb', p.product_picture_uri,
    'sort_order', i.sort_order,
    'par_level', i.par_level,
    'created', TRUNC(EXTRACT(EPOCH FROM i.created)),
    'updated', TRUNC(EXTRACT(EPOCH FROM i.updated)),
    'deleted', TRUNC(EXTRACT(EPOCH FROM i.deleted)),
    'measures', COALESCE((SELECT json_agg(measure_json) FROM get_measures_by_inventory_as_json(i.id)), '[]')::json
  )
  FROM inventory i 
  INNER JOIN product p ON i.product_id = p.id
  LEFT JOIN unit um ON p.mass_unit_id = um.id
  LEFT JOIN unit uc ON p.count_unit_id = uc.id
  WHERE i.location_id = locid
  AND i.deleted >= del
  AND p.deleted >= del;
$$
LANGUAGE sql;



CREATE OR REPLACE FUNCTION get_inventories_recursive_as_json(locid UUID[], del TIMESTAMP WITH TIME ZONE DEFAULT now())
RETURNS JSON
AS $$
  -- returns JSONified location info and inventories in that location
  -- and recurses into child locations, showing the same
  SELECT json_agg(loc) FROM (
    SELECT l.id, array_agg(c.id), json_build_object(
    'id', l.id,
    'name', l.name,
    'type', t.code,
    'locations', get_inventories_recursive_as_json(array_agg(c.id)),
    'inventories', COALESCE((SELECT json_agg(inventory_json) FROM get_inventories_by_location_as_json(l.id)),'[]')::json
    ) AS loc
    FROM location l
    LEFT OUTER JOIN location c ON l.id = c.parent_id
    INNER JOIN location_type t ON l.location_type_id = t.id
    WHERE l.id = ANY(locid)
    AND l.deleted >= del
    GROUP BY l.id, l.name, t.code
  ) AS out;
$$
LANGUAGE sql;
试图通过一个CTE来实现它,这个CTE本来是非常优雅的,但是在没有遇到与无法在递归中聚合相关的错误的情况下,无法找到如何实现它

WITH RECURSIVE locations AS (
  WITH inventories AS (
    WITH measures AS (
     SELECT m.inventory_id, json_agg(json_build_object(
        'id', m.id, 
        'quantity', m.quantity,
        'read', TRUNC(EXTRACT(EPOCH FROM m.read_date)),
        'created', TRUNC(EXTRACT(EPOCH FROM m.created)), 
        'updated', TRUNC(EXTRACT(EPOCH FROM m.updated)),
        'deleted', TRUNC(EXTRACT(EPOCH FROM m.deleted))
      )) as measures
      FROM measure m
      GROUP BY m.inventory_id
    )
    SELECT i.location_id, json_agg(json_build_object(
      'id', i.id,
      'product_id', p.id,
      'name', p.name,
      'mass_quantity', p.mass_quantity,
      'mass_unit', um.code,
      'count_unit', uc.code,
      'thumb', p.product_picture_uri,
      'sort_order', i.sort_order,
      'par_level', i.par_level,
      'created', TRUNC(EXTRACT(EPOCH FROM i.created)),
      'updated', TRUNC(EXTRACT(EPOCH FROM i.updated)),
      'deleted', TRUNC(EXTRACT(EPOCH FROM i.deleted)),
      'measures', COALESCE(m.measures, '[]')
    )) AS inventories
    FROM inventory i 
    INNER JOIN product p ON i.product_id = p.id
    LEFT JOIN unit um ON p.mass_unit_id = um.id
    LEFT JOIN unit uc ON p.count_unit_id = uc.id
    LEFT JOIN measures m ON i.id = m.inventory_id 
    GROUP BY i.location_id
  )

  SELECT null as id, null as name, null as type, null as inventories
  FROM location l
  INNER JOIN location_type t ON l.location_type_id = t.id
  LEFT OUTER JOIN inventories i ON l.id = i.location_id
  GROUP BY l.parent_id

  /*UNION ALL

  SELECT p.id, p.parent_id, p.name, t.code, COALESCE(i.inventories, '[]')::jsonb AS inventories, json_agg(row_to_json(c.*))
  FROM location p
  INNER JOIN location_type t ON p.location_type_id = t.id
  LEFT OUTER JOIN inventories i ON p.id = i.location_id
  INNER JOIN locations c ON p.id = c.parent_id
  GROUP BY p.id, p.name, t.code, COALESCE(i.inventories, '[]')::jsonb*/

)
SELECT * FROM locations