在Postgres中收集递归JSON密钥

时间:2015-05-08 20:44:22

标签: json postgresql recursion

我将JSON文档存储在Postgres的JSON数据类型下(Postgres 9.3),我需要以递归的方式从树中收集密钥名称。

例如,给定此JSON树

{
 "files": {
  "folder": {
   "file1": {
    "property": "blah"
   },
   "file2": {
    "property": "blah"
   },
   "file3": {
    "property": "blah"
   },
   "file4": {
    "property": "blah"
   }
 }
},
"software": {
  "apt": {
    "package1": {
        "version": 1.2
    },
    "package2": {
        "version": 1.2
    },
    "package3": {
        "version": 1.2
    },
    "package4": {
        "version": 1.2
    }
  }
 }
}

我想提取类似[file1,file2,file3,file3,package1,package2,package3,package4]的内容

基本上只是我可以用于文本搜索索引的键列表。

我知道我可以使用像

这样的东西获得最外层对象的键列表
SELECT DISTINCT(json_object_keys(data))

我知道可以使用像

之类的东西递归爬过树
WITH RECURSIVE data()
但是我在将两者放在一起时遇到了麻烦。

有人可以帮忙吗?

4 个答案:

答案 0 :(得分:8)

诀窍是在正确的位置使用json_typeof添加一些最终条件测试。

如果您不关心对象键顺序,也应该使用jsonb

这是我的工作环境:

CREATE TABLE test (
  id  SERIAL PRIMARY KEY,
  doc JSON
);

INSERT INTO test (doc) VALUES ('{
 "files": {
  "folder": {
   "file1": {
    "property": "blah"
   },
   "file2": {
    "property": "blah"
   },
   "file3": {
    "property": "blah"
   },
   "file4": {
    "property": "blah",
    "prop" : {
      "clap": "clap"
    }
   }
 }
},
"software": {
  "apt": {
    "package1": {
        "version": 1.2
    },
    "package2": {
        "version": 1.2
    },
    "package3": {
        "version": 1.2
    },
    "package4": {
        "version": 1.2
    }
  }
 }
}');

当第二个查询未返回任何行时,将停止递归。这是通过将空对象传递给json_each来完成的。

 WITH RECURSIVE doc_key_and_value_recursive(key, value) AS (
  SELECT
    t.key,
    t.value
  FROM test, json_each(test.doc) AS t

  UNION ALL

  SELECT
    t.key,
    t.value
  FROM doc_key_and_value_recursive,
    json_each(CASE 
      WHEN json_typeof(doc_key_and_value_recursive.value) <> 'object' THEN '{}' :: JSON
      ELSE doc_key_and_value_recursive.value
    END) AS t
)
SELECT *
FROM doc_key_and_value_recursive
WHERE json_typeof(doc_key_and_value_recursive.value) <> 'object';

答案 1 :(得分:6)

我写了一个函数来执行此操作:

CREATE OR REPLACE FUNCTION public.jsonb_keys_recursive(_value jsonb)
 RETURNS TABLE(key text)
 LANGUAGE sql
AS $function$
WITH RECURSIVE _tree (key, value) AS (
  SELECT
    NULL   AS key,
    _value AS value
  UNION ALL
  (WITH typed_values AS (SELECT jsonb_typeof(value) as typeof, value FROM _tree)
   SELECT v.*
     FROM typed_values, LATERAL jsonb_each(value) v
     WHERE typeof = 'object'
   UNION ALL
   SELECT NULL, element
     FROM typed_values, LATERAL jsonb_array_elements(value) element
     WHERE typeof = 'array'
  )
)
SELECT DISTINCT key
  FROM _tree
  WHERE key IS NOT NULL
$function$;

举个例子,试试:

SELECT jsonb_keys_recursive('{"A":[[[{"C":"B"}]]],"X":"Y"}');

请注意,其他两个答案在数组内的对象中找不到键,我的解决方案确实如此。 (这个问题根本没有提供任何数组的例子,所以在数组中查找键可能不是原始提问者所需要的,但这正是我所需要的。)

答案 2 :(得分:1)

您可以测试一个更简洁的版本:

WITH RECURSIVE reports (key, value) AS (
  SELECT
    NULL as key,
    '{"k1": {"k2": "v1"}, "k3": {"k4": "v2"}, "k5": "v3"}'::JSONB as value

UNION ALL

   SELECT
    jsonb_object_keys(value)as key,
    value->jsonb_object_keys(value) as value
   FROM
    reports
   WHERE
    jsonb_typeof(value) = 'object'
)

SELECT
    *
FROM
    reports;

这将返回一个列表,然后您需要使用distinct进行分组。

答案 3 :(得分:0)

@Simon 上面的回答很好,但是对于我构建 JSON 对象 diff 的类似案例,我想要像 JSONpath 形式一样的键路径,而不仅仅是姓氏,包括数组索引和值。

因此,在示例 {"A":[[[{"C":"B"}, {"D":"E"}]]],"X":"Y", "F": {"G": "H"}} 中,我不仅需要键 XDGCF、{{1 }},但每个路径上的值都像 A = 'B'。

还有一些小的改进,例如:

  1. 提供值的数据类型
  2. 提供价值本身,无需额外的引号

我希望它也能对某人有所帮助:

.A[0][0][0].C

Dbfiddle to run