# 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