You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 12:45:02