跳转至

运行你的第一个指标查询

这篇教程拿一份现成的语义模型,浏览它的指标,把其中一个编译成 SQL, 再在真实数据库上跑一遍,看数字如期出来。走完就理解了 Dosi 的完整闭环: 模型 → 查询 → SQL → 结果

约 10 分钟,不需要 OSI 基础。

前置条件

已经安装好 Dosi,且 dosi validate --model fixtures/orders/model.yaml 能打印出 ✓ 1 semantic model(s) valid。 下面的命令都在仓库根目录执行。(dosi 不在 PATH 里的话, 请读作 ./target/release/dosi。)

用的是内置的 orders 模型:一个很小的电商示例,三张表 (orders、customers、products)、五个指标。它刻意做得小,方便手工核对每个数字。

第 1 步 —— 看看模型里有什么

先看这份模型定义了哪些指标:

$ dosi list metrics --model fixtures/orders/model.yaml
NAME              KIND        DATASETS          DESCRIPTION
revenue           aggregate   orders            Total order amount
order_count       aggregate   orders            Number of orders
unique_customers  aggregate   orders            Distinct purchasing customers
avg_order_value   ratio       orders            Revenue per order (ratio)
total_margin      expression  orders, products  Revenue minus cost (expression over two aggregates)

每个指标都带一个推断出来的种类(kind):

  • aggregate(聚合型):单个聚合,比如 SUM(orders.amount)
  • ratio(比率型):一个聚合除以另一个聚合,如 avg_order_value
  • expression(表达式型):多个聚合参与的算术运算,可以跨表, 如 total_margin 横跨 ordersproducts

种类不用声明,Dosi 从每个指标的 SQL 里推断出来。

第 2 步 —— 确认模型是有效的

$ dosi validate --model fixtures/orders/model.yaml
✓ 1 semantic model(s) valid

validate 检查结构、名称唯一性、关系是否指向真实存在的列,以及每个指标表达式 能否编译。这就是该放进 CI 的那道关卡,能在坏模型上线前拦住它。

第 3 步 —— 看看能按什么分组

指标回答"多少",字段负责把它拆开看。列出模型暴露的所有字段, 时间字段会带标记:

$ dosi list dimensions --model fixtures/orders/model.yaml
NAME                   TIME  DESCRIPTION
orders.order_id
orders.customer_id
orders.product_id
orders.order_date      time
orders.status
orders.amount
customers.customer_id
customers.region
customers.signup_date  time
products.product_id
products.category
products.unit_cost

time 标记的是时间维度,这里有 orders.order_datecustomers.signup_date,查询时可以按天、周、月、季、年分桶。 下面先按 orders.status 分组,再按月看一次。

第 4 步 —— 把第一条查询编译成 SQL

revenue,按订单状态拆开。不加 --execute,Dosi 只打印将要执行的 SQL; 加 --pretty 可以格式化输出(默认方言 DuckDB):

$ dosi query --model fixtures/orders/model.yaml \
    --metrics revenue --group-by orders.status --pretty
SELECT
  orders.status AS status,
  SUM(orders.amount) AS revenue
FROM main.orders AS orders
GROUP BY
  orders.status

只写了一个指标名和一个维度,聚合和 GROUP BY 都由 Dosi 生成。

第 5 步 —— 换数仓,但不改模型

这里才是语义引擎真正体现价值的地方。取按月的营收, 也就是在时间维度上用 :month 粒度,先为 DuckDB 编译:

$ dosi query --model fixtures/orders/model.yaml \
    --metrics revenue --group-by orders.order_date:month --pretty --dialect duckdb
SELECT
  DATE_TRUNC('MONTH', orders.order_date) AS order_date__month,
  SUM(orders.amount) AS revenue
FROM main.orders AS orders
GROUP BY
  DATE_TRUNC('MONTH', orders.order_date)

再把完全相同的请求编译给 MySQL,只有 --dialect 变了:

$ dosi query --model fixtures/orders/model.yaml \
    --metrics revenue --group-by orders.order_date:month --pretty --dialect mysql
SELECT
  STR_TO_DATE(DATE_FORMAT(orders.order_date, '%Y-%m-01'), '%Y-%m-%d') AS order_date__month,
  SUM(orders.amount) AS revenue
FROM main.orders AS orders
GROUP BY
  STR_TO_DATE(DATE_FORMAT(orders.order_date, '%Y-%m-01'), '%Y-%m-%d')

MySQL 没有 DATE_TRUNC,Dosi 就换成 MySQL 确实支持的写法来分桶, 而切换数仓一个字都没动模型。一份定义,在每个数仓上都是正确的 SQL。 两种情况下输出列都叫 order_date__month,即 {field}__{grain}

第 6 步 —— 真跑一次

这次真连数据库跑。先用内置的种子脚本把示例数据灌进一个本地 DuckDB 文件:

$ duckdb orders.db < fixtures/orders/seed.sql

然后给第 4 步那条"按状态看营收"加上 --execute,指向这个文件:

$ dosi query --model fixtures/orders/model.yaml \
    --metrics revenue --group-by orders.status \
    --execute --db orders.db
status     revenue
completed  350
cancelled  100
2 rows

手工核对:种子数据里已完成的订单是 100 + 50 + 80 + 120 = 350, 已取消的是 30 + 70 = 100。引擎给的数字完全对得上。

第 7 步 —— 按时间拆开看

把第 5 步那条按月的查询(:month 粒度)也真跑一次, 加上 --order order_date__month 按分桶排序:

$ dosi query --model fixtures/orders/model.yaml \
    --metrics revenue --group-by orders.order_date:month \
    --execute --db orders.db --order order_date__month
order_date__month    revenue
2024-01-01 00:00:00  150
2024-02-01 00:00:00  230
2024-03-01 00:00:00  70
3 rows

1 月 = 100 + 50 = 150,2 月 = 80 + 30 + 120 = 230,3 月 = 70。 又对上了,而且没写一行日期相关的 SQL 就拿到了按月分桶。

第 8 步 —— 试一个比率型指标

最后看一个聚合除以聚合的指标:全部六笔订单的平均订单金额。

$ dosi query --model fixtures/orders/model.yaml \
    --metrics avg_order_value --execute --db orders.db
avg_order_value
75
1 row

总营收 450 ÷ 6 笔订单 = 75。注意 Dosi 是按真正的比率算的, 还防住了整数除法,而不是对每行的值取平均。

你学到了什么

Dosi 的完整闭环已经端到端跑通:

  • 浏览模型的指标和维度(list)。
  • 校验模型(validate)。
  • 把指标编译成 SQL,再用一个参数重定向到另一个数仓 (query--dialect)。
  • 执行并手工核对结果(--execute)。

每个数字都和源数据对得上 —— 指标只定义一次,剩下的交给引擎。

接下来去哪

  • 连接数仓

    --execute 指向 Postgres、Snowflake、ClickHouse、StarRocks 等等。

  • 为什么用 Dosi

    详解"绝不悄悄重复计数"这个保证。

  • CLI 参考

    每一条命令和参数:--where、时间范围、--format json、Arrow。