Polars SQL 中的 CREATE TABLE:用SQLContext注册新表的三种方式与源码解析
【免费下载链接】polarsExtremely fast Query Engine for DataFrames, written in Rust项目地址: https://gitcode.com/GitHub_Trending/po/polars
Polars 提供了SQLContext,让你用 SQL 语法直接对LazyFrame与DataFrame执行查询。CREATE TABLE ... AS SELECT是其中最常用的语句之一——它基于一段 SELECT 查询在会话内创建并注册一张新表。本文以官方文档 docs/source/user-guide/sql/create.md 为主线,讲解CREATE TABLE的语法、运行结果与取值方式,并结合 Polars 源码与测试说明其背后三种建表路径的实现细节,帮助你在已有 SQL 代码库迁移或与 DataFrame 混合编程时正确使用这一特性。
一、基本语法与语义
在SQLContext中,CREATE TABLE语句用于创建一张新表。其基础语法如下:
CREATE TABLE table_name AS SELECT ...其中table_name是新表的名字,SELECT ...定义了将要存入该表的数据。Polars 的 SQL 方言参考了 PostgreSQL 的语法与函数行为,因此对熟悉 PostgreSQL 的读者来说这一点并不陌生。
这里必须澄清一个常见的误区:CREATE TABLE的“产物”并不是这张表本身,而是一个执行成功的提示。表会被登记进SQLContext的注册表中;如果想把它重新变回一个DataFrame,需要用一条SELECT * FROM ...语句去取回数据。这一点在文档的 Result 提示中着重强调,也是本文第三部分要展开的内容。
二、完整可运行的建表示例
下面的示例来自官方配套代码 docs/source/src/python/user-guide/sql/create.py,它是随文档一起被 mkdocs 渲染执行的真实代码:
import polars as pl data = {"name": ["Alice", "Bob", "Charlie", "David"], "age": [25, 30, 35, 40]} df = pl.LazyFrame(data) ctx = pl.SQLContext(my_table=df, eager=True) result = ctx.execute( """ CREATE TABLE older_people AS SELECT * FROM my_table WHERE age > 30 """ ) print(ctx.execute("SELECT * FROM older_people"))运行流程拆解:
- 准备数据源:用
pl.LazyFrame(data)构造一个懒加载的 DataFrame,列名为name与age。 - 注册数据源:在初始化
pl.SQLContext(my_table=df, eager=True)时,通过关键字参数把df注册为名为my_table的 SQL 表。这里eager=True表示执行查询后自动把结果收集为DataFrame。 - 执行建表:调用
ctx.execute(...)执行CREATE TABLE older_people AS SELECT * FROM my_table WHERE age > 30,从my_table中筛选出age大于 30 的所有行,生成新表older_people。筛选后该表仅剩 Charlie(35)与 David(40)两行。 - 取回数据:再次
ctx.execute("SELECT * FROM older_people"),把已注册的older_people表作为查询结果输出。
你将会看到输出:
shape: (2, 2) ┌─────────┬─────┐ │ name ┆ age │ │ --- ┆ --- │ │ str ┆ i64 │ ╞═════════╪═════╡ │ Charlie ┆ 35 │ │ David ┆ 40 │ └─────────┴─────┘三、CREATE TABLE的结果不是表,而是确认信息
由于CREATE TABLE属于数据定义语句(DDL),它的返回值并不携带数据。看 crates/polars-sql/src/context.rs 中的实现:
let df_created = df! { "Response" => [format!("CREATE TABLE {}", name.0.first().unwrap().as_ident().unwrap().value)] }; Ok(df_created.unwrap().lazy())也就是说,execute()执行成功后返回的是一个只有单列Response、单行文本(例如"CREATE TABLE older_people")的微型帧,用于向调用方确认操作结果。真正建好的表被保存在SQLContext内部的注册表table_map中:
self.register(tbl_name, lf);其底层即 SQLContext::register——把表名到LazyFrame的映射写入一张读写锁保护的 map。因此后续对这张表的访问,都需要经由SQLContext,通过SELECT * FROM ...、JOIN等语句间接完成。
四、eager 与 lazy:什么时候能拿到DataFrame
SQL 查询在 Polars 中总是以惰性模式执行,以便利用完整的查询计划优化。上文ctx = pl.SQLContext(my_table=df, eager=True)中的eager参数就是用来控制收集行为的,两种方式二选一:
- 在构造
SQLContext时传入eager=True,让每次execute自动把LazyFrame结果收集成DataFrame; - 保持上下文为 lazy,而在某一次查询调用时传
eager=True或显式.collect()。
Python 侧 py-polars/src/polars/sql/context.py 中execute()的结尾正是这条规则:
return res.collect() if eager or self._eager_execution else res在第一个示例里,两次execute都返回DataFrame:第一次返回的是Response确认表,第二次返回的是older_people的实际数据。
提示:如果
CREATE TABLE的子查询基于 CSV、NDJSON 等文件做懒加载,建表本身只是定义逻辑,真正扫描文件数据会被推迟到SELECT * FROM older_people收集时,从而可以只加载必要的行与列。
五、三种建表路径:AS query、列定义与LIKE
虽然 create.md 重点示范了AS SELECT的形式,但从源码看 Polars 的CREATE TABLE支持三种互斥的建表路径。测试 py-polars/tests/unit/sql/test_table_operations.py 一次覆盖了全部三种用法:
with pl.SQLContext() as ctx: # test all three ways of creating a new table ctx.execute("CREATE TABLE tbl1(colx VARCHAR, coly DATE, colz ARRAY<DOUBLE>)") ctx.execute("CREATE TABLE tbl2 AS SELECT * FROM tbl1") ctx.execute("CREATE TABLE tbl3 LIKE tbl2") df = ctx.execute("SELECT * FROM tbl3", eager=True)对应到 execute_create_table 的实现,会按(query, columns, like)组合进行分支:
CREATE TABLE [IF NOT EXISTS] <name> AS <query>:子查询存在且未提供列定义时,直接递归执行子查询得到LazyFrame——这是 create.md 展示的路径,其子查询可以是任意合法的 SELECT、UNION、JOIN,甚至引用其他已注册表或READ_CSV之类的表函数。CREATE TABLE [IF NOT EXISTS] <name> (<coldef>, ...):仅给出列定义时,把每个 SQL 数据类型通过map_sql_dtype_to_polars映射为 Polars 数据类型(如VARCHAR → String、DATE → Date、ARRAY<DOUBLE> → List(Float64)),构建一个只含 schema、不含数据的空表。CREATE TABLE [IF NOT EXISTS] <name> LIKE <table>:复制一张已存在表的 schema 但清空数据;若源表不存在会直接报错table given in LIKE does not exist。
如果三个来源一个都没给出,会抛出错误CREATE TABLE expected a query, column definitions, or LIKE clause;如果同时给出多个来源(比如既有AS子查询又带列定义),会报告“互斥选项”错误。此外,虽然内部结构支持if_not_exists,但从代码注释看 Polars 的CREATE本身已具备幂等友好处理,而CREATE OR REPLACE TABLE、CREATE TEMPORARY TABLE及大批存储/分区/物化子句均不被支持,详见 validate_create_table。
六、支持范围与限制速览
一句话概括文档 docs/source/user-guide/sql/intro.md 的结论:Polars 不实现完整 SQL 规范,但覆盖了最常用语句的一个子集,并尽可能贴近 PostgreSQL 语义。
已支持的CREATE相关语句包括:
CREATE TABLE xxx AS ...(本文主题);- 列出注册表的
SHOW TABLES; - 删除表的
DROP TABLE [IF EXISTS] tablename; - 清空表的
TRUNCATE TABLE [IF EXISTS] tablename。
当前尚未支持的包括INSERT/UPDATE/DELETE语句,以及ANALYZE之类的元查询——这意味着在 Polars 中建表不等于持久化存储,也不支持对表做行级写操作。
七、更进一步
CREATE TABLE建出的新表与子查询共享 schema 推断:name会被推断为String,age为Int64(参见上文输出中str与i64的标注)。- 想让新表与文件读取无缝衔接,可把
READ_CSV、READ_NDJSON等表函数放进AS子查询,测试 test_create_table_from_file_io 演示了CREATE TABLE foods AS SELECT * FROM READ_CSV(...)的用法;类似机制同样适用于scan_pyarrow_dataset指向的 S3、Azure Data Lake 等云数据源。 - 由于表实际存储的是
LazyFrame引用,会话内可继续用register/unregister/register_globals/register_many等 API 增删表(Python 侧定义见 py-polars/src/polars/sql/context.py),从而灵活组合 DataFrame 编程与 SQL 编程两种范式。
结合本文的核心结论——CREATE TABLE负责把一段查询逻辑注册为新表并返回确认信息,而真正的数据要依靠后续SELECT才能取回——你就能在迁移 SQL 工作负载到 Polars 时,把建表语句当作“物化中间视图”来使用,同时继续享受 Polars 原生引擎带来的惰性优化与执行性能。
【免费下载链接】polarsExtremely fast Query Engine for DataFrames, written in Rust项目地址: https://gitcode.com/GitHub_Trending/po/polars
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考