<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>Cube on AI博士 万戈</title>
        <link>https://www.yesmiracle.net/tags/cube/</link>
        <description>AI博士万戈的技术博客，聚焦 Agentic AI、AI Infra 与 Agent Security，分享 AI 基础设施与工程落地实践。</description>
        <generator>Hugo -- gohugo.io</generator>
        <language>zh-cn</language>
        <managingEditor>admin@yesmiracle.net (万戈)</managingEditor>
        <webMaster>admin@yesmiracle.net (万戈)</webMaster>
        <lastBuildDate>Sat, 10 Oct 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://www.yesmiracle.net/tags/cube/index.xml" rel="self" type="application/rss+xml" /><item>
        <title>手写语义层的时代开始松动了：dbt、LookML、Cube、Snowflake 四条老路，和一条想自己「读」出语义的新路！</title>
        <link>https://www.yesmiracle.net/post/20261010-semantic-layer-handwritten-vs-inferred/</link>
        <pubDate>Sat, 10 Oct 2026 00:00:00 +0000</pubDate>
        <author>admin@yesmiracle.net (万戈)</author>
        <guid>https://www.yesmiracle.net/post/20261010-semantic-layer-handwritten-vs-inferred/</guid>
        <description>&lt;img src="https://www.yesmiracle.net/post/20261010-semantic-layer-handwritten-vs-inferred/cover.svg" alt="Featured image of post 手写语义层的时代开始松动了：dbt、LookML、Cube、Snowflake 四条老路，和一条想自己「读」出语义的新路！" /&gt;&lt;p&gt;我做 Agent 查数据这块有一阵子了，有个场景反复出现：业务同学问「上个季度总营收多少」，Agent 秒回一个数，SQL 干干净净，没人报错。然后财务那边给的是另一个数。&lt;/p&gt;
&lt;p&gt;两个人都没错。错的是「营收」这个词在那套仓库里从来没有被写下来过——取消的订单算不算？用四张订单表里的哪一张？这个决定，平时只活在三个建仓库的工程师脑子里。&lt;/p&gt;
&lt;p&gt;要把这份「没写下来的知识」变成机器能读的东西，传统的答案是语义层。但 dbt、LookML、Cube、Snowflake 这四家主流工具，都要求你自己动手把它写出来。&lt;/p&gt;
&lt;p&gt;前段时间 Show HN 上冒出来一个叫 semlayer 的项目，口号直接冲着这件事来：&lt;strong&gt;skip the quarter of hand-writing dbt YAML&lt;/strong&gt;——别再用一个季度去手写语义层的 YAML 了。它的主张是，语义不该手写，应该让引擎从你自己的数据里读出来，写进一份任何 Agent 都能读的开放文件。&lt;/p&gt;
&lt;p&gt;我把这五条路线摊在一张桌子上看了一遍。有意思的地方不在功能对比，而在于它们对同一个问题的不同回答：语义层里那份「真相」，到底该由谁产出、谁维护、谁背书。&lt;/p&gt;
&lt;p&gt;先上总表，后面再一条条拆。&lt;/p&gt;
&lt;h2 id=&#34;五条路线总览&#34;&gt;五条路线总览&lt;/h2&gt;
&lt;p&gt;这张表是我自己整理的，每一格都尽量对应到各家的真实机制，不是官网文案。&lt;/p&gt;
&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th&gt;维度&lt;/th&gt;
          &lt;th&gt;dbt Semantic Layer&lt;/th&gt;
          &lt;th&gt;LookML&lt;/th&gt;
          &lt;th&gt;Cube&lt;/th&gt;
          &lt;th&gt;Snowflake Semantic Views&lt;/th&gt;
          &lt;th&gt;semlayer&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td&gt;谁来写&lt;/td&gt;
          &lt;td&gt;人手写 YAML&lt;/td&gt;
          &lt;td&gt;人手写 view.lkml&lt;/td&gt;
          &lt;td&gt;人手写 cube YAML/JS&lt;/td&gt;
          &lt;td&gt;人手写 DDL&lt;/td&gt;
          &lt;td&gt;引擎推断 + 人工 review&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;自动发现业务规则&lt;/td&gt;
          &lt;td&gt;否&lt;/td&gt;
          &lt;td&gt;否&lt;/td&gt;
          &lt;td&gt;否&lt;/td&gt;
          &lt;td&gt;否&lt;/td&gt;
          &lt;td&gt;是（假设检验，scope: measures）&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;过期 / 漂移检测&lt;/td&gt;
          &lt;td&gt;靠自建调度&lt;/td&gt;
          &lt;td&gt;靠 Git / IDE 校验&lt;/td&gt;
          &lt;td&gt;需自建&lt;/td&gt;
          &lt;td&gt;MAX_STALENESS + 物化&lt;/td&gt;
          &lt;td&gt;semlayer drift（orphaning 状态机）&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;消费者契约&lt;/td&gt;
          &lt;td&gt;无&lt;/td&gt;
          &lt;td&gt;无&lt;/td&gt;
          &lt;td&gt;无&lt;/td&gt;
          &lt;td&gt;部分（verified queries）&lt;/td&gt;
          &lt;td&gt;有（normative SPEC）&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;交付方式&lt;/td&gt;
          &lt;td&gt;编译进 dbt 项目&lt;/td&gt;
          &lt;td&gt;Looker 平台内&lt;/td&gt;
          &lt;td&gt;API / 语义层服务&lt;/td&gt;
          &lt;td&gt;schema 级对象，仓库内&lt;/td&gt;
          &lt;td&gt;MCP（stdio），可移植 YAML&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;绑定深度&lt;/td&gt;
          &lt;td&gt;项目级&lt;/td&gt;
          &lt;td&gt;Looker 平台&lt;/td&gt;
          &lt;td&gt;商业授权&lt;/td&gt;
          &lt;td&gt;只能 Snowflake&lt;/td&gt;
          &lt;td&gt;仓库无关（目前 3 家）&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;成本量级&lt;/td&gt;
          &lt;td&gt;人力密集&lt;/td&gt;
          &lt;td&gt;人力密集&lt;/td&gt;
          &lt;td&gt;商业授权&lt;/td&gt;
          &lt;td&gt;平台内&lt;/td&gt;
          &lt;td&gt;约 $0.70 / 100 张表&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;看这张表你会发现一件事：前四列在「谁来写」这一行全是「人手写」，只有最后一列不同。这不是巧合。&lt;/p&gt;
&lt;p&gt;举个具体点的例子。像「平均订单金额」这种指标，它在任何一张表里都不是一列——它是某个人关于「除以什么」的一个决定，而两种都说得通的算法，结果能差三倍。两份都对、都对得理直气壮，最后在同一个会上报出两个数。语义层要做的，就是把这个决定写下来：哪些行算数、分母是什么、凭什么这么定。区别只在于——这份记录，是有人坐下来手写，还是引擎从数据里读出来。&lt;/p&gt;
&lt;h2 id=&#34;dbt-semantic-layer把语义写进-yaml查询时现拼-sql&#34;&gt;dbt Semantic Layer：把语义写进 YAML，查询时现拼 SQL&lt;/h2&gt;
&lt;p&gt;dbt 的路子是把语义直接写进项目里，和你的转换模型放在一起。每个模型挂一个 &lt;code&gt;semantic_model&lt;/code&gt; 块，声明它的聚合时间维度、实体（也就是 join key）和维度：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;models&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  - &lt;span style=&#34;color:#f92672&#34;&gt;name&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;fact_transactions&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;semantic_model&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;enabled&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;agg_time_dimension&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;transaction_date  &lt;/span&gt; &lt;span style=&#34;color:#75715e&#34;&gt;# 必填&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;columns&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        - &lt;span style=&#34;color:#f92672&#34;&gt;name&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;transaction_id&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#f92672&#34;&gt;entity&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#f92672&#34;&gt;type&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;primary         &lt;/span&gt; &lt;span style=&#34;color:#75715e&#34;&gt;# primary / foreign / unique / natural&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#f92672&#34;&gt;name&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;transaction&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        - &lt;span style=&#34;color:#f92672&#34;&gt;name&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;customer_id&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#f92672&#34;&gt;entity&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#f92672&#34;&gt;type&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;foreign&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#f92672&#34;&gt;name&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;customer&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        - &lt;span style=&#34;color:#f92672&#34;&gt;name&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;transaction_date&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#f92672&#34;&gt;granularity&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;day&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#f92672&#34;&gt;dimension&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#f92672&#34;&gt;type&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;time&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        - &lt;span style=&#34;color:#f92672&#34;&gt;name&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;order_country&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#f92672&#34;&gt;dimension&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#f92672&#34;&gt;type&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;categorical&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;metrics&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        - &lt;span style=&#34;color:#f92672&#34;&gt;name&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;transaction_total&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#f92672&#34;&gt;type&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;simple             &lt;/span&gt; &lt;span style=&#34;color:#75715e&#34;&gt;# simple / ratio / derived / cumulative...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#f92672&#34;&gt;agg&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;sum&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#f92672&#34;&gt;expr&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;transaction_total&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;代码来自 dbt 官方文档的 semantic models 页。这里有几个设计点值得记住。&lt;/p&gt;
&lt;p&gt;一是 &lt;code&gt;agg_time_dimension&lt;/code&gt; 是&lt;strong&gt;必填&lt;/strong&gt;，而且任何要定义 metric 的语义模型，至少得有一个带 granularity 的时间维度列。换句话说，dbt 不是让你「随便」写，它逼你先把时间轴定下来。&lt;/p&gt;
&lt;p&gt;二是 MetricFlow（dbt 语义层的执行引擎）在&lt;strong&gt;查询时&lt;/strong&gt;才动态构造 join，而不是预先物化出所有可能的分组。官方原话大意是：你不用提前建一套「把所有分组方式都算好」的系统，问什么维度，它现场拼什么 join。这个思路省事，但也意味着复杂 join 的代价摊到了每一次查询上。&lt;/p&gt;
&lt;p&gt;三是时间聚合还依赖一个叫 &lt;strong&gt;time spine&lt;/strong&gt;（时间脊柱）的东西——一张连续的日期表，所有的按时间 join 和聚合都架在它上面。第一次用 dbt 语义层的人经常在这里卡住：模型写完了，跑时间维度就报错，因为它没有时间脊柱。&lt;/p&gt;
&lt;p&gt;一句话总结 dbt 这一路：&lt;strong&gt;语义是你团队的一项持续工程&lt;/strong&gt;。写得越细，Agent 回答得越准，但这笔人力账要一直付下去。&lt;/p&gt;
&lt;h2 id=&#34;lookml这套东西的祖师爷也把写刻进了基因&#34;&gt;LookML：这套东西的祖师爷，也把「写」刻进了基因&lt;/h2&gt;
&lt;p&gt;Looker 的 LookML 是这类工具的鼻祖。它的基本单位是 view 文件，里面声明维度和度量：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-lookml&#34; data-lang=&#34;lookml&#34;&gt;view: orders {
  dimension: id {
    primary_key: yes
    type: number
    sql: ${TABLE}.id ;;
  }
  dimension: customer_id {
    sql: ${TABLE}.customer_id ;;
  }
  dimension_group: created {
    type: time
    timeframes: [date, week]
    sql: ${TABLE}.created_at ;;
  }
  measure: count {
    type: count
    sql: ${id} ;;
  }
  measure: total_amount {
    type: sum
    sql: ${amount} ;;
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;dimension_group&lt;/code&gt; 是个挺聪明的设计，一次声明就能自动铺开 date / week / month 等一堆时间维度。查询侧用 &lt;code&gt;explore&lt;/code&gt; 加 &lt;code&gt;join&lt;/code&gt; 把多个 view 拼起来：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-lookml&#34; data-lang=&#34;lookml&#34;&gt;explore: orders {
  join: customers {
    sql_on: ${orders.customer_id} = ${customers.id} ;;
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;如果查询重、想缓存，可以定义 &lt;code&gt;derived_table&lt;/code&gt;，写成 PDT（persistent derived table），按 &lt;code&gt;persistence strategy&lt;/code&gt; 定期重算，落到数据库的一个 scratch schema 里。另外还有个 &lt;code&gt;manifest.lkml&lt;/code&gt; 管项目级配置，比如从别的项目 &lt;code&gt;remote_dependency&lt;/code&gt; 导入 LookML 文件。&lt;/p&gt;
&lt;p&gt;LookML 的问题也在这里：它的一切都长在 Looker 平台上。模型写得再漂亮，离开 Looker 就编译不了。语义和平台是绑死的。&lt;/p&gt;
&lt;h2 id=&#34;cube语义层顺手把快也解决了&#34;&gt;Cube：语义层顺手把「快」也解决了&lt;/h2&gt;
&lt;p&gt;Cube 的建模语言是 cube，可以用 YAML 或 JS 写，里面是维度、度量，再加上它最有特色的一块——&lt;strong&gt;pre-aggregations（预聚合）&lt;/strong&gt;：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;cubes&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  - &lt;span style=&#34;color:#f92672&#34;&gt;name&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;orders&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;sql_table&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;orders&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;measures&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#f92672&#34;&gt;name&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;count&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;type&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;count&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;dimensions&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#f92672&#34;&gt;name&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;status&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;type&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;string&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;sql&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;status&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;pre_aggregations&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#f92672&#34;&gt;name&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;orders_by_status&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;measures&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          - &lt;span style=&#34;color:#ae81ff&#34;&gt;CUBE.count&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;dimensions&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          - &lt;span style=&#34;color:#ae81ff&#34;&gt;CUBE.status&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;time_dimension&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;CUBE.created_at&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;granularity&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;day&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;预聚合本质上是物化好的 rollup 表，Cube 会做 &lt;strong&gt;aggregate awareness&lt;/strong&gt;：一个查询来的时候，它自己判断能不能用现成的 rollup 回答，能就命中，不能才回源。它还有 &lt;code&gt;rollup_join&lt;/code&gt; 把不同 cube 的维度和度量拼进同一张 rollup，&lt;code&gt;rollup_lambda&lt;/code&gt; 支持把实时数据源和预聚合混在一起。要压规模，还有分区和索引，落进 Cube Store。&lt;/p&gt;
&lt;p&gt;所以 Cube 和前三家的差别，不完全在「语义」上，而在它把语义层和&lt;strong&gt;性能层&lt;/strong&gt;绑成了一件事。你写一份模型，顺带拿到一套缓存和物化方案。代价一样：全部手写，而且这是要商业授权的产品。&lt;/p&gt;
&lt;h2 id=&#34;snowflake-semantic-views仓库亲自下场&#34;&gt;Snowflake Semantic Views：仓库亲自下场&lt;/h2&gt;
&lt;p&gt;Snowflake 的做法最不一样——它没做外部工具，而是把语义变成数据库里的一个&lt;strong&gt;原生对象&lt;/strong&gt;，用 DDL 定义，和表、视图平级：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;CREATE&lt;/span&gt; SEMANTIC &lt;span style=&#34;color:#66d9ef&#34;&gt;VIEW&lt;/span&gt; finance_metrics
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  TABLES (
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    orders &lt;span style=&#34;color:#66d9ef&#34;&gt;AS&lt;/span&gt; orders_tbl &lt;span style=&#34;color:#66d9ef&#34;&gt;PRIMARY&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;KEY&lt;/span&gt; (order_id)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  RELATIONSHIPS (
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    orders &lt;span style=&#34;color:#66d9ef&#34;&gt;AS&lt;/span&gt; customers_id &lt;span style=&#34;color:#66d9ef&#34;&gt;REFERENCES&lt;/span&gt; customers (customer_id)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  FACTS ( ... )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  DIMENSIONS ( ... )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  METRICS (
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    orders.total_revenue &lt;span style=&#34;color:#66d9ef&#34;&gt;AS&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;SUM&lt;/span&gt;(orders.amount)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  AI_SQL_GENERATION &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Revenue excludes cancelled orders.&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  AI_VERIFIED_QUERIES (
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    quarterly_revenue &lt;span style=&#34;color:#66d9ef&#34;&gt;AS&lt;/span&gt; (
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      QUESTION &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;What was Q3 revenue?&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;SQL&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;SELECT ...&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  );
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;语法来自 Snowflake 的 &lt;code&gt;CREATE SEMANTIC VIEW&lt;/code&gt; 文档。这一路有几个细节很能说明它的思路：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;关系里支持 &lt;strong&gt;ASOF join&lt;/strong&gt; 和 &lt;strong&gt;range join&lt;/strong&gt;（&lt;code&gt;BETWEEN start_column AND end_column EXCLUSIVE&lt;/code&gt;），也就是把「按时间就近匹配」「按区间匹配」这类容易写错的 join 变成了声明式的东西。&lt;/li&gt;
&lt;li&gt;指标支持 &lt;strong&gt;NON ADDITIVE BY&lt;/strong&gt; 和窗口函数指标，专门处理那种「不能直接求和」的度量。&lt;/li&gt;
&lt;li&gt;有 &lt;code&gt;WITH SYNONYMS&lt;/code&gt;、&lt;code&gt;AI_SQL_GENERATION&lt;/code&gt; 自定义指令，还有 &lt;strong&gt;AI_VERIFIED_QUERIES&lt;/strong&gt;——人工背书的「金标 SQL」，相当于把资深分析师验过的答案固化成参考。&lt;/li&gt;
&lt;li&gt;有 &lt;code&gt;MAX_STALENESS&lt;/code&gt; 和物化，管新鲜度。&lt;/li&gt;
&lt;li&gt;从旧的 Cortex Analyst YAML 迁移，走 &lt;code&gt;SYSTEM$CREATE_SEMANTIC_VIEW_FROM_YAML&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;语义视图的标准 SQL 查询在 2026 年 3 月已经 GA，等于把「用 SQL 语义化地查数据」这件事搬进了仓库本身。但代价也直接写在设计里：&lt;strong&gt;它只能看 Snowflake 自己仓库里的数据&lt;/strong&gt;。你栈里那另外二三十个系统，它管不到。&lt;/p&gt;
&lt;h2 id=&#34;semlayer干脆不写&#34;&gt;semlayer：干脆不写&lt;/h2&gt;
&lt;p&gt;前面四家，无论包装成什么样，本质都是「你来写」。semlayer 想换个前提：&lt;strong&gt;别写，让引擎读出来&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;它的流水线分四段，每段都带 confidence 和 provenance：先 profile 每一列（每张表两次宽 SELECT，置信度低于 0.7 才升级到 LLM），再 link 出没人声明的外键（关键约束是「统计永不单独自动纳入」，必须命名信号 + LLM 双证），然后 describe 做两遍上下文传播生成列描述，最后 enrich 用&lt;strong&gt;聚合对账&lt;/strong&gt;——假设检验——从汇总表里反推出业务规则。它自己宣称在 TPC-DS 风格的测试上，104 个未声明外键全部找对，F1 = 1.0，而且 0 个假陷阱被误纳。&lt;/p&gt;
&lt;p&gt;它给出的 benchmark 数字是这样的（messy_mart，38 个业务问题，Claude Haiku 档）：&lt;/p&gt;
&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th&gt;配置&lt;/th&gt;
          &lt;th&gt;答对率&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td&gt;裸 schema&lt;/td&gt;
          &lt;td&gt;0.42&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;语义层&lt;/td&gt;
          &lt;td&gt;0.87&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;语义层 + lint&lt;/td&gt;
          &lt;td&gt;0.89&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;它修的是&lt;strong&gt;静默错误&lt;/strong&gt;：裸 schema 下「总营收」悄悄把取消订单算进去，报出 $16.3M，而财务那边是 $14.6M；fan-out join 会把总额悄悄翻三倍，只有 linter 看得见。&lt;/p&gt;
&lt;p&gt;这里有个很多人没注意的细节——semlayer 同一份报告里，还藏着一个&lt;strong&gt;负面结果&lt;/strong&gt;：在干净、命名规范的 TPC-DS 仓库上，裸 DDL 是 0.67，加了语义层反而降到 0.58。它把这个对自己不利的数字和好数字并排放在仓库里，还写了句「如果你的仓库很整齐，你不需要这个」。&lt;/p&gt;
&lt;p&gt;它最有区分度的一块，是 &lt;code&gt;spec/SPEC.md&lt;/code&gt; 里的&lt;strong&gt;消费者契约&lt;/strong&gt;。契约规定：置信度低于 0.6 的推断&lt;strong&gt;禁止&lt;/strong&gt;用来回答；&lt;code&gt;deprecated&lt;/code&gt; 的表禁新查询；&lt;code&gt;fanout_risk&lt;/code&gt; 为真的聚合&lt;strong&gt;必须&lt;/strong&gt;套策略或者直接拒绝，静默求和算 non-conforming；SCD2 必须走 asof 时间窗。它自己的 linter（&lt;code&gt;semlayer lint&lt;/code&gt; / MCP 的 &lt;code&gt;check_sql&lt;/code&gt;）校验的是&lt;strong&gt;语义而不是语法&lt;/strong&gt;——「跑得通的查询照样可能在求和已取消的订单」。&lt;/p&gt;
&lt;p&gt;交付方式是 MCP，把 layer.yaml 通过 stdio 喂给任意 Agent；工具面铺开有 &lt;code&gt;semantic_search&lt;/code&gt;、&lt;code&gt;get_domains&lt;/code&gt;、&lt;code&gt;get_tables&lt;/code&gt;、&lt;code&gt;table_detail&lt;/code&gt;、&lt;code&gt;get_metrics&lt;/code&gt;、&lt;code&gt;route_intent&lt;/code&gt;、&lt;code&gt;check_sql&lt;/code&gt;、&lt;code&gt;compile_metric&lt;/code&gt; 这一批，连上时还会下发一组 standing instructions。这里有个我觉得比「推断」本身更值得抄的设计——&lt;strong&gt;compile, don&amp;rsquo;t execute&lt;/strong&gt;：这个 MCP server 不碰你的仓库，没有凭证、不开连接、不读一行数据，它只吐 SQL 文本，执行交给仓库自己那一侧的 MCP server。这么一分，语义层就退成了一个纯粹的「编译层」，风险面小了一大截。另外还有个 &lt;code&gt;--no-llm&lt;/code&gt; 全确定性模式，零 API 调用，照样能用。成本大约 &lt;strong&gt;$0.70 / 100 张表&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;当然，局限也得如实说：beta，star 个位数，只支持 Snowflake / BigQuery / DuckDB（Iceberg 要走 DuckDB 桥），LLM 只接 Anthropic，没有托管服务，校准数据还等第三方复现。&lt;/p&gt;
&lt;h2 id=&#34;把五家摊在一起我真实的判断&#34;&gt;把五家摊在一起，我真实的判断&lt;/h2&gt;
&lt;p&gt;看功能清单没意思，我想说的是这几个更别扭的点。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第一，前四家把「写」当成用户的责任，这不是设计选择，是商业模式。&lt;/strong&gt; 你手写出来的 YAML、LookML、语义视图 DDL，是一份粘性资产——只有它家能编译、能跑。写得越细，你越走不了。「自推断」在方向上是反粘性的：语义一旦是一份可搬家的开放文件，平台就少了拿捏你的把手。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第二，大厂两周能不能抄出「推断」？功能上能，动机上未必。&lt;/strong&gt; 这点很多人会误判。Snowflake 其实已经把 AI 塞进语义视图了——&lt;code&gt;AI_SQL_GENERATION&lt;/code&gt;、&lt;code&gt;AI_VERIFIED_QUERIES&lt;/code&gt;、从 YAML 迁移都有。但你仔细看，它做的是「让 AI 用你&lt;strong&gt;写好的&lt;/strong&gt;语义去回答问题」,不是「推断出语义」。两个方向之间隔着的，是整个成本模型：前者让你更离不开它写的那份语义，后者让你有一天能带着语义走人。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第三，真正的问题不是「谁写」，是「谁保证它不过期」。&lt;/strong&gt; 四家的手写层都有一个共同的死穴——&lt;code&gt;ALTER TABLE&lt;/code&gt; 那天它就过期了。semlayer 的 &lt;code&gt;drift&lt;/code&gt; 命令是冲这个来的。但我想说句可能不讨喜的：漂移检测本身不难，难的是「谁有权改那份 ground truth」。这是治理问题，不是算法问题。一份推断出来的层，第一次跟你的业务规则打架时，谁签字、谁负责、审计怎么留痕，这些没解决之前，「自推断」在生产环境里就还是个 demo。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第四，别急着喊颠覆。&lt;/strong&gt; 前面那个负面结果——干净仓库上裸 DDL 0.67 &amp;gt; 语义层 0.58——说明这事儿的市场是「存量脏数据」，不是「新建数仓」。如果你的仓库本来就整齐，这整套东西对你的增量很有限。semlayer 自己承认了这一点,我认为这比它那些漂亮数字更能说明它真懂自己的边界。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第五，短期赢家大概率还是 dbt 和 Snowflake，因为它们卖的是分发，不是算法。&lt;/strong&gt; dbt 已经把语义层做成了「写了转换逻辑就顺手写一点语义」的自然延伸；Snowflake 直接把它做进仓库，用户的迁移成本接近零。semlayer 最可能的结局也不是被打死，而是——&lt;strong&gt;「推断」变成一个开关，被它们抄走&lt;/strong&gt;。对你来说这不是坏事，对 semlayer 是不是，取决于它能不能在被抄之前先长出自己的分发。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第六，大厂抄不走的那一样东西，是可移植。&lt;/strong&gt; dbt 的语义活在 dbt 项目里,Snowflake 的活在它自己的仓库里,semlayer 的活在一份能搬家的 YAML 里。对平台来说,可移植是负资产——它要的恰恰是绑定。所以这既是 semlayer 唯一的护城河,也是最薄的一条。&lt;/p&gt;
&lt;h2 id=&#34;真要选我会怎么选&#34;&gt;真要选，我会怎么选&lt;/h2&gt;
&lt;p&gt;不谈谁强谁弱，只说场景。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;已经全套在 Snowflake 上：直接用 Snowflake Semantic Views。语义就在仓里，&lt;code&gt;AI_VERIFIED_QUERIES&lt;/code&gt; 那套人工背书的金标对你这种「要求口径一致」的场景特别顶用。&lt;/li&gt;
&lt;li&gt;已经在用 dbt：继续用 dbt 语义层。语义跟着转换逻辑走，链路最短，团队不用学第二套东西。&lt;/li&gt;
&lt;li&gt;要嵌进产品里做海量查询、性能是硬指标：Cube。预聚合那套东西就是为这个生的。&lt;/li&gt;
&lt;li&gt;老 Looker 用户：留着。迁移的性价比不高。&lt;/li&gt;
&lt;li&gt;仓库很脏、又实在没人手写、主要目的是给 Agent 喂上下文：可以上 semlayer，但请把它当成「生成初稿 + 人工签字」，而不是「一把梭」。它的 review 队列和 confidence 分级就是为这个设计的。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果你已经看过我写的那篇 semlayer 源码级拆解（&lt;a class=&#34;link&#34; href=&#34;https://www.yesmiracle.net/post/20261009-semantic-layer-text-to-sql-agent-deep-dive/&#34; &gt;《AI 写的 SQL 没报错，58% 的答案却是错的：语义层正在成为 Agent 查数据的命门！》&lt;/a&gt;），那篇讲的是它内部怎么工作，这篇是把它放回它所在的这张桌子旁边。两篇一起看，你会更清楚它想掀的到底是哪张桌子。类似的横向拆解我还在决策模型那篇里做过一次（&lt;a class=&#34;link&#34; href=&#34;https://www.yesmiracle.net/post/20260925-decision-model-llm-hybrid-architecture/&#34; &gt;《决策模型 + LLM 混合架构：Jev、OpenJev、AnyJev、Laya 四大 System One 方案横评与融合设计》&lt;/a&gt;），方法是一样的：把方案摆平，看它们对同一个问题的分歧在哪。&lt;/p&gt;
&lt;h2 id=&#34;写在最后&#34;&gt;写在最后&lt;/h2&gt;
&lt;p&gt;语义层的护城河，从来不在「谁来写」上。&lt;/p&gt;
&lt;p&gt;手写派把那份 ground truth 的权力留在自己平台里，自推断派想把它变成一份能搬家的文件。这两条路谁赢，不是推断算法决定的，是分发决定的——谁能把「定义正确性」这件事塞进用户已有的工作流，谁就赢。Snowflake 把语义塞进仓库，dbt 把它塞进 CI，都是这个逻辑。&lt;/p&gt;
&lt;p&gt;所以我对 semlayer 的真实看法是：它做对了方向，但它赌的不是技术，是「平台愿不愿意放弃绑定」。这个赌注，历史上赢过，也输过很多次。&lt;/p&gt;
&lt;p&gt;对手写语义层的团队来说，真正该问的问题也不是「要不要换成自推断」，而是：&lt;strong&gt;当有人跑了 &lt;code&gt;ALTER TABLE&lt;/code&gt; 之后，今天是谁、在多久之后、靠什么机制发现语义已经过期了？&lt;/strong&gt; 这个问题的答案，比选哪家工具重要得多。&lt;/p&gt;
</description>
        </item>
        
    </channel>
</rss>
