查询指标¶
指标查询只需写明要哪些指标、按哪些维度拆分;聚合、连接和 GROUP BY 由 Dosi 生成。
要的是行级记录而不是聚合值时,见查询明细行。
$ dosi query --model $DOSI_EXAMPLES/orders/model.yaml \
--metrics revenue,order_count \
--group-by customers.region,orders.status \
--order -revenue \
--execute --db orders.db
region status revenue order_count
east completed 270 3
west completed 80 1
east cancelled 70 1
west cancelled 30 1
4 rows
不用写 JOIN:revenue 在 orders 上,region 在 customers 上,
Dosi 沿着两者之间的关系连接。本页的例子都用自带的 orders 模型,
以及教程里导入数据的 orders.db 文件。
选指标和维度¶
--metrics 写指标名,--group-by 写 dataset.field。不确定有哪些,先问模型:
$ dosi list metrics --model $DOSI_EXAMPLES/orders/model.yaml
$ dosi list dimensions --model $DOSI_EXAMPLES/orders/model.yaml
$ dosi show metric revenue --model $DOSI_EXAMPLES/orders/model.yaml
metric: revenue
description: Total order amount
kind: aggregate
expr: SUM(orders.amount)
datasets: orders
group_by: orders, customers, products
time: metric_time (day, week, month, quarter, year)
group_by 列出这个指标能按哪些数据集拆分,time 列出它接受的时间粒度。
名字写错时直接拒绝,并给出模型里实际有的名字:
$ dosi query --model $DOSI_EXAMPLES/orders/model.yaml --metrics revenu
error: UnknownMetric: unknown metric "revenu"
candidates: revenue, order_count, unique_customers, avg_order_value, total_margin
时间粒度与 metric_time¶
在时间维度后面加 :粒度 即可按该时间粒度分桶,可选 day、week、month、quarter、year。
输出列名为 {field}__{grain}:
$ dosi query --model $DOSI_EXAMPLES/orders/model.yaml \
--metrics revenue --group-by orders.order_date:month \
--order order_date__month --execute --db orders.db
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
metric_time 代表每个指标的主(聚合)时间维度。问"按月看收入"时,
不必知道日期在哪一列,输出列名是 metric_time__month。
指标的主时间来自 D-TIME 扩展,
没有声明时取其数据集里唯一的时间字段。
时间范围¶
--start-time 包含该值,--end-time 不包含,即左闭右开 [start, end)。
2024 年 2 月这样写:
$ dosi query --model $DOSI_EXAMPLES/orders/model.yaml \
--metrics revenue --start-time 2024-02-01 --end-time 2024-03-01 \
--execute --db orders.db
revenue
230
1 row
group-by 里没有时间维度时,范围作用在每个指标的主时间上。 group-by 里有多个时间维度时,Dosi 不替你选:
$ dosi query --model $DOSI_EXAMPLES/orders/model.yaml \
--metrics revenue \
--group-by orders.order_date:month,customers.signup_date:month \
--start-time 2024-02-01 --end-time 2024-03-01
error: TimeRangeNeedsDimension: the group-by contains 2 time dimensions; name the one the time range applies to
candidates: orders.order_date, customers.signup_date
hint: pass time_dimension (e.g. --time-dimension orders.order_date, or metric_time)
加上 --time-dimension orders.order_date 就能跑。
过滤¶
--where 写 SQL,Dosi 按 AND 拆开,每个条件根据它读的内容决定位置:
- 条件读的是字段:过滤输入行,在聚合之前;
- 条件读的是已选的指标:过滤结果行,在聚合之后(相当于手写的
HAVING)。
有一个例外:查询选了窗口指标时,只读 group-by 字段的条件也过滤结果行,
保留下来的行仍是在完整总体上算出的排名和分区计数。想在缩小后的范围内排名,把条件写进 --context-filter。
一个 --where 可以两种都有:
$ dosi query --model $DOSI_EXAMPLES/orders/model.yaml \
--metrics revenue,order_count --group-by customers.region \
--where "orders.status = 'completed' AND revenue > 100" \
--execute --db orders.db
region revenue order_count
east 270 3
1 row
去掉 --execute、加上 --pretty 就能看到落点:orders.status 在内层 WHERE,
revenue > 100 在外层对结果行的 WHERE。查询里某一部分读不到的条件不会悄悄丢掉,
而是作为警告返回(打在 stderr 上;用 --format json 时在 warnings 里)。
--context-filter 是另一个过滤槽位:它始终限定参与计算的总体,
包括固定粒度字段的输入。什么时候需要它,见 LOD 过滤。
完整的落点规则见语义契约 §8。
排序与限行¶
--order 写逗号分隔的排序键,前缀 - 表示降序。排序键只能是指标或 group-by 项,
可以写输出列名(order_date__month),也可以照抄 --group-by 里的写法
(orders.order_date:month)。其他名字会被拒绝,并列出可用的键:
$ dosi query --model $DOSI_EXAMPLES/orders/model.yaml \
--metrics revenue --order -amount
error: UnknownOrderKey: order key "amount" is not a metric or group-by item
candidates: revenue
NULL 在每个数仓上都排在最后,升序降序都一样;
--order "-revenue nulls first" 让 NULL 排在最前。--limit 在排序之后截取行数,
所以 --order -revenue --limit 10 就是前 10 名。
编译还是执行¶
不加 --execute 时,dosi query 只打印 SQL。用来检查查询,或者把 SQL 交给别的工具:
| 参数 | 作用 |
|---|---|
--pretty |
格式化 SQL |
--dialect <name> |
编译成另一个数仓的 SQL,模型不用改 |
--explain |
在 SQL 上方打印逻辑计划:选了哪些连接、哪些聚合 |
--execute |
执行并打印结果行 |
--connection <name> |
在连接配置文件里的某个配置上执行(见连接数仓) |
--format json |
机器可读输出:sql、outputs、warnings,执行时还有 rows |
$ dosi query --model $DOSI_EXAMPLES/orders/model.yaml \
--metrics revenue --group-by metric_time:month \
--start-time 2024-01-01 --end-time 2024-03-01 \
--dialect snowflake --pretty
SELECT
DATE_TRUNC('MONTH', orders.order_date) AS metric_time__month,
SUM(orders.amount) AS revenue
FROM orders AS orders
WHERE
orders.order_date >= CAST('2024-01-01' AS DATE)
AND orders.order_date < CAST('2024-03-01' AS DATE)
GROUP BY
DATE_TRUNC('MONTH', orders.order_date)
不写 --dialect 时,Dosi 按默认连接的方言编译(没有默认连接时用 DuckDB),所以检查过的 SQL 就是 --execute 实际执行的 SQL。
Dosi 何时拒绝¶
Dosi 不返回它无法担保的数字。最重要的拒绝是 fan_out_risk:
指标会跨一个让行重复的连接求和。在自带的 tpcds 模型里,
store_productivity 汇总的是按门店的值,而一个门店并不对应唯一的日期:
$ dosi query --model $DOSI_EXAMPLES/tpcds/model.yaml \
--metrics total_sales,store_productivity --group-by date_dim.d_date:month
error: FanOutRisk: metric "store_productivity" aggregates Sum over dataset "store", which has no many→one path to ["date_dim"]; evaluating a duplicate-sensitive aggregate (SUM/AVG/COUNT) across that join would double-count rows
hint: group by dimensions reachable from the metric's dataset, or restate the metric with a duplicate-insensitive aggregate (COUNT DISTINCT/MIN/MAX)
手写这个连接也能得到一个数字,但它是错的。改成按指标所在数据集能到达的维度分组, 或者把两个指标分开查。
每次拒绝都带机器可读的 code、涉及的名字、名字写错时的 candidates,
以及 suggested_retry;加 --format json 就能拿到 JSON。常见的几种:
| 错误码 | 含义 |
|---|---|
unknown_metric、unknown_dimension、unknown_field |
模型里没有这个名字;candidates 列出有的 |
unknown_order_key |
排序键既不是指标,也不是 group-by 项 |
time_range_needs_dimension |
有多个时间维度;用 --time-dimension 指明 |
fan_out_risk |
结果会重复计数;换分组,或者拆开查 |
完整列表见错误码。
CLI 之外¶
同一个指标查询在各个接口上字段相同、语义相同、错误也相同:
- REST API:
POST /v1/query/compile、/v1/query/explain、/v1/query/execute - MCP 服务:工具
compile_sql、explain_query、run_query - Python:
dosi-enginewheel 里的Engine.compile()和Engine.execute()