
本文介绍如何利用 Polars SQL API(支持 UNNEST 和数组聚合)或 Polars DataFrame API,将源表中存储的城市数组,依据查找表动态映射为对应国家数组,并确保结果结构一致、去重准确。
本文介绍如何利用 polars sql api(支持 `unnest` 和数组聚合)或 polars dataframe api,将源表中存储的城市数组,依据查找表动态映射为对应国家数组,并确保结果结构一致、去重准确。
在实际数据处理中,常遇到非范式化设计——例如某列以数组形式存储多个城市名(如 ['Manchester', 'Paris']),而另一张查找表(Lookup)则按国家维度组织首都与其它城市列表。目标是将原始数组中的每个城市,精准映射为其所属国家,并最终以相同数组结构返回(如 ['Britain', 'France'])。尽管该结构不符合传统关系型建模原则,但借助 Polars 强大的数组处理能力,这一需求完全可实现。
✅ 推荐方案:Polars SQL(简洁、声明式)
Polars SQL 支持标准的 UNNEST(等价于 explode)、条件 JOIN 与 ARRAY_AGG 聚合,是最贴近问题描述的原生 SQL 解法:
WITH Exploded AS (
SELECT Name, UNNEST(Cities) AS City
FROM Data
),
Matched AS (
SELECT e.Name, l.Country
FROM Exploded e
JOIN Lookup l
ON e.City = l.Capital_City
OR e.City = ANY(l.Other_cities)
)
SELECT Name, ARRAY_AGG(DISTINCT Country) AS Countries
FROM Matched
GROUP BY Name;⚠️ 关键说明:
-
UNNEST(Cities)将每行数组展开为多行(一行一城市),是后续逐项匹配的前提; -
e.City = ANY(l.Other_cities)是 Polars SQL 对数组成员判断的标准语法(等价于e.City IN UNNEST(l.Other_cities)); -
ARRAY_AGG(DISTINCT Country)确保同一人多次匹配同一国家时自动去重,且最终仍以数组形式返回。
⚙️ 备选方案:Polars DataFrame API(更灵活、易调试)
当 SQL 表达受限(如复杂嵌套逻辑)或需额外清洗时,推荐使用 Polars Python API,代码清晰、链式可读性强:
import polars as pl
# 构造示例数据
data = pl.DataFrame({
"Name": ["Mark", "Aisha"],
"Cities": [["Manchester", "Paris"], ["London", "Munich"]]
})
lookup = pl.DataFrame({
"Country": ["Britain", "France", "Germany"],
"Capital_City": ["London", "Paris", "Berlin"],
"Other_cities": [["Manchester", "Bristol"], ["Bordeaux", "Marseilles"], ["Munich", "Cologne"]]
})
# 步骤1:展开 Cities 数组 → 每城一行
data_exploded = data.explode("Cities")
# 步骤2:分别按首都 & 其它城市匹配(两次 JOIN)
capital_match = (
data_exploded.join(lookup, left_on="Cities", right_on="Capital_City", how="inner")
.select(["Name", "Country"])
)
other_match = (
data_exploded.join(lookup.explode("Other_cities"),
left_on="Cities", right_on="Other_cities", how="inner")
.select(["Name", "Country"])
)
# 步骤3:合并结果 + 去重聚合
result = (
pl.concat([capital_match, other_match])
.group_by("Name")
.agg(pl.col("Country").unique().alias("Countries"))
)
print(result)✅ 输出结构严格对齐需求:
shape: (2, 2) ┌───────┬─────────────────────────┐ │ Name │ Countries │ ├───────┼─────────────────────────┤ │ Mark │ ["Britain", "France"] │ │ Aisha │ ["Britain", "Germany"] │ └───────┴─────────────────────────┘
? 注意事项与最佳实践
-
性能提示:若
Cities数组极大,explode后数据量激增,建议在JOIN前对Lookup表预处理(如explode("Other_cities")一次并缓存),避免重复展开; -
空值/未匹配处理:当前逻辑仅保留成功匹配项;如需保留原始数组长度(未匹配城市填
NULL),可改用left join并配合coalesce; -
大小写与空格:生产环境中务必添加
.str.to_lowercase()或.str.strip_chars()预处理,规避因格式差异导致匹配失败; -
扩展性建议:长期来看,可考虑将
Cities列拆为关联子表(Data_Cities),实现真正规范化,兼顾查询性能与语义清晰度。
综上,无论选择 Polars SQL 还是 DataFrame API,核心思路统一:先展开(UNNEST/explode)→ 再精确匹配(JOIN + ANY/IN)→ 最后聚合(ARRAY_AGG/.unique())。该模式可无缝迁移至城市→国家、ID→名称、编码→标签等各类“数组映射”场景。

















