明细查询¶
同一份语义模型,Dosi 回答两类问题。
| 问题 | 命令 | 返回 |
|---|---|---|
| 上个月各地区的收入是多少? | dosi query |
聚合后的指标 |
| 列出 Enterprise 客户最大的 100 笔订单 | dosi select |
明细行 |
两者走同一个计划器、同一张关系图、同一套方言后端,只差一个算子:
query 做聚合,select 做投影。
$ dosi select \
--from orders \
--fields orders.order_id,orders.amount,customers.name \
--where "customers.tier = 'Enterprise'" \
--order -amount \
--limit 100
不用写 JOIN。只要点名想要哪些字段,Dosi 自己判断它们落在哪些数据集上、 哪些关系把它们连起来,以及这样连是否安全。
--from 定义粒度,不是 SQL 的 FROM¶
--from orders 的含义是一行结果对应一个订单,不是"SQL 里只能出现
orders"。
字段、过滤、排序、时间范围都可以引用别的数据集。Dosi 收集这次查询需要的 全部数据集,为每一个找到关系路径,然后生成连接:
customers 一个字段都没投影,但过滤需要它,于是照样连上。结果仍是一行
一个订单。
这一点成立,是因为 Dosi 只沿一个方向走关系:从多的一侧走向一的一侧。 每一跳最多给已有的每一行附上目标表的一行,所以整条路径走完,要的粒度 依然不变。
字段的三种写法¶
| 写法 | 例子 | 什么时候用 |
|---|---|---|
| 裸字段 | amount |
该名字在模型里唯一 |
| 数据集限定 | orders.amount |
常规写法 |
| 关系前缀 | orders_to_buyer.name |
需要自己指定连接路径 |
第三种在模型能用多条路径抵达同一数据集时才用得上。假设 orders 到
regions 既能直连(发货地),也能经 customers(买家所在地)。这时问
regions.region_name 确实有歧义,Dosi 会说出来,而不是替你挑一条:
$ dosi select --from orders --fields regions.region_name --format json
{
"code": "ambiguous_join_path",
"candidates": [
"orders -[orders_to_customers]-> customers, customers -[customers_to_regions]-> regions",
"orders -[orders_to_ship_region]-> regions"
],
"suggested_retry": "name the path you mean by prefixing the field with its relationships, one of: orders_to_customers.customers_to_regions.<field> | orders_to_ship_region.<field>"
}
把其中一个前缀贴回去,查询就能跑:
关系前缀在 --where 里同样可用,路径多长都行;指标平面的 --group-by
也接受 —— 到处是同一种写法。
输出列名¶
输出列名 = 字段引用去掉根前缀,剩下的每个 . 换成 __:
引用(根为 orders) |
列名 |
|---|---|
orders.order_id |
order_id |
customers.name |
customers__name |
orders_to_ship_region.region_name |
orders_to_ship_region__region_name |
orders.created_at:month |
created_at__month |
根字段保持裸名,因为问的就是它;其余都带上来源,这样两个数据集各有一个
name 时不会悄悄撞在一起。两个字段会产生同一个列名时报
duplicate_output_name。
什么情况下拒绝¶
引擎绝不返回自己无法担保的行。有三种拒绝值得认识。
detail_fanout —— 要的数据集只能逆着连接方向抵达。一个客户有多笔
订单,所以从 --from customers 投影订单字段会把一个客户变成好几行:
$ dosi select --from customers --fields customers.name,orders.order_id --format json
{
"code": "detail_fanout",
"suggested_retry": "query at the finer grain instead: --from orders and project the customers fields you need"
}
改法就写在提示里:换到更细的粒度去问。--from orders --fields
orders.order_id,customers.name 给出同样的信息,一行一个订单,不重复。
ambiguous_join_path —— 多条路径都能抵达该字段。按上面的办法点名一条。
metric_in_detail_query —— 指标需要聚合、时间范围和分组,明细查询
这三样都没有。提示里直接给出该用哪个命令。
每个拒绝都带机器可读的 code、涉及的名字,以及 suggested_retry。
参见错误码。
时间范围¶
--start-time / --end-time 给出半开区间 [start, end)。不写
--time-dimension 时,范围落到根数据集的主时间维度上:
$ dosi select \
--from orders \
--fields orders.order_id,orders.amount \
--start-time 2026-01-01 --end-time 2026-02-01
根数据集没有主时间维度、投影里也没有唯一的时间字段时,Dosi 会要求点名 一个,而不是猜。
行数上限¶
--limit 默认 100,上限截到 10000。四个接口面都是这个规则,
所以 Agent 忘了写 limit 只会拿到一份样本,不会扫全表。
CLI 之外¶
同一个 DetailQuery 通到每个接口面,语义一致、错误一致。
信任结果之前先看连接¶
--explain 打印逻辑计划:根数据集、选中的每一条连接,以及投影。
$ dosi select --from orders --fields orders.order_id,customers.name --explain
logical plan
Project[order_id, customers__name]
JoinRelated[orders_to_customers -> customers, left]
ReadDataset[orders]
连接默认 LEFT,所以匹配不上的行会带着 NULL 保留下来,不会凭空消失。
关系可以改成 INNER,见控制 JOIN 与空值填充。
本版本的限制¶
- 一次查询抵达每个数据集只能走一条路径。两条命名路径指向同一数据集 需要两个表别名,目前还没有,所以引擎宁可拒绝,也不把同一个连接读两遍。
- 排序键必须是已投影的列。想按哪个字段排序,就把它投影出来。
- 指标和字段不能混在一次查询里。分别查两个平面,结果自己拼。