SQL 参考

Struct 类型强制转换与字段映射

师成师成· 更新于 2026-09-28· 阅读 21 分钟· 0 次阅读

登录后可跨设备保存划线和私人笔记登录

结构体类型强制转换与字段映射

DataFusion 在不同操作之间对结构体类型进行强制转换时,采用基于名称的字段映射。本文档说明结构体强制转换的工作方式、适用时机,以及如何处理 NULL 字段。

概述:基于名称与基于位置的映射

当需要合并来自不同来源的结构体时(例如在 UNION、数组构造或 JOIN 中),DataFusion 会按名称而非位置来匹配结构体字段。与基于位置的匹配相比,这种方式的行为更加健壮、更可预测。

示例:字段重排可被透明处理

-- These two structs have the same fields in different order
SELECT [{a: 1, b: 2}, {b: 3, a: 4}];

-- Result: Field names matched, values unified
-- [{"a": 1, "b": 2}, {"a": 4, "b": 3}]

使用基于名称匹配的强制转换路径

以下查询操作在进行结构体强制转换时使用基于名称的字段映射:

1. 数组字面量构造

当使用字段顺序不同的结构体元素创建数组字面量时:

-- Structs with reordered fields in array literal
SELECT [{x: 1, y: 2}, {y: 3, x: 4}];

-- Unified type: List(Struct("x": Int32, "y": Int32))
-- Values: [{"x": 1, "y": 2}, {"x": 4, "y": 3}]

适用场景:

  • 包含结构体元素的数组字面量:[{...}, {...}]
  • 包含结构体的嵌套数组:[[{x: 1}, {x: 2}]]

2. 从列构建数组

当使用具有不同结构体 schema 的表列来构建数组时:

CREATE TABLE t_left (s struct(x int, y int)) AS VALUES ({x: 1, y: 2});
CREATE TABLE t_right (s struct(y int, x int)) AS VALUES ({y: 3, x: 4});

-- Dynamically constructs unified array schema
SELECT [t_left.s, t_right.s] FROM t_left JOIN t_right;

-- Result: [{"x": 1, "y": 2}, {"x": 4, "y": 3}]

适用场景:

  • 使用列引用进行数组构造:[col1, col2]
  • 连接操作中字段名一致时的数组构造

3. UNION 操作

当组合 struct 字段顺序不同的查询结果时:

SELECT {a: 1, b: 2} as s
UNION ALL
SELECT {b: 3, a: 4} as s;

-- Result: {"a": 1, "b": 2} and {"a": 4, "b": 3}

适用场景:

  • 含结构体的 UNION ALL:跨分支匹配字段名
  • 含结构体的 UNION(去重):跨分支匹配字段名

4. 公用表表达式(CTE)

当多个 CTE 生成字段顺序不同的结构体并被组合在一起时:

WITH
  t1 AS (SELECT {a: 1, b: 2} as s),
  t2 AS (SELECT {b: 3, a: 4} as s)
SELECT s FROM t1
UNION ALL
SELECT s FROM t2;

-- Result: Field names matched across CTEs

5. VALUES 子句

当使用字段顺序不同的结构体值创建表或临时结果时:

CREATE TABLE t AS VALUES ({a: 1, b: 2}), ({b: 3, a: 4});

-- Table schema unified: struct(a: int, b: int)
-- Values: {a: 1, b: 2} and {a: 4, b: 3}

6. JOIN 操作

当连接的表其 JOIN 条件涉及字段顺序不同的结构体时:

CREATE TABLE orders (customer struct(name varchar, id int));
CREATE TABLE customers (info struct(id int, name varchar));

-- Join matches struct fields by name
SELECT * FROM orders
JOIN customers ON orders.customer = customers.info;

7. 聚合函数

使用 array_agg 等聚合函数收集字段顺序不同的结构体时:

SELECT array_agg(s) FROM (
  SELECT {x: 1, y: 2} as s
  UNION ALL
  SELECT {y: 3, x: 4} as s
) t
GROUP BY category;

-- Result: Array of structs with unified field order

8. 窗口函数

当对字段顺序不同的结构体表达式使用窗口函数时:

SELECT
  id,
  row_number() over (partition by s order by id) as rn
FROM (
  SELECT {category: 1, value: 10} as s, 1 as id
  UNION ALL
  SELECT {value: 20, category: 1} as s, 2 as id
);

-- Fields matched by name in PARTITION BY clause

缺失字段的 NULL 处理

当结构体(struct)的字段集合不同时,缺失的字段会在类型协调过程中被填充为 NULL 值。

示例:字段部分重叠

-- Struct in first position has fields: a, b
-- Struct in second position has fields: b, c
-- Unified schema includes all fields: a, b, c

SELECT [
  CAST({a: 1, b: 2} AS STRUCT(a INT, b INT, c INT)),
  CAST({b: 3, c: 4} AS STRUCT(a INT, b INT, c INT))
];

-- Result:
-- [
--   {"a": 1, "b": 2, "c": NULL},
--   {"a": NULL, "b": 3, "c": 4}
-- ]

限制

字段数量必须完全匹配。 如果结构体的字段数量不同,且字段名不能完全重合,查询将会失败:

-- This fails because field sets don't match:
-- t_left has {x, y} but t_right has {x, y, z}
SELECT [t_left.s, t_right.s] FROM t_left JOIN t_right;
-- Error: Cannot coerce struct with mismatched field counts

变通方案:使用显式 CAST

为了处理部分字段重叠的情况,可以将结构体显式转换为统一的 schema:

SELECT [
  CAST(t_left.s AS STRUCT(x INT, y INT, z INT)),
  CAST(t_right.s AS STRUCT(x INT, y INT, z INT))
] FROM t_left JOIN t_right;

比较与排序

DataFusion 支持使用标准比较运算符(=、!=、<、<=、>、>=)对 STRUCT 值进行比较。排序比较采用字典序,并遵循 DataFusion 默认的升序比较行为,即 NULL 排序在非 NULL 值之前。

示例

SELECT {x: 1, y: 2} < {x: 1, y: 3};
-- true

SELECT {x: 1, y: NULL} < {x: 1, y: 2};
-- true

SELECT {x: 1, y: NULL} = {x: 1, y: NULL};
--true

迁移指南:从按位置匹配改为按名称匹配

如果你现有的代码依赖于结构体字段的按位置匹配,可能需要对其进行更新。

示例:行为发生变化的查询

旧行为(按位置匹配):

-- These would have been positionally mapped (left-to-right)
SELECT [{x: 1, y: 2}, {y: 3, x: 4}];
-- Old result (positional): [{"x": 1, "y": 2}, {"y": 3, "x": 4}]

新行为(基于名称):

-- Now uses name-based matching
SELECT [{x: 1, y: 2}, {y: 3, x: 4}];
-- New result (by name): [{"x": 1, "y": 2}, {"x": 4, "y": 3}]

迁移步骤

  1. 检查结构体操作 - 查找合并来自不同数据源的结构体的查询
  2. 检查字段名称 - 确认字段名称符合预期(而不是按位置匹配)
  3. 使用新的强制转换进行测试 - 运行查询并验证结果是否符合预期
  4. 处理字段重排序 - 如果需要特定的字段顺序,请使用显式 CAST 操作

使用显式 CAST 保证兼容性

如果需要精确控制结构体的字段顺序和类型,请使用显式 CAST:

-- Guarantee specific field order and types
SELECT CAST({b: 3, a: 4} AS STRUCT(a INT, b INT));
-- Result: {"a": 4, "b": 3}

最佳实践

1. 明确指定 Schema 定义

在连接或组合 struct 时,请明确定义目标 schema:

-- Good: explicit schema definition
SELECT CAST(data AS STRUCT(id INT, name VARCHAR, active BOOLEAN))
FROM external_source;

2. 使用具名结构体构造函数

为清晰起见,优先使用具名结构体构造函数:

-- Good: field names are explicit
SELECT named_struct('id', 1, 'name', 'Alice', 'active', true);

-- Or using struct literal syntax
SELECT {id: 1, name: 'Alice', active: true};

3. 测试字段映射

始终验证字段映射是否符合预期:

-- Use arrow_typeof to verify unified schema
SELECT arrow_typeof([{x: 1, y: 2}, {y: 3, x: 4}]);
-- Result: List(Struct("x": Int32, "y": Int32))

4. 显式处理部分字段重叠

合并具有部分字段重叠的结构体时,请使用显式 CAST:

-- Instead of relying on implicit coercion
SELECT [
  CAST(left_struct AS STRUCT(x INT, y INT, z INT)),
  CAST(right_struct AS STRUCT(x INT, y INT, z INT))
];

5. 记录 Struct Schema

在复杂查询中,需要记录所期望的 struct schema:

-- Expected schema: {customer_id: INT, name: VARCHAR, age: INT}
SELECT {
  customer_id: c.id,
  name: c.name,
  age: c.age
} as customer_info
FROM customers c;

错误信息与故障排查

“Cannot coerce struct with different field counts”

原因: 试图合并字段数量不同的 struct。

解决方案:

-- Use explicit CAST to handle missing fields
SELECT [
  CAST(struct1 AS STRUCT(a INT, b INT, c INT)),
  CAST(struct2 AS STRUCT(a INT, b INT, c INT))
];

“结构体中找不到字段 X”

原因: 引用了结构体中不存在的字段名。

解决方案:

-- Verify field names match exactly (case-sensitive)
SELECT s['field_name'] FROM my_table;  -- Use bracket notation for access
-- Or use get_field function
SELECT get_field(s, 'field_name') FROM my_table;

类型强转后出现意外的 NULL 值

原因: Struct 强转为缺失的字段补上了 NULL。

解决方案: 检查所有 struct 是否都包含所需字段,或显式处理 NULL:

SELECT COALESCE(s['field'], default_value) FROM my_table;

评论

登录后参与评论

正在加载评论…