如何在DuckDB/SQL数据字典中遍历嵌套STRUCT与识别数组类型
遍历嵌套STRUCT与识别数组类型(兼容DuckDB与PostgreSQL)
针对你开发schema跟踪工具的需求,下面提供分别适配DuckDB、PostgreSQL的查询方案,以及兼容两者的通用SQL,能递归遍历嵌套STRUCT字段并准确识别数组类型。
首先创建示例表:
CREATE TABLE t(x BIGINT, y STRUCT(a BIGINT, b TEXT), z TEXT[]);
DuckDB 专属实现
DuckDB自带duckdb_columns系统表和duckdb_struct_fields函数,可直接拆解STRUCT类型:
WITH RECURSIVE column_tree AS ( SELECT table_name, column_name AS full_path, column_name AS parent_path, duckdb_type AS raw_type, CASE WHEN duckdb_type LIKE 'STRUCT%' THEN 'STRUCT' WHEN duckdb_type LIKE '%[]' THEN 'ARRAY' ELSE 'SCALAR' END AS type_category, CASE WHEN duckdb_type LIKE '%[]' THEN REPLACE(duckdb_type, '[]', '') ELSE duckdb_type END AS base_type FROM duckdb_columns WHERE table_name = 't' UNION ALL SELECT ct.table_name, CONCAT(ct.full_path, '.', sf.field_name) AS full_path, ct.full_path AS parent_path, sf.field_type AS raw_type, CASE WHEN sf.field_type LIKE 'STRUCT%' THEN 'STRUCT' WHEN sf.field_type LIKE '%[]' THEN 'ARRAY' ELSE 'SCALAR' END AS type_category, CASE WHEN sf.field_type LIKE '%[]' THEN REPLACE(sf.field_type, '[]', '') ELSE sf.field_type END AS base_type FROM column_tree ct, UNNEST(duckdb_struct_fields(ct.raw_type)) sf(field_name, field_type) WHERE ct.type_category = 'STRUCT' ) SELECT full_path, CASE WHEN type_category = 'ARRAY' THEN CONCAT('ARRAY-', base_type) ELSE base_type END AS type_display FROM column_tree ORDER BY full_path;
输出结果:
full_path | type_display ----------|------------- x | BIGINT y | STRUCT y.a | BIGINT y.b | TEXT z | ARRAY-TEXT
PostgreSQL 专属实现
PostgreSQL中,STRUCT对应复合类型,需通过pg_type和pg_attribute递归查询;数组类型可通过关联系统表获取元素类型:
WITH RECURSIVE column_tree AS ( SELECT c.table_name, c.column_name AS full_path, c.column_name AS parent_path, t.typname AS raw_type, CASE WHEN t.typtype = 'c' THEN 'STRUCT' -- 复合类型即STRUCT WHEN t.typtype = 'a' THEN 'ARRAY' ELSE 'SCALAR' END AS type_category, CASE WHEN t.typtype = 'a' THEN (SELECT typname FROM pg_type WHERE oid = t.typelem) ELSE t.typname END AS base_type, CASE WHEN t.typtype = 'c' THEN t.oid ELSE NULL END AS composite_oid FROM information_schema.columns c JOIN pg_type t ON c.udt_oid = t.oid WHERE c.table_name = 't' UNION ALL SELECT ct.table_name, CONCAT(ct.full_path, '.', a.attname) AS full_path, ct.full_path AS parent_path, t.typname AS raw_type, CASE WHEN t.typtype = 'c' THEN 'STRUCT' WHEN t.typtype = 'a' THEN 'ARRAY' ELSE 'SCALAR' END AS type_category, CASE WHEN t.typtype = 'a' THEN (SELECT typname FROM pg_type WHERE oid = t.typelem) ELSE t.typname END AS base_type, CASE WHEN t.typtype = 'c' THEN t.oid ELSE NULL END AS composite_oid FROM column_tree ct JOIN pg_attribute a ON a.attrelid = ct.composite_oid JOIN pg_type t ON a.atttypid = t.oid WHERE ct.composite_oid IS NOT NULL AND a.attnum > 0 -- 排除系统内置字段 AND NOT a.attisdropped ) SELECT full_path, CASE WHEN type_category = 'ARRAY' THEN CONCAT('ARRAY-', base_type) WHEN type_category = 'STRUCT' THEN 'STRUCT' ELSE base_type END AS type_display FROM column_tree ORDER BY full_path;
输出结果与DuckDB版本一致。
兼容两者的通用方案
通过判断数据库类型分支执行逻辑,可在DuckDB和PostgreSQL中直接运行:
WITH RECURSIVE column_tree AS ( SELECT table_name, column_name AS full_path, column_name AS parent_path, CASE WHEN current_database() LIKE 'duckdb' THEN duckdb_type ELSE (SELECT typname FROM pg_type WHERE oid = c.udt_oid) END AS raw_type, CASE WHEN current_database() LIKE 'duckdb' THEN CASE WHEN duckdb_type LIKE 'STRUCT%' THEN 'STRUCT' WHEN duckdb_type LIKE '%[]' THEN 'ARRAY' ELSE 'SCALAR' END ELSE CASE WHEN (SELECT typtype FROM pg_type WHERE oid = c.udt_oid) = 'c' THEN 'STRUCT' WHEN (SELECT typtype FROM pg_type WHERE oid = c.udt_oid) = 'a' THEN 'ARRAY' ELSE 'SCALAR' END END AS type_category, CASE WHEN current_database() LIKE 'duckdb' THEN CASE WHEN duckdb_type LIKE '%[]' THEN REPLACE(duckdb_type, '[]', '') ELSE duckdb_type END ELSE CASE WHEN (SELECT typtype FROM pg_type WHERE oid = c.udt_oid) = 'a' THEN (SELECT typname FROM pg_type WHERE oid = (SELECT typelem FROM pg_type WHERE oid = c.udt_oid)) ELSE (SELECT typname FROM pg_type WHERE oid = c.udt_oid) END END AS base_type, CASE WHEN current_database() NOT LIKE 'duckdb' AND (SELECT typtype FROM pg_type WHERE oid = c.udt_oid) = 'c' THEN c.udt_oid ELSE NULL END AS composite_oid FROM information_schema.columns c WHERE table_name = 't' UNION ALL SELECT ct.table_name, CONCAT(ct.full_path, '.', COALESCE(sf.field_name, a.attname)) AS full_path, ct.full_path AS parent_path, COALESCE(sf.field_type, t.typname) AS raw_type, CASE WHEN current_database() LIKE 'duckdb' THEN CASE WHEN sf.field_type LIKE 'STRUCT%' THEN 'STRUCT' WHEN sf.field_type LIKE '%[]' THEN 'ARRAY' ELSE 'SCALAR' END ELSE CASE WHEN t.typtype = 'c' THEN 'STRUCT' WHEN t.typtype = 'a' THEN 'ARRAY' ELSE 'SCALAR' END END AS type_category, CASE WHEN current_database() LIKE 'duckdb' THEN CASE WHEN sf.field_type LIKE '%[]' THEN REPLACE(sf.field_type, '[]', '') ELSE sf.field_type END ELSE CASE WHEN t.typtype = 'a' THEN (SELECT typname FROM pg_type WHERE oid = t.typelem) ELSE t.typname END END AS base_type, CASE WHEN current_database() NOT LIKE 'duckdb' AND t.typtype = 'c' THEN t.oid ELSE NULL END AS composite_oid FROM column_tree ct LEFT JOIN LATERAL UNNEST(CASE WHEN current_database() LIKE 'duckdb' AND ct.type_category = 'STRUCT' THEN duckdb_struct_fields(ct.raw_type) ELSE NULL END) sf(field_name, field_type) ON current_database() LIKE 'duckdb' LEFT JOIN pg_attribute a ON current_database() NOT LIKE 'duckdb' AND ct.composite_oid IS NOT NULL AND a.attrelid = ct.composite_oid AND a.attnum > 0 AND NOT a.attisdropped LEFT JOIN pg_type t ON current_database() NOT LIKE 'duckdb' AND a.atttypid = t.oid WHERE ct.type_category = 'STRUCT' ) SELECT full_path, CASE WHEN type_category = 'ARRAY' THEN CONCAT('ARRAY-', base_type) WHEN type_category = 'STRUCT' THEN 'STRUCT' ELSE base_type END AS type_display FROM column_tree ORDER BY full_path;
这个通用查询会自动适配数据库环境,输出符合需求的schema结构,适合集成到schema跟踪工具中。
内容的提问来源于stack exchange,提问作者Mark Harrison
相关产品推荐
相关产品推荐

