)
PostHog 数据建模常见维度表目录与建模实战自然键、取数方式与粒度规范【免费下载链接】posthog:hedgehog: PostHog is the leading platform for building self-driving products. Our developer tools – AI observability, analytics, session replay, flags, experiments, error tracking, logs, and more – capture all the context agents need to diagnose problems, uncover opportunities, and ship fixes. Steer it all from Slack, web, desktop, or the MCP.项目地址: https://gitcode.com/GitHub_Trending/po/posthog本篇基于 PostHog 仓库中 data_modeling 产品的modeling-dimension-tables技能参考文档系统讲解星型模型中常见维度表国家/地区、时区、货币、日期、套餐/层级、产品的完整目录每个维度的自然键是什么、数据从哪来、建模时有哪些注意点。读完本篇你可以在 PostHogHogQL 视图 保存连接或 dbt 项目上独立为事件、收入、留存等事实表构建可复用的 conformed 维度表并掌握“一行一实体”的粒度纪律与去重手法。一、维度目录总览自然键、来源与建模要点原目录文档dimension-catalog.md开宗明义每个维度都要明确“怎么取数source it、它的自然键是什么、以及注意事项”并且每个维度都必须建模为“每键一行”one row per key。完整目录如下表维度自然键取数方式备注Country国家country_codeISO-2从事件的properties.$geoip_country_code派生或上传一份 ISO 国家列表用上传的查找表补充 region/continent地区/大洲Region / continent地区/大洲country_code→region上传 country→region 的 CSVdbt seed / warehouse source与国家维度 conformed一致对齐通过country_code关联Timezone时区timezoneIANA 字符串如Europe/Berlin取自properties.$timezone或 country→timezone 查找表上传一个国家可对应多个时区所以键要用 IANA 字符串而不是国家Currency货币currencyISO-4217内建——直接用convertCurrency()PostHog 上无需建表只有在 dbt 侧没有等价能力或需要非默认汇率提供方时才自建dim_currency_rateDate日期date生成日历 spinedate, week, month, quarter, DOW, is_weekend经典的 conformed 维度成本极低一次物化即可Plan / tier套餐/层级plan_id或plan_name上传 plan→tier→price 查找表或从 Stripe products 同步与收入事实表关联后可得到 tier 级别的收入Product产品product_id使用受管的revenue_analyticsproduct 视图或从数据源同步若受管视图存在优先使用它参见仓库中的modeling-revenue-metrics技能这张表是整篇文档的骨架它把“维度选型”从模糊的架构讨论收敛成三个可执行的问题——键是什么、数据从哪来、有什么坑。下节逐维度展开。二、逐维度详解2.1 Country从事件属性派生再用查找表补全国家维度的自然键是ISO-2 的country_code。最便宜的取数方式是直接从事件属性派生PostHog 的事件里带有properties.$geoip_country_code字段由地理 IP 识别自动填充先SELECT DISTINCT出“真实出现过的国家”再用一份上传的 country→region/continent 查找表做富化enrich。仓库中给出了完整的 HogQL 配方 dim_country.sql可以逐段对照理解目录文档中“Derive enrich”这条路径-- 从事件派生国家维度并用上传的 country-region 查找表富化 -- 每行一个 ISO-2 country_code自然键。所有列必须显式别名建视图的硬性要求 -- 将 country_region_lookup 替换为你上传的查找表列country_code, region, continent SELECT seen.country_code AS country_code, any(lk.region) AS region, any(lk.continent) AS continent, count() AS events_seen -- 可选出现频次用于 QA FROM ( SELECT upper(properties.$geoip_country_code) AS country_code FROM events WHERE properties.$geoip_country_code ! AND timestamp now() - INTERVAL 90 DAY ) AS seen LEFT JOIN country_region_lookup AS lk ON seen.country_code lk.country_code GROUP BY country_code -- 以慢调度7day/30day物化国家属性几乎不变但会被持续高频读取几个值得注意的实现细节upper(...)统一大小写$geoip_country_code可能混有大写/小写写法不归一化就会让de与DE变成两个键直接破坏“每键一行”。时间窗口now() - INTERVAL 90 DAY派生型维度只覆盖“近 90 天事件里真实出现过的国家”这是“derive from events”模式天生的边界——它最便宜但只覆盖事件中出现过的值目录文档的 Sourcing patterns 一节明确指出这一点。LEFT JOIN而非INNER JOIN保证即使某个国家在查找表里缺失国家维度本身不丢行只是region/continent为空。any(lk.region)GROUP BY country_code在派生子查询里已经去重的前提下any()是“取任意一行”的轻量写法events_seen计数则保留下来做 QA 参考哪些国家量大。2.2 Region / continent与国家维度 conformed靠country_code关联目录文档对这一条的表述是“Conforms with the country dimension; join oncountry_code”——也就是说地区/大洲不是一个独立数据源而是挂在国家维度上的查找表上传一份country_code, region, continent三列的 CSVPostHog 上作为 warehouse sourcedbt 里作为 seed然后LEFT JOIN进国家维度。上面 2.1 的配方里lk.region、lk.continent正是这样来的。dbt 侧的等价配方见 dbt/dim_country.sql-- 在 dbt 中构建国家维度country-region seed 关联到阶段化事件中观察到的国家 -- 需要 seeds/country_region.csv列为country_code, region, continent {{ config(materializedtable) }} with seen as ( select distinct upper(current_country) as country_code from {{ ref(stg_events) }} where current_country is not null ), lookup as ( select country_code, region, continent from {{ ref(country_region) }} ) select s.country_code, l.region, l.continent from seen s left join lookup l using (country_code)结构上与 PostHog 版本一一对应seen事件派生的国家集合lookupseed 查找表left join。差异仅在语法——dbt 用ref()引用 seed且{{ config(materializedtable) }}直接物化而 PostHog 侧是先建视图再按慢调度物化。2.3 Timezone键必须是 IANA 字符串不能是国家目录文档对时区维度有一条非常关键的建模告诫“One country can have many timezones; key on the IANA string, not the country.”一个国家可以横跨多个时区例如巴西横跨 UTC-3 到 UTC-5如果错误地以country_code作自然键时区维度就会出现“一多国一”的键冲突关联事实表时引发 fan-out一对多扩散。自然键timezoneIANA 时区名如Europe/Berlin取数方式优先取事件的properties.$timezone客户端/采集端上报或上传一份 country→timezone 查找表做补充粒度每个 IANA 字符串一行。2.4 CurrencyPostHog 内建不要自建汇率表这是目录文档中唯一以加粗 “Built in”标注的维度也是 PostHog 与 dbt 两套栈差异最大的一点PostHog 侧直接使用内建函数convertCurrency(from, to, amount, timestamp?)不需要任何维度表。该函数背后是受管的汇率维度数据来自 Open Exchange Rates按日粒度存储并依据传入的timestamp应用历史时点汇率省略timestamp则用最新汇率。典型用法是把每笔收入折算成 USD-- 在收入模型里把每笔金额按历史汇率折算为 USD SELECT convertCurrency(currency, USD, amount, timestamp) AS amount_usd FROM ...dbt 侧没有等价能力。此时才需要自建dim_currency_rate按(currency, date)为键的汇率表可用 seed CSV 或同步源提供并在 mart 模型中 join。目录文档的原始表述是“Only build adim_currency_ratein dbt (no equivalent there) or for a non-default provider.”——即只有两种情况自建在 dbt 里或者你需要 PostHog 未提供的汇率提供方。其余场景一律用convertCurrency()。2.5 Date最经典的 conformed 维度一次物化终身复用日期维度的自然键就是date本身。目录文档给出的建模要点“Generate a calendar spine (date, week, month, quarter, DOW, is_weekend). Classic conformed dimension; cheap; materialize once.” 即生成一份日历 spine带上年/周/月/季/星期几/是否周末等常用属性它便宜、变化为零一次物化即可。dbt 侧的完整配方见 dbt/dim_date.sql-- Conformed 日期维度每行一天带常用日历属性 -- 使用 dbt_utils.date_spine请按需调整日期范围与仓库的日期函数 {{ config(materializedtable) }} with spine as ( {{ dbt_utils.date_spine( datepartday, start_datecast(2020-01-01 as date), end_datecast(2031-01-01 as date) ) }} ) select date_day::date as date, extract(year from date_day) as year, extract(month from date_day) as month, extract(day from date_day) as day_of_month, date_trunc(week, date_day)::date as week_start, date_trunc(month, date_day)::date as month_start, extract(quarter from date_day) as quarter, extract(dow from date_day) as day_of_week, extract(dow from date_day) in (0, 6) as is_weekend from spine注意start_date/end_date需要按业务实际范围调整date_trunc(week)/date_trunc(month)的起点定义因仓库ClickHouse、Postgres、BigQuery 等而异配方中也提示“adjust the range and your warehouses date functions as needed”。2.6 Plan / tier上传查找表或同步 Stripe最新值胜出套餐维度的自然键是plan_id或plan_name取数有两条路上传plan→tier→price 的手维护查找表CSV 作为 warehouse source同步system of record如 Stripe products 或你应用库里的plans表。建模后与收入事实表关联revenue_item.product_id或订阅的plan_id dim_plan.plan_id即可得到 tier 级别的收入。仓库配方 dim_plan.sql 展示了关键的去重手法-- 从上传/同步的 plan 查找表构建套餐维度去重为每 plan_id 一行 -- 该模式适用于任何手维护或 system-of-record 查找表 -- 别名为干净列名并收敛到一行一 key。 -- 将 plan_source 替换为你上传的 CSV 表或同步源表。 SELECT plan_id AS plan_id, argMax(plan_name, updated_at) AS plan_name, -- 存在历史时取最新值 argMax(tier, updated_at) AS tier, argMax(monthly_price, updated_at) AS monthly_price FROM plan_source GROUP BY plan_id -- 通过 saved join 挂载revenue_item.product_id / subscription plan_id dim_plan.plan_id -- 于是 tier 与 price 可以直接作为收入事实上的原生字段读取argMax(value, updated_at)是 ClickHouse 系 HogQL 的“取某键最新一条记录的值”的标准写法——当源表因历史变更存在多个plan_id行时argMax按updated_at挑选最新值从而保证维度仍然满足“一行一 key”。这正是下一节“Keys and grain”规则的具体落地。2.7 Product优先使用受管视图产品维度的自然键是product_id。目录文档建议优先使用受管的revenue_analyticsproduct 视图如果它存在否则从数据源同步。该建议指向仓库中同系列的收入建模技能modeling-revenue-metrics其核心思想是受管视图已经替你做好了去重、别名与治理重复造一份只会产生“竞争对手副本”。三、三种取数模式Sourcing patterns目录文档将维度的数据来源归纳为三种模式这是选型时的第一决策点Upload / seed上传/播种——适合小型、手维护的查找表country→region、plan→tier。PostHog 上把 CSV 上传为 warehouse sourcedbt 里放seeds/*.csv并dbt seed。上文的 region 查找表与 plan 查找表都属于这类。Warehouse source仓库源——适合由某个 system of record 拥有的维度Stripe products、应用库里的plans表。通过数据仓库源同步PostHog 侧对应setting-up-a-data-warehouse-source流程再把原始表整理成带别名的干净视图。Derive from events从事件派生——适合数据里天然隐含的维度实际出现过的国家、事件里观察到的 plan 属性。做法是SELECT DISTINCTargMax按键取最新值。这是最便宜的模式但边界也很明确只覆盖事件里出现过的值——某个从未被事件命中的国家/套餐不会出现在派生维度里。选型建议从目录文档的备注列可以推断有权威源的用 warehouse source纯静态映射用 upload探索期或无权威源时用 derive等数据稳定后再把 derive 的结果沉淀为正式查找表。四、键与粒度去重先于入库目录文档的 “Keys and grain” 一节给出了全篇最重要的纪律The natural key must be truly unique in the dimension — dedupe before saving. If a raw source has multiple rows per key (history, per-source duplicates), collapse to one row (argMaxby updated-at) or model an explicit slowly-changing dimension. Fact tables carry the key; the dimension carries everything else.翻译成可执行规则自然键必须在维度内真正唯一——去重发生在“保存维度”之前而不是 join 之后祈祷不出错。维度键一旦重复每一次与事实表的关联都会静默 fan-out行数与金额被成倍放大且报表看起来完全正常。源表出现“一 key 多行”时只有两条路收敛成一行按更新时间取最新即argMax(value, updated_at)dim_plan.sql 的标准手法或者显式建模缓慢变化维度SCD保留历史行但引入版本键。职责划分事实表只携带键country_code、plan_id、product_id……描述性属性全部放维度里。这条“事实带键、维度带其余一切”的分工是星型模型不产生歧义 join 的前提。dbt 侧把这条纪律固化为测试。参见 dbt/schema.ymlversion: 2 # 维度的键必须 unique 且非空——否则它会让每一次 join 都 fan out models: - name: dim_date description: Calendar dimension, one row per day. columns: - name: date data_tests: [unique, not_null] - name: dim_country description: Country dimension with region/continent, one row per ISO-2 code. columns: - name: country_code data_tests: [unique, not_null] - name: region seeds: - name: country_region description: Static country_code - region/continent lookup. columns: - name: country_code data_tests: [unique, not_null]要点每个维度的自然键都挂uniquenot_null双重测试把“键唯一”从口头纪律变成 CI 里可失败的断言查找表本身seeds段的country_region的键同样要测——seed 里有重复键污染会一路传导进dim_country。五、建完之后挂载、物化与命名规范目录文档讲的是“维度是什么、数据从哪来”而把维度真正变成“任何查询、过滤、拆分里都可直接使用的原生字段”还需要配套机制。同技能的 SKILL.md 与 foundations 文档joins-and-dimensions.md给出了完整闭环PostHog 侧的挂载方式三种 join按复用度选择Saved table join持久SQL 编辑器里为源表定义一次source_table.key joined_table.key此后任意查询中维度表的列都可作为源表的嵌套字段直接引用。这是把维度“挂”到事实流上的内建方式无需重复写 JOIN 语法。Person join持久特例把维度表接到persons上让维度列在 insights、过滤器、拆分、cohort 中像原生 person 属性一样工作——适合“全产品范围”都要用的客户/账户维度。Ad-hoc HogQL join一次性某个视图内部专用的普通JOIN/LEFT JOIN不持久化。原则是被多个模型复用的维度用 saved join 或 person join局部逻辑才用 ad-hoc join。与目录文档呼应的三条配套纪律来自同技能 SKILL.md 的“Rules before you model”别名到干净、稳定的列名country_code、region、plan_tier——这些名字就是所有下游依赖的“join 面”PostHog 建视图时要求每个SELECT列显式别名dim_country.sql 里每列都有AS就是这个约束的体现静态维度按慢调度物化——目录文档对 Country 的备注与 dim_country.sql 末尾注释都写明国家属性几乎不变、但被持续高频读取应以7day/30day的sync_frequency物化不要让一个高频读取的查找表一直停留在虚视图状态注册并认证certify维度——在目录中为维度加注释负载重的维度做认证让其他模型能发现它、而不必再造一个竞争副本。六、小结一张目录表背后的建模方法论回看 dimension-catalog.md 全文它的价值不在于罗列了哪七个维度而在于给出了维度建模的可复用检查表先定键自然键必须唯一ISO-2 国家码、IANA 时区串、ISO-4217 货币码、日期、plan_id、product_id键错则一切错——时区维度以国家为键是最典型的反面教材再定源upload / warehouse source / derive from events 三选一并清楚每种模式的覆盖边界derive 只覆盖事件出现过的值后守粒度argMax收敛历史行uniquenot_null测试固化纪律事实带键、维度带属性货币是特例PostHog 上用convertCurrency()dbt 里才建dim_currency_rate最后挂载saved join / person join 让维度列成为原生字段慢调度物化保证性能。这套流程在仓库中是成体系的modeling-dimension-tables/SKILL.md 定义了完整方法论本文的目录文档解决“建什么、键是什么”references/posthog/ 与 references/dbt/ 提供两套栈的可复制配方而modeling-revenue-metrics、modeling-conversion-metrics、modeling-activation-metrics、modeling-product-usage-metrics等下游技能则消费这些维度——维度建一次处处复用这正是星型模型在事件分析平台上的落地方式。【免费下载链接】posthog:hedgehog: PostHog is the leading platform for building self-driving products. Our developer tools – AI observability, analytics, session replay, flags, experiments, error tracking, logs, and more – capture all the context agents need to diagnose problems, uncover opportunities, and ship fixes. Steer it all from Slack, web, desktop, or the MCP.项目地址: https://gitcode.com/GitHub_Trending/po/posthog创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考