Как развернуть все узлы дерева JSON в PostgreSQL? - PullRequest
0 голосов
/ 14 апреля 2019

Давайте создадим таблицу со столбцом jsonb, которая представляет древовидную структуру узлов. Создать простой список всех узлов дерева, по одному на строку, легко в случае простой древовидной структуры с использованием удивительных рекурсивных CTE:

CREATE TEMPORARY TABLE api_schema (id INT, content JSONB);
INSERT INTO api_schema VALUES (1, '[{"name": "A", "category": "tuple", "children": [{"name": "B", "category": "datapoint"}, {"name": "C", "category": "datapoint"}]}]');
INSERT INTO api_schema VALUES (2, '[{"name": "D", "category": "tuple", "children": [{"name": "E", "category": "tuple", "children": [{"name": "F", "category": "datapoint"}]}]}]');

WITH RECURSIVE schema_objects (id, object) AS (
  SELECT id, jsonb_array_elements(content) FROM api_schema
  UNION
  SELECT id, jsonb_array_elements(object->'children') FROM schema_objects
  WHERE object->>'category' != 'datapoint'
) SELECT * FROM schema_objects;

Самое сложное - это когда требуется больше логики в формуле рекурсии. В моем случае, кроме категорий datapoint (без детей) и tuple (потомки - это список), существует категория multivalue (потомки - это один узел). Как заставить CTE обращаться с этим делом?

Наивное переписывание CTE таково:

INSERT INTO api_schema VALUES (3, '[{"name": "D", "category": "multivalue", "children": {"name": "E", "category": "tuple", "children": [{"name": "F", "category": "datapoint"}]}}]');

WITH RECURSIVE schema_objects (id, object) AS (
  SELECT id, jsonb_array_elements(content) FROM api_schema
  UNION
  SELECT id, CASE WHEN jsonb_typeof(object->'children') = 'array'
               THEN jsonb_array_elements(object->'children')
               ELSE object->'children'
             END AS object
  FROM schema_objects
  WHERE object->>'category' != 'datapoint'
) SELECT * FROM schema_objects;

Однако проблема в том, что это не работает в Postgres 10:

ERROR:  set-returning functions are not allowed in CASE

Можем ли мы сделать два SELECT, каждый из которых охватывает отдельную категорию? Это не разрешено:

WITH RECURSIVE schema_objects (id, object) AS (
  SELECT id, jsonb_array_elements(content) FROM api_schema
  UNION
  (SELECT id, jsonb_array_elements(object->'children') FROM schema_objects WHERE object->>'category' = 'tuple'
   UNION
   SELECT id, object->'children' FROM schema_objects WHERE object->>'category' = 'multivalue')
) SELECT * FROM schema_objects WHERE id=1;

ERROR:  recursive reference to query "schema_objects" must not appear more than once

Идея, распространяющаяся по Интернету, заключается в использовании CTE для выделения CASE, но мы уже внутри CTE, так что это даже не компилируется:

WITH RECURSIVE schema_objects (id, object) AS (
  SELECT id, jsonb_array_elements(content) FROM api_schema
  UNION
  WITH schema_children (id, children) AS (
    SELECT CASE jsonb_typeof(object->'children') WHEN 'array' THEN object->'children' ELSE jsonb_build_array(object->'children') END AS children
    FROM schema_objects
    WHERE object->>'category' != 'datapoint'
  )
  SELECT id, jsonb_array_elements(children)
  FROM schema_children
) SELECT * FROM schema_objects WHERE id=1;

Postgres также предлагает использовать боковое FROM, но не ясно, как составить его в ситуации с одной «таблицей».

1 Ответ

0 голосов
/ 14 апреля 2019

Я обнаружил ошибку в своей ранней попытке при составлении вопроса :). Использование подвыбора во FROM делает свое дело, возвращая не массив в виде массива:

WITH RECURSIVE schema_objects (id, object) AS (
  SELECT id, jsonb_array_elements(content) FROM api_schema

  UNION

  SELECT id, jsonb_array_elements(children)
  FROM (SELECT id, CASE jsonb_typeof(object->'children') WHEN 'array' THEN object->'children' ELSE jsonb_build_array(object->'children') END AS children
    FROM schema_objects
    WHERE object->>'category' != 'datapoint'
  ) s
) SELECT * FROM schema_objects

Однако, один общий недостаток этого подхода, который довольно огромен, заключается в том, что он очень медленный - с деревьями 10000 ~ 30 узлов, я смотрю на запросы, занимающие минут .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...