【问题标题】:Automatically Normalizing a Postgres JSON Column into a New Table自动将 Postgres JSON 列规范化为新表
【发布时间】:2021-11-06 14:28:35
【问题描述】:

我有一个 非常大 包含数百万行的 Postgres 表。其中一列称为data,类型为JSONB,带有嵌套的JSON(但幸运的是没有子数组)。 JSON 的“模式”大部分是一致的,但随着时间的推移发生了一些变化,获得和丢失新的键和嵌套的键。

我想要一个可以将列规范化为新表的过程,并且过程尽可能简单。

例如,如果表格看起来像:

id | data
---+----------------------------------------------
  1| {"hi": "mom", "age": 43}
  2| {"bye": "dad", "age": 41}

它应该创建并填充一个新表,例如

id | data.hi | data.age | data.bye
---+----------------------------------------------
  1|     mom |       43 | NULL
  2|    NULL |       41 |  dad  

(注意:列名并不重要)

理论上,我可以做到以下几点:

  1. 将列选择到 Pandas DataFrame 中并在其上运行 json_normalize
  2. 将架构推断为步骤 1 中派生列的超集
  3. 使用步骤 2 的架构创建 Postgres 表并插入(to_sql 是实现此目的的简单方法)

这似乎并不算太​​糟糕,但请记住,表非常大,我们应该假设这不能从单个 DataFrame 中完成。如果我们尝试做下一个最好的事情 - 将上述步骤批处理 - 我们将遇到架构在批处理之间略有变化的问题。

有没有比我的方法更好的方法来解决这个问题? “完美”的解决方案是“纯 SQL”,根本不涉及任何 Python。但我不是在这里寻求完美。只是一个不需要人工干预的自动且稳健的过程。

【问题讨论】:

  • 阅读this post.中的通用解决方案@
  • 这太棒了!

标签: python sql pandas postgresql jsonb


【解决方案1】:

您可以尝试通过CREATE TABLE AS 语句创建一个新表。

CREATE TABLE newtable AS
SELECT 
  id, 
  (data->>'hi')::text AS data_hi,
  (data->>'bye')::text AS data_bye,
  (data->'age')::int AS data_age
FROM mytable

如果 JSON 结构未知,所有键和数据类型都可以这样选择:

SELECT DISTINCT
  jsonb_object_keys(data) as col_name,
  jsonb_typeof(data->jsonb_object_keys(data)) as col_type
FROM mytable

输出:

col_name    col_type
--------------------
bye         string
hi          string
age         number

对于嵌套结构

id   data
---------
3    {"age": 33, "foo": {"bar": true}}

您可以使用递归查询:

WITH RECURSIVE cte AS (
  select
    jsonb_object_keys(data) as col_name,
    jsonb_object_keys(data) as col_path,
    jsonb_typeof(data->jsonb_object_keys(data)) as col_type,
    data
  from mytable
  union all
  select
    jsonb_object_keys(data->col_name) as col_name,
    col_path || '_' || jsonb_object_keys(data->col_name) as col_path, 
    jsonb_typeof(data->col_name->jsonb_object_keys(data->col_name)) as col_type,
    data->cte.col_name AS data
  from cte
  where col_type = 'object'
) 
SELECT distinct col_path AS col_name, col_type 
FROM cte
WHERE col_type <> 'object';

输出:

col_name    col_type
--------------------
age         number
foo_bar     boolean

接下来,您需要根据这些数据为SELECT 子句构建一个列列表,以便在CREATE TABLE AS 语句中使用,如上所示。

以下小提琴有一个生成整个 SQL 的助手:

db<>fiddle

请注意,所有数字类型,包括小数类型,都将被指定为数字类型并需要更正。

【讨论】:

  • 感谢 id'7238 的回答。它显示了如何使用 CREATE SELECT 从 JSON 列填充规范化表。但是,为了编写 create 语句,我们需要知道模式。我想让 SQL 自动解决这个问题。也许jsonb_populate_record 在这方面可能有用。
  • 即使我们能够解决上述问题,jsonb_populate_record 也只会查看 JSON 的顶层。为了展开子 json,我们需要一个递归版本。
  • 如果 JSON 数据结构未知,那么可以尝试获取所有键名及其数据类型(参见db<>fiddle)。但是您必须根据此数据手动描述 SELECT 子句中的列列表,然后执行 CREATE TABLE AS 查询,如上所述。我不知道这对你有多大用处。
  • 我会好好考虑的。那个 DB Fiddle 中相当壮观的 SQL!
猜你喜欢
  • 2021-10-18
  • 2011-03-06
  • 2020-12-11
  • 1970-01-01
  • 2014-03-07
  • 2017-10-27
  • 2021-03-22
  • 2019-03-20
  • 1970-01-01
相关资源
最近更新 更多