【问题标题】:PostgreSQL: Efficiently split JSON array into rowsPostgreSQL:有效地将 JSON 数组拆分为行
【发布时间】:2016-08-20 08:03:47
【问题描述】:

我有一个表(表 A),其中包含一个包含 JSON 编码数据的文本列。

JSON 数据始终是一个包含一到几千个普通对象的数组。

我有另一个表(表 B)有几列,包括一个数据类型为“JSON”的列

我想从表 A 中选择所有行,将 json 数组拆分为其元素并将每个元素插入到表 B 中

奖励目标:每个对象(几乎)总是有一个键,x。我想将x 的值提取到列中,并从原始对象中删除x(如果存在)。

例如:表 A

| id | json_array (text)              |
+----+--------------------------------+
|  1 | '[{"x": 1}, {"y": 8}]'         |
|  2 | '[{"x": 2, "y": 3}, {"x": 1}]' |
|  3 | '[{"x": 8, "z": 2}, {"z": 3}]' |
|  4 | '[{"x": 5, "y": 2, "z": 3}]'   |

...将变为:表 B

| id | a_id | x    | json (json)        |
+----+------+------+--------------------+
|  0 |    1 |    1 | '{}'               |
|  1 |    1 | NULL | '{"y": 8}'         |
|  2 |    2 |    2 | '{"y": 3}'         |
|  3 |    2 |    1 | '{}'               |
|  4 |    3 |    8 | '{"y": 2}'         |
|  5 |    3 | NULL | '{"z": 3}'         |
|  6 |    4 |    5 | '{"y": 2, "z": 3}' |

这最初必须处理几百万行,然后需要定期运行,因此提高效率将是当务之急。

是否可以在不使用循环和 PL/PgSQL 的情况下做到这一点?我的进步不大。

【问题讨论】:

  • 你可以使用jsonb(即PG9.5)吗? json 数据类型不适合操作对象,而 jsonb 有更多选项。使用jsonb 删除x 很容易,使用json 更复杂。

标签: sql json postgresql


【解决方案1】:

json 数据类型不是特别适合(或不打算)在数据库级别进行修改。因此,从 JSON 对象中提取 "x" 对象很麻烦,尽管可以做到。

您应该创建表 B(希望列名比 "json" 更有创意;我在这里使用 item)并将 id 列设置为从 0 开始的 serial。A json 解决方案然后看起来像这样:

INSERT INTO b (a_id, x, item)
  SELECT sub.a_id, sub.x,
         ('{' ||
         string_agg(
             CASE WHEN i.k IS NULL THEN '' ELSE '"' || i.k || '":' || i.v END,
             ', ') ||
         '}')::json
  FROM (
    SELECT a.id AS a_id, (j.items->>'x')::integer AS x, j.items
    FROM a, json_array_elements(json_array) j(items) ) sub
  LEFT JOIN json_each(sub.items) i(k,v) ON i.k <> 'x'
  GROUP BY sub.a_id, sub.x
  ORDER BY sub.a_id;

在子查询中,这会提取 a_idx 值以及 JSON 对象。在外部查询中,JSON 对象被分解成单独的部分,并且带有键 x 的对象被丢弃(LEFT JOIN ON i.k &lt;&gt; 'x')。在选择列表中,这些片段通过字符串连接再次组合在一起并分组为复合对象。

这必须是这样的,因为json 没有任何后果的内置操作函数。这适用于 PG 版本 9.3+,即自古以来就 JSON 支持而言。

如果您使用的是 PG9.5+,通过强制转换为 jsonb,解决方案要简单得多:

INSERT INTO b (a_id, x, item)
  SELECT a.id, (j.items->>'x')::integer, j.items #- '{x}'
  FROM a, jsonb_array_elements(json_array::jsonb) j(items);

jsonb 数据类型上的 #- 运算符在这里完成了所有脏活。显然,在幕后进行了大量工作,将json 转换为jsonb,因此如果您发现需要更频繁地操作JSON 对象,那么您最好使用jsonb 类型开始和。在您的情况下,我建议您使用 EXPLAIN ANALYZE SELECT ...(您可以放心地在测试时忘记 INSERT)对大约 10,000 行进行一些基准测试,看看哪个最适合您的设置。

【讨论】:

  • 不幸的是,我们还没有进入 9.5,但希望不会太久。非常感谢您的全面回答。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-09-20
  • 2020-12-06
  • 1970-01-01
  • 2023-01-18
  • 2012-12-23
  • 2018-05-02
  • 2012-12-18
相关资源
最近更新 更多