我有一个表(表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的情况下执行此操作?我还没有取得多大进展。
答案 0 :(得分:2)
json
数据类型不是特别适合(或打算)在数据库级别进行修改。因此,从JSON对象中提取"x"
对象是很麻烦的,尽管可以这样做。
您应该创建表格B(希望列表名称比"json"
更具创意;我在这里使用item
)并将id
列设为serial
从0开始。纯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_id
和x
值以及JSON对象。在外部查询中,JSON对象被分解为其各个部分以及抛出键x
的对象(LEFT JOIN ON i.k <> '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对象,那么最好使用{{{ 1}}开始输入。在您的情况下,我建议您使用jsonb
进行一些基准测试(您可以安全地忘记测试时EXPLAIN ANALYZE SELECT ...
)大约10,000行,以查看哪种方法最适合您的设置。