台球厅数仓 DWD 层数据库说明书 本说明书详细列出了台球厅经营系统的 DWD 层表结构。 每张表都包含字段名称、数据类型、来源、含义、是否属于主键/外键、业务重要性、未知作用标记,以及枚举值解释。说明书依据《*-Analysis.md》中提供的字段说明整理完成,未出现省略号,确保字段信息完整可追溯。 因业务需求,将一个表拆成主数据表和扩展数据表(Ex为后缀),如:维度表的门店数据表分为主表dim_site 和扩展表dim_site_Ex,主键相同,作为唯一关联标识。在业务代码处理的读和写时,使用统一处理方式,将数据视为一个表格。注意,极少数表,没有扩展表。 注意:考虑到后期分布式部署,以及测试的便利性。所有的“外键”的处理,使用业务处理,不在数据库中强制约束。 维度表(DIM) dim_site 门店维度表,提取自各 ODS 中的 siteProfile 对象,如table_fee_transactions。记录门店的基本信息和配置,是其他事实表的外键。 dim_site_Ex dim_table 台桌维度表,来自 site_tables_master。每行代表一张球台或包厢,包含区域和业务角色信息。 dim_table_Ex dim_assistant 助教档案维表,对应 assistant_accounts_master。每行代表一位助教账号及其人事/账号状态。 dim_assistant_Ex dim_member 会员档案维表,对应 member_profiles。每行记录租户内某会员的主档信息,包括等级、状态、注册信息等。 dim_member_Ex dim_member_card_account 已开通的会员卡账户视图,来自 member_stored_value_cards。每行代表一张会员卡账户的快照,记录卡种、持卡人、余额、有效期及各种折扣/扣款配置。 重要说明:本视图不仅包含储值卡,还囊括活动抵用券、台费卡、酒水卡、月卡等多种卡种。 大多数折扣/扣款字段在当前数据中保持默认值(如 10.0 表示不打折、100.0 表示全额抵扣、0 表示不启用),业务上暂未使用,但为系统预留能力。 dim_member_card_account_Ex dim_tenant_goods 租户级商品档案,来自 tenant_goods_master。每行代表一款商品标准定义。 dim_tenant_goods_Ex dim_store_goods 门店级商品档案,来自 store_goods_master.json。每行代表门店自定义的商品 SKU,包括售价和折扣。关联到 dim_tenant_goods 和分类维度。 dim_store_goods_Ex dim_goods_category 商品分类索引树,来自 stock_goods_category_tree.json。每行是一个分类节点。 categoryBoxes 是“某个分类节点下面的子分类列表”,整个文件里只有两层:根节点 + 子节点两级,不存在孙节点。 每个 categoryBoxes 里的元素结构与根节点完全一致(同样的 11 个字段),只是 pid 指向父节点的 id,categoryBoxes 为空。 同一个分类树在 JSON 里分页返回了两次,goodsCategoryList 和每个 categoryBoxes 在两个 page 中完全重复,真正的不同分类节点一共只有 26 个。 从数仓角度,树结构的“真实关系”完全由 id 和 pid 就可以表达,categoryBoxes 更像是前端为了直接画树而准备的冗余展开结果,在 DWD 里不需要原样存这一坨结构,只需要被“打散”成一行一个节点。 下面我把完整的 categoryBoxes 结构按业务和数据的视角展开给你看。 一、整体结构:categoryBoxes 是子分类数组,深度只有两层 stock_goods_category_tree.json 顶层是一个分页数组 pages,每个元素形如: { "code": 0, "data": { "total": 9, "goodsCategoryList": [ 根分类1, 根分类2, ... 共9个 ] } } 每个“根分类”对象都有这些字段: id tenant_id category_name alias_name pid business_name tenant_goods_business_id open_salesman categoryBoxes sort is_warehousing 其中: pid = 0 表示根分类。 categoryBoxes 是一个数组,里面放的是子分类节点对象。 子分类对象和根分类字段完全一样,只是: pid = 父节点 id categoryBoxes = [] 两个 page 的 goodsCategoryList 完全相同,所以你看到的 18 个“根”其实是相同的 9 个重复了两次,categoryBoxes 里的子节点也重复了两次。按照去重后的真实结构: 根节点 9 个。 子节点 17 个。 总共 26 个不同的 id。 所以“完整的 categoryBoxes”其实就是:9 个根节点各自带着若干子节点。 二、逐个根节点把 categoryBoxes 展开给你看 下面按“根分类 → categoryBoxes 子分类”的树形方式列一遍,便于你直观看到完整结构。 1. 根:槟榔 根节点: id = 2790683528350533 category_name = 槟榔 business_name = 槟榔 pid = 0 categoryBoxes 只有一个子分类: 子分类 1: id = 2790683528350534 category_name = 槟榔 business_name = 槟榔 pid = 2790683528350533 categoryBoxes = [] 其它字段:tenant_id、tenant_goods_business_id、is_warehousing、open_salesman、sort 与父节点一致。 可以理解为:业务线“槟榔”,下面只有一个细分类“槟榔”。 2. 根:器材 根节点: id = 2790683528350535 category_name = 器材 business_name = 器材 pid = 0 categoryBoxes 子分类 3 个: 子分类 1: id = 2790683528350536 category_name = 皮头 pid = 2790683528350535 子分类 2: id = 2790683528350537 category_name = 球杆 pid = 2790683528350535 子分类 3: id = 2790683528350538 category_name = 其他 pid = 2790683528350535 这条业务线代表所有“器材相关商品”,细分为皮头、球杆、器材其他。 3. 根:酒水 根节点: id = 2790683528350539 category_name = 酒水 business_name = 酒水 pid = 0 categoryBoxes 子分类 6 个: 子分类 1:饮料 id = 2790683528350540 category_name = 饮料 pid = 2790683528350539 子分类 2:酒水 id = 2790683528350541 category_name = 酒水 pid = 2790683528350539 子分类 3:茶水 id = 2790683528350542 category_name = 茶水 pid = 2790683528350539 子分类 4:咖啡 id = 2790683528350543 category_name = 咖啡 pid = 2790683528350539 子分类 5:加料 id = 2790683528350544 category_name = 加料 pid = 2790683528350539 子分类 6:洋酒 id = 2793221553489733 category_name = 洋酒 pid = 2790683528350539 这里是最典型的一棵分类树:业务线“酒水”,细分成饮料、普通酒水、茶水、咖啡、加料、洋酒。 4. 根:果盘(业务线:水果) 根节点: id = 2790683528350545 category_name = 果盘 business_name = 水果 pid = 0 categoryBoxes 子分类 1 个: 子分类: id = 2792050275864453 category_name = 果盘 business_name = 水果 pid = 2790683528350545 这里有个有意思的点: 分类名称用的是“果盘”,而业务大类 business_name 是“水果”,说明业务线从“水果”角度管理,这个店真正卖的具体品类是“果盘”。 5. 根:零食 根节点: id = 2791941988405125 category_name = 零食 business_name = 零食 pid = 0 categoryBoxes 子分类 2 个: 子分类 1: id = 2791948300259205 category_name = 零食 pid = 2791941988405125 子分类 2: id = 2793236829620037 category_name = 面 pid = 2791941988405125 这说明“面”类商品也被算在零食这条业务线里(这完全是你们的门店本地习惯)。 6. 根:雪糕 根节点: id = 2791942087561093 category_name = 雪糕 business_name = 雪糕 pid = 0 categoryBoxes 子分类 1 个: 子分类: id = 2792035069284229 category_name = 雪糕 pid = 2791942087561093 7. 根:香烟 根节点: id = 2792062778003333 category_name = 香烟 business_name = 香烟 pid = 0 categoryBoxes 子分类 1 个: 子分类: id = 2792063209623429 category_name = 香烟 pid = 2792062778003333 8. 根:其他 根节点: id = 2793217944864581 category_name = 其他 business_name = 其他 pid = 0 categoryBoxes 子分类 1 个: 子分类: id = 2793218343257925 category_name = 其他2 pid = 2793217944864581 可以理解为“杂项类商品”的一级和二级拆分。 9. 根:小吃 根节点: id = 2793220945250117 category_name = 小吃 business_name = 小吃 pid = 0 categoryBoxes 子分类 1 个: 子分类: id = 2793221283104581 category_name = 小吃 pid = 2793220945250117 三、categoryBoxes 元素的字段与含义 无论是在 goodsCategoryList 还是 categoryBoxes 里,每个分类节点的字段集合完全一致: id:分类节点主键,唯一。 tenant_id:租户 ID,本文件所有节点相同。 category_name:分类名,见上面的各种名称。 alias_name:分类别名,当前全部为空字符串。 pid:父级分类 ID,根节点为 0,子节点为父节点 id。 business_name:业务大类名,用于业务线聚合。 tenant_goods_business_id:业务大类 ID,对应 business_name,根节点与子节点在同一业务线中取值相同。 open_salesman:营业员开关,当前所有值为 2,表示没启用分类级差异。 categoryBoxes:子分类数组,只有根节点非空,子节点都是空数组。 sort:排序,小部分为 1,大部分为 0,目前排序未精细化使用。 is_warehousing:是否走库存,本文件全部为 1。 这一点很重要:categoryBoxes 里的元素不是“别的结构”,就是一套完整分类节点,只是挂在父节点下面而已。 四、从 DWD 建模角度怎么看 categoryBoxes 结合上面的完整展开,可以得出几个明确结论和建议: 真实树关系靠的是 id 和 pid 子节点的 pid 永远等于父节点的 id。 即使没有 categoryBoxes,你也完全可以自下而上拼出整棵树。 categoryBoxes 是前端友好的结构,不是建模必要字段。 当前树只有两层 根节点的 categoryBoxes 非空,子节点的 categoryBoxes 全为空。 对应 DWD 可以直接用一个 category_level 字段区分 1、2 层,再配一个 is_leaf 字段。 JSON 有分页重复 同一套分类树出现在两个 page 中,goodsCategoryList 与 categoryBoxes 内容重复。 ETL 时必须按 id 去重,否则维表会重复插入相同分类。 在 DWD 的 dim_goods_category 里,categoryBoxes 本身不需要落列 只保留每个节点一行:category_id、category_name、parent_category_id、category_level、is_leaf、tenant_goods_business_id、business_name 等即可。 如果你特别想保留源结构,可以另开一个 raw_json 字段存原始节点 JSON,日后排错用,但不建议在分析建模中依赖它。 dim_goods_category_Ex dim_groupbuy_package 团购套餐定义,来自 group_buy_packages。每行代表一种团购套餐及其使用规则。 dim_groupbuy_package_Ex 事实表(DWD) 以下事实表均以“业务事件”为粒度,不做聚合。字段来源包括原始 JSON(或ODS) 中的明细数组以及对象属性。时间单位均统一为秒,并保留原始字段以备检查。金额按照源系统保持符号规则,不做符号转换。 dwd_settlement_head(结账记录) 来自 settlement_records的内层 settleList 对象,每行代表一次结账。该表在业务上是其他明细事实表的汇总头,用于串联台费、商品、助教、券等明细。 dwd_settlement_head_Ex(结账记录扩展) dwd_table_fee_log(台费流水) 来自 table_fee_transactions的 siteTableUseDetailsList,忽略siteProfile(已在dim_site实现)。粒度为一次台费使用记录(包括包厢)。该表连结订单结账头、桌台、会员等维度。 dwd_table_fee_log_Ex(台费流水扩展) dwd_table_fee_adjust(台费折扣/调整) 来自 table_fee_discount_records的table_fee_discount_records.data.taiFeeAdjustInfos. 路径下字段路径。每行代表一次台费打折或减免操作。由于结构相对简单,字段说明如下: dwd_table_fee_adjust_Ex(台费折扣/调整扩展) dwd_store_goods_sale(商品销售明细) 来自 store_goods_sales_records的 orderGoodsLedgers。每行代表订单中的一条商品销售明细。字段较多,以下列出关键字段及其作用。 dwd_store_goods_sale_Ex(商品销售明细扩展) dwd_assistant_service_log(助教服务流水) 来自 assistant_service_records的 assistant_service_records.data.orderAssistantDetails.。每行表示一次助教提供服务的记录,包括服务时长、金额、助教与会员关联等。 dwd_assistant_service_log_Ex(助教服务流水扩展) dwd_assistant_trash_event(助教废除事件) 来自 assistant_cancellation_records 的 abolitionAssistants。每行代表一次助教服务被废除的事件,无法直接与结算记录或助教流水关联,只能通过门店+台桌+助教+时间窗口软关联。 dwd_assistant_trash_event_Ex(助教废除事件扩展) dwd_member_balance_change(会员余额变动) 来自 member_balance_changes.json,粒度为一次储值卡账户余额变动。此表是分析会员资金往来的核心事实表。 dwd_member_balance_change_EX(会员余额变动扩展) dwd_groupbuy_redemption(团购券核销) 来自 group_buy_redemption_records.json 中各条记录。每行代表一次团购券使用/核销事件。 dwd_groupbuy_redemption_Ex(团购券核销扩展) 来自 group_buy_redemption_records.json 中各条记录。每行代表一次团购券使用/核销事件。 dwd_platform_coupon_redemption(第三方平台券核销) 来自 platform_coupon_redemption_records.json。每条记录代表一次第三方团购券的核销,用于追踪渠道引流和兑换。 dwd_platform_coupon_redemption_Ex(第三方平台券核销扩展) dwd_recharge_order(充值结算) 来自 recharge_settlements.json的settleList.settleList.。每行是一条充值订单,记录充值金额、赠送金额及是否首充。 dwd_recharge_order_Ex(充值结算扩展) dwd_payment(支付流水) 来自 payment_transactions.json。每行代表一笔支付或收款流水,与结算单、充值单等关联。只有 pay_status=2 的支付成功记录被导出。 dwd_refund(退款流水) 来自 refund_transactions.json。每行代表一笔退款,对应原支付流水。退款金额以负数存储在 pay_amount 字段;字段 refund_amount 全部为 0,实际退款金额需取 pay_amount 的绝对值。 dwd_refund(退款流水) 来自 refund_transactions.json。每行代表一笔退款,对应原支付流水。退款金额以负数存储在 pay_amount 字段;字段 refund_amount 全部为 0,实际退款金额需取 pay_amount 的绝对值。 总结 本说明书列出了经营数据仓库 DWD 层的主要维度表和事实表的字段结构及说明,尽可能在每个字段上标注其来源、含义及业务重要性。对于未在 MD 文档中解释的字段标记为 作用未知,在建模时建议保留字段但谨慎使用;对业务逻辑影响较小的展示类字段标记为 不重要。枚举字段均列出了观测到的取值和推断的含义,便于后续 ETL 做值域映射和数据清洗。随着业务扩展和数据补充,可继续完善枚举信息、用途说明和字段分类。