语义视图关系建模与聚合粒度
语义视图通过外键关系自动处理表连接和聚合。关系怎么建,直接决定查询结果对不对 ——同样的数据,关系定义不同,"订单数""客户数"可能差几倍。本文用一组带边界数据的例子,说明外键关系如何影响 JOIN 和聚合粒度,以及哪些场景会被引擎拦截。
前置准备
全文示例共用以下四张表。数据特意构造了几种边界情况:一笔没有对应客户的孤儿订单、一个没有任何订单的客户、以及一个有多个地址的客户。
CREATE TABLE doc_customers (c_custkey INT, c_name STRING, c_city STRING);
CREATE TABLE doc_orders (o_orderkey INT, o_custkey INT, o_totalprice DECIMAL(12,2), o_orderdate DATE);
CREATE TABLE doc_line_items (l_orderkey INT, l_linenumber INT, l_qty INT);
CREATE TABLE doc_addresses (a_custkey INT, a_type STRING);
INSERT INTO doc_customers VALUES
(1,'Alice','New York'),(2,'Bob','Boston'),(3,'Carol','New York'),(4,'Dave','LA');
INSERT INTO doc_orders VALUES
(101,1,250.00,DATE'2025-01-01'),(102,1,150.00,DATE'2025-02-01'),
(103,2,300.00,DATE'2025-01-15'),(104,3,500.00,DATE'2025-03-01'),
(105,99,999.00,DATE'2025-04-01'); -- 客户 99 不存在:孤儿订单
INSERT INTO doc_line_items VALUES (101,1,5),(101,2,3),(103,1,7),(104,1,2);
INSERT INTO doc_addresses VALUES (1,'home'),(1,'work'),(2,'home'),(3,'home'),(3,'work'),(3,'billing');
数据特征:Dave(客户 4)没有订单;订单 105 的客户号 99 在客户表里不存在;Alice 有 2 笔订单、2 个地址,Carol 有 1 笔订单、3 个地址。
语义视图定义如下。customers 下挂两条一对多分支(orders、addresses),orders 下再挂 line_items:
CREATE SEMANTIC VIEW doc_rel_analysis
TABLES (
customers AS doc_customers PRIMARY KEY (c_custkey),
orders AS doc_orders
PRIMARY KEY (o_orderkey)
FOREIGN KEY (o_custkey) REFERENCES customers,
line_items AS doc_line_items
PRIMARY KEY (l_orderkey, l_linenumber)
FOREIGN KEY (l_orderkey) REFERENCES orders,
addresses AS doc_addresses
PRIMARY KEY (a_custkey, a_type)
FOREIGN KEY (a_custkey) REFERENCES customers
)
DIMENSIONS (
customers.customer_name AS customers.c_name,
customers.customer_city AS customers.c_city
)
METRICS (
orders.order_count AS COUNT(orders.o_orderkey),
orders.order_total AS SUM(orders.o_totalprice),
line_items.qty_total AS SUM(line_items.l_qty),
addresses.addr_count AS COUNT(addresses.a_type)
);
查询粒度由指标所在表驱动
按客户名分组、查订单指标时,结果不是"每个客户一行",而是以订单表为事实表、把客户属性挂上去:
SELECT * FROM semantic_view(
doc_rel_analysis,
DIMENSIONS customers.customer_name,
METRICS orders.order_count,
METRICS orders.order_total
) ORDER BY customer_name;
+---------------+-------------+-------------+
| customer_name | order_count | order_total |
+---------------+-------------+-------------+
| NULL | 1 | 999.00 |
| Alice | 2 | 400.00 |
| Bob | 1 | 300.00 |
| Carol | 1 | 500.00 |
+---------------+-------------+-------------+
两个关键现象:
孤儿订单出现,但维度为 NULL :订单 105 的客户号 99 在客户表里不存在,它仍出现在结果中,customer_namecustomer_name
为 NULLNULL
。说明订单不会因为关联不上客户而被丢弃。
无订单的客户不出现 :Dave 没有任何订单,结果里没有他。客户属性是挂在订单事实上的,没有订单事实就没有对应行。
记住这个心智模型:查询的粒度由指标所在的表决定,维度表的属性沿外键关系挂上去 。如果你需要"列出所有客户(含无订单的)",应直接查客户表,而不是查语义视图的订单指标。
链路内扇出:一对多的正确聚合
当查询涉及一条关系链上的两级(customers→orders→line_items),引擎会在各自的粒度上计算指标,不会因为 JOIN 放大:
SELECT * FROM semantic_view(
doc_rel_analysis,
DIMENSIONS customers.customer_name,
METRICS orders.order_count,
METRICS line_items.qty_total
) ORDER BY customer_name;
+---------------+-------------+-----------+
| customer_name | order_count | qty_total |
+---------------+-------------+-----------+
| Alice | 2 | 8 |
| Bob | 1 | 7 |
| Carol | 1 | 2 |
+---------------+-------------+-----------+
Alice 有 2 笔订单(101、102),订单 101 含 2 行明细(数量 5、3),订单 102 无明细。
order_countorder_count
是 2 而不是被明细行数放大成 3,
qty_totalqty_total
正确累加为 8。这正是语义层相对手写 JOIN 的价值:手写
orders JOIN line_itemsorders JOIN line_items
后对订单做
COUNTCOUNT
会重复计数,语义视图自动按指标的原始粒度聚合。
💡 提示 :不同指标组合下,空关联行(如孤儿订单、无明细订单)是否出现可能不同。本查询涉及 line_items,结果只包含有明细活动的三位客户。查询后建议核对行数与数值量级是否符合预期。
多分支与 chasm trap
orders 和 addresses 是 customers 下两条独立的一对多分支。把它们的指标放进同一次查询会报错:
SELECT * FROM semantic_view(
doc_rel_analysis,
DIMENSIONS customers.customer_name,
METRICS orders.order_count,
METRICS addresses.addr_count
);
CZLH-65000: Compiler internal error - generating logical plan failed,
error message No relationship found for table addresses
这是经典的 chasm trap(扇出陷阱):如果引擎天真地把 orders 和 addresses 通过 customers 连在一起,Alice 的 2 笔订单会和 2 个地址交叉成 4 行,把订单数和地址数同时放大。引擎选择直接报错,而不是静默返回错数。
对策是分两次查询,每个分支各自单独聚合。地址分支单独查是正确的:
SELECT * FROM semantic_view(
doc_rel_analysis,
DIMENSIONS customers.customer_name,
METRICS addresses.addr_count
) ORDER BY customer_name;
+---------------+------------+
| customer_name | addr_count |
+---------------+------------+
| Alice | 2 |
| Bob | 1 |
| Carol | 3 |
+---------------+------------+
⚠️ 注意 :报错信息
No relationship found for tableNo relationship found for table
字面上像"找不到关系",实际原因是两个分支无法在同一查询里合并聚合。把不同分支的指标拆到各自的查询中即可。
多列主键与多跳外键
line_items 用复合主键
(l_orderkey, l_linenumber)(l_orderkey, l_linenumber)
,并通过 line_items→orders→customers 两跳外键关联到客户。这类多列主键和多跳关系都受支持——前面"链路内扇出"按客户名查明细量,明细量就正确上卷到了客户粒度。
外键约束
建模时注意两条硬约束:
类型一致 :外键列与被引用列的数据类型必须相同,否则创建报错。例如订单的 o_custkeyo_custkey
(int)若去引用客户的 c_namec_name
(string),报 type int ... does not match type stringtype int ... does not match type string
。当外键列与被引用表主键列名不同时,需显式指定引用列名,如 FOREIGN KEY (o_custkey) REFERENCES customers (c_custkey)FOREIGN KEY (o_custkey) REFERENCES customers (c_custkey)
。
定义顺序 :被引用的逻辑表必须在 TABLESTABLES
子句中先于引用方定义。
详见创建语义视图 和语义视图能力与限制参考 。
常见问题与对策
现象 原因 对策 维度值出现 NULL 行 子表存在关联不上父表的孤儿行 检查外键数据完整性;或在外层 WHEREWHERE
过滤 NULL 某些维度成员缺失 该成员在指标表里没有事实行(如无订单的客户) 需要全集时直接查维度表,不查语义视图指标 查询报 No relationship found 组合了两个无直接关系路径的分支指标(chasm trap) 拆成多次查询,每次只取一条关系链上的指标 跨表指标数值被放大 手写 JOIN 才会双重计算 用语义视图自动按指标粒度聚合,不要手写 JOIN
相关文档