# cwind-tableau **Repository Path**: carson_add/cwind-tableau ## Basic Information - **Project Name**: cwind-tableau - **Description**: No description available - **Primary Language**: Unknown - **License**: MIT - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 1 - **Created**: 2026-07-27 - **Last Updated**: 2026-08-17 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # cwind-tableau
**基于 Python 的 Tableau 查询 DSL — 通过链式 API 构建聚合查询,生成并执行 SQL。** [简体中文](./README.zh-CN.md) | [English](./README.md)
--- ## 安装 ```bash pip install cwind-tableau ``` 需要 Python >= 3.10。 ## 快速开始 ```python import cwind_tableau as tab # 引用列并应用聚合 total_sales = tab.col("销量").sum() avg_sales = tab.col("销量").avg().alias("平均销量") # 创建带有维度和聚合的视图 view = tab.view( table="销量表", dims=["产品", "地区"], aggs=[total_sales, avg_sales], ) # 生成 SQL print(view.to_sql()) # SELECT 产品, 地区, SUM(销量) AS "SUM(销量)", AVG(销量) AS "平均销量" # FROM 销量表 # GROUP BY 产品, 地区 # 使用 DuckDB 执行查询 result = view.to_query() ``` ## 功能特性 ### 列引用 ```python tab.col("销量") # 引用一个列 tab.col("订单日期") # 列名支持中文及任意 UTF-8 字符 ``` ### 内置聚合函数 ```python tab.col("销量").sum() # 求和 tab.col("销量").avg() # 平均值 tab.col("销量").count() # 计数 tab.col("销量").count_distinct() # 去重计数 tab.col("销量").min() # 最小值 tab.col("销量").max() # 最大值 tab.col("销量").median() # 中位数 tab.col("销量").std() # 标准差 tab.col("x").custom("MY_FUNC") # 自定义聚合函数(简单模式) tab.col("x").custom("MY_FUNC({col}, {b}, {c})", b=1, c='xxx') # 模板模式:MY_FUNC(x, 1, 'xxx') ``` ### 自定义别名 ```python tab.col("销量").sum().alias("总销量") # 聚合别名 tab.col("订单日期").dt.year().alias("年") # 维度别名 ``` ### 日期提取(`.dt` 访问器) ```python tab.col("订单日期").dt.year() # 年 tab.col("订单日期").dt.month() # 月 tab.col("订单日期").dt.day() # 日 tab.col("订单日期").dt.quarter() # 季度 tab.col("订单日期").dt.week() # 周 tab.col("订单日期").dt.month_name() # 月份名称(如 "January") ``` ### 窗口维度(`.fixed()`) 将聚合函数转换为窗口函数,结果作为维度使用: ```python # 每个客户的首单日期 — 作为维度而非聚合 tab.col("订单日期").min().fixed("客户ID").alias("首单日期") # 多分区列 tab.col("订单日期").min().fixed(["客户ID", "产品"]) ``` ### INCLUDE / EXCLUDE LOD 维度 基于详细级别表达式(LOD)生成维度: ```python # INCLUDE:按视图维度 ∪ 产品名 分区 tab.col("销量").sum().include("产品名").alias("含产品销售额") # EXCLUDE:按视图维度 − 地区 分区 tab.col("销量").sum().exclude("地区").alias("排除地区销售额") # 多列 # tab.col("销量").sum().include(["产品名", "城市"]) # tab.col("销量").sum().exclude(["地区", "类别"]) ``` | 方法 | LOD 类型 | 分区列 | |------|----------|--------| | `.fixed(cols)` | FIXED | 仅 cols,忽略视图维度 | | `.include(cols)` | INCLUDE | 视图维度 ∪ cols | | `.exclude(cols)` | EXCLUDE | 视图维度 − cols | ### LOD 作为度量 在 LOD 维度上调用聚合方法,生成被外层聚合包裹的 LOD 度量(如 `SUM({FIXED ...})`): ```python # FIXED 作为度量 — SUM({FIXED [产品] : SUM([销量])}) tab.col("销量").sum().fixed("产品").sum().alias("产品级总额") # INCLUDE 作为度量 — AVG({INCLUDE [产品] : SUM([销量])}) tab.col("销量").sum().include("产品").avg().alias("含产品均价") # EXCLUDE 作为度量 — MAX({EXCLUDE [地区] : SUM([销量])}) tab.col("销量").sum().exclude("地区").max().alias("排除地区最大") # 在 View 中使用 view = tab.view( table="销售表", dims=["地区"], aggs=[ tab.col("销量").sum().alias("地区销量"), tab.col("销量").sum().fixed("产品").sum().alias("产品级总额"), ], ) ``` ### 复合度量(聚合间的算术运算) 对多个聚合表达式进行算术运算(`+`、`-`、`*`、`/`)。引擎自动将表达式拆解 为独立原子、分别压平再通过 LEFT JOIN 拼接运算。 ```python # 利润占比 = SUM(收入) / SUM(成本) profit_ratio = (tab.col("收入").sum() / tab.col("成本").sum()).alias("收入成本比") # 总利润 = SUM(收入) - SUM(成本) profit = (tab.col("收入").sum() - tab.col("成本").sum()).alias("总利润") # 利润率 = (SUM(收入) - SUM(成本)) * 100 / SUM(收入) margin = ( (tab.col("收入").sum() - tab.col("成本").sum()) * 100 / tab.col("收入").sum() ).alias("利润率%") # 含 LOD 度量:SUM(销售额) / SUM({FIXED [产品] : SUM(销售额)}) ratio = ( tab.col("销售额").sum() / tab.col("销售额").sum().fixed("产品").sum().alias("产品总销售额") ).alias("占比") # 反向运算:标量在左 remaining = (2000 - tab.col("销售额").sum()).alias("剩余") # 在 View 中使用 view = tab.view( table="销售表", dims=["地区"], aggs=[profit_ratio, profit], ) ``` 详见 [examples/10_compound_measures.py](./examples/10_compound_measures.py)。 ### 列间算术表达式 ```python # 计算金额 = 价格 × 数量,再求和 (tab.col("价格") * tab.col("数量")).sum() ``` ### CASE WHEN 条件表达式 通过 `tab.if_()` 构建 `CASE WHEN` 条件表达式,结果可作为维度使用: ```python # 基本分类 c = tab.if_(tab.col("销量") > 500, "大").else_("小").alias("订单大小") # 多分支分类 c = ( tab.if_(tab.col("销量") > 500, "高价值") .else_if_(tab.col("销量") > 200, "中价值") .else_("低价值") .alias("价值分类") ) # 在 View 中使用 view = tab.view( table="销售表", dims=["地区", c], aggs=[tab.col("销量").sum().alias("总销量")], ) # 配合窗口维度(FixedDim)使用 每天利润 = tab.col("利润").sum().fixed("订单日期").alias("每日利润") c2 = ( tab.if_(每天利润 > 2000, "高利润日") .else_if_(每天利润 < 0, "亏损日") .else_("正常日") .alias("日利润级别") ) ``` 支持运算符:`>`、`<`、`>=`、`<=`。值支持 `int`、`float`、`str`、`Column` 类型。 ## API 参考 ### `tab.col(name: str) -> Column` 创建列引用,返回 `Column` 对象,可调用聚合方法及日期提取方法。 ### `tab.view(table: str, dims: list, aggs: list) -> View` 创建透视视图。 | 参数 | 类型 | 说明 | |------|------|------| | `table` | `str` | 数据源表名 | | `dims` | `list[str \| Dimension]` | 维度列,用于 GROUP BY | | `aggs` | `list[Aggregation]` | 聚合表达式,用于 SELECT | ### `View.to_sql() -> str` 生成 SQL 字符串(DuckDB 方言)。 ### `View.to_query(con=None) -> duckdb.DuckDBPyResult` 通过 DuckDB 执行查询。 - 不传 `con`:使用 `duckdb.query()` 在默认连接上执行 - 传入 `con`:使用 `con.query()` 在指定连接上执行 ## 项目架构 ``` cwind-tableau/ ├── src/cwind_tableau/ │ ├── api/ # 门面层:tab.col, tab.view │ ├── core/ # 编排层:View、SQL 生成 │ └── models/ # 实体层:Column, Aggregation, Dimension ├── tests/ ├── docs/ └── examples/ ``` ## 设计原则 - **聚合可复用**:聚合表达式可定义为变量,在各种查询中复用 - **默认别名**:未使用 `.alias()` 时自动生成默认别名(如 `"SUM(销量)"`) - **链式 API**:所有操作通过链式调用完成,风格统一、可读性强 - **表达式即对象**:任何中间结果(列、聚合、维度)都是 Python 对象,可保存、传递、组合 ## 依赖 - **DuckDB** — SQL 执行引擎 - **sqlglot** — SQL 生成(未来支持方言转译) ## 许可证 MIT