## 一、数据库概览 | 数据库 | 类型 | 连接地址 | 端口 | 角色 | |--------|------|----------|------|------| | `vala_bi` | PostgreSQL | bj-postgres-16pob4sg.sql.tencentcdb.com | 28591 | BI 数据(订单、用户、学习行为) | | `vala` / `vala_user` / `vala_order` | MySQL | bj-cdb-dh2fkqa0.sql.tencentcdb.com | 27751 | 配置数据 + 用户行为源数据 | > 🔔 **所有数据库操作均为只读,禁止写入。** --- ## 二、PostgreSQL `vala_bi` 库 ### 📦 bi_vala_order(订单表) > 🔔 **金额计算统一使用 `pay_amount_int`(分),显示为元时除以 100。不要用 `pay_amount`(varchar 字符串)。** | 字段 | 类型 | 释义 | 计算说明 | |------|------|------|----------| | `id` | bigint | 订单主键 | `COUNT(DISTINCT id)` = 订单数 | | `account_id` | bigint | 关联用户账号 | JOIN `bi_vala_app_account.id` | | `trade_no` | varchar | 交易流水号 | 关联退费表 `bi_refund_order.trade_no` | | `out_trade_no` | varchar | 商户订单号 | 关联退费表 `bi_refund_order.out_trade_no` | | `pay_amount_int` | integer | 支付金额(分) | **GMV/GSV 统计字段**,元 = 值/100 | | `pay_amount` | varchar | 支付金额(元) | ⚠️ 字符串格式,不推荐使用 | | `order_status` | integer | 订单状态 | 1=未支付 2=已支付 3=已完成 **4=已退款** 5=已取消 | | `pay_success_date` | timestamptz | 支付成功时间 | 用于时间维度统计(新增付费用户日期基准) | | `key_from` | text | 销售渠道标识 | 渠道映射见[渠道映射表](#九渠道映射表) | | `sale_channel` | integer | 端内购课渠道编号 | 仅 `key_from=app-active-h5-0-0` 时有效 | | `goods_name` | text | 商品名称 | 购买的课程/商品名称 | | `pay_channel` | integer | 支付渠道 | 微信/支付宝等 | | `is_used` | integer | 是否已使用 | 订单是否已被消费 | | `quantity` | integer | 购买数量 | 默认 1 | | `expire_days` | integer | 有效期天数 | 课程有效期 | | `created_at` | timestamptz | 创建时间 | | | `updated_at` | timestamptz | 更新时间 | | | `deleted_at` | timestamptz | 删除时间 | NULL=未删除 | **关键 JOIN 条件**: ```sql -- 测试账号剔除(所有订单统计必须) LEFT JOIN bi_vala_app_account a ON o.account_id = a.id AND a.status = 1 -- 退费关联(退费成功的订单) LEFT JOIN bi_refund_order r ON o.trade_no = r.trade_no AND o.out_trade_no = r.out_trade_no AND r.status = 3 -- 退费成功 AND r.deleted_at IS NULL ``` --- ### 📦 bi_vala_app_account(用户账号表) > 🔔 **所有涉及订单/用户的统计,必须 `WHERE status = 1` 剔除测试账号。`download_channel` 为汉字格式,用 `LIKE '%关键词%'` 匹配。** | 字段 | 类型 | 释义 | 计算说明 | |------|------|------|----------| | `id` | bigint | 账号主键 | 被多方关联的关键字段 | | `tel` | varchar | 手机号 | 明文 | | `tel_encrypt` | varchar | 加密手机号 | | | `name` | varchar | 用户姓名 | | | `status` | smallint | 账号状态 | **1=正常用户,2=测试用户**。统计必须 `=1` | | `key_from` | varchar | 注册来源 | 渠道标识 | | `download_channel` | text | 下载渠道(汉字) | 新增用户分渠道统计字段,匹配用 `LIKE` | | `pay_status` | integer | 付费状态 | | | `login_times` | integer | 登录次数 | | | `created_at` | timestamptz | 注册时间 | 新增注册用户统计的时间基准 | | `updated_at` | timestamptz | 更新时间 | | | `deleted_at` | timestamptz | 删除时间 | NULL=未删除 | | `id_card` | varchar | 身份证 | ⚠️ 敏感信息 | | `remark` | text | 备注 | | --- ### 📦 bi_vala_app_character(角色表) > 🔔 **关联路径:`account_id` → `bi_vala_app_account.id`,`id`(=角色ID)→ 学习记录表的 `user_id`。** | 字段 | 类型 | 释义 | 计算说明 | |------|------|------|----------| | `id` | bigint | 角色主键 | 关联学习记录表的 `user_id` | | `account_id` | bigint | 关联账号 | JOIN `bi_vala_app_account.id` | | `nickname` | varchar | 角色昵称 | | | `gender` | smallint | 性别 | 0=girl 1=boy | | `birthday` | varchar | 出生日期 | YYYY-MM-DD,`split_part(birthday,'-',1)` 取年份 | | `pay_status` | smallint | 付费状态 | 0=未付费,1=已付费 | | `status` | smallint | 角色状态 | | | `latest_login` | timestamptz | 最后登录 | | | `created_at` | timestamptz | 创建时间 | | | `updated_at` | timestamptz | 更新时间 | | | `deleted_at` | timestamptz | 删除时间 | NULL=未删除 | | `avatar` | text | 头像 URL | | | `total_sp_point` | integer | 总 SP 积分 | | | `purchase_season_package` | text | 赛季包购买状态 | JSON 数组 | --- ### 📦 bi_refund_order(退费订单表) > 🔔 **退费校验必须同时满足双条件:`status = 3` AND 关联订单 `order_status = 4`。缺一不可。** | 字段 | 类型 | 释义 | 计算说明 | |------|------|------|----------| | `id` | bigint | 退费主键 | | | `account_id` | bigint | 退费用户 | | | `trade_no` | varchar | 交易流水号 | 关联 `bi_vala_order.trade_no` | | `out_trade_no` | varchar | 商户订单号 | 关联 `bi_vala_order.out_trade_no` | | `refund_amount_int` | integer | 退费金额(分) | 退费金额计算字段,元 = 值/100 | | `refund_amount` | varchar | 退费金额(元) | ⚠️ 字符串格式 | | `status` | integer | 退费状态 | **3 = 退费成功** | | `reason` | text | 退费原因 | | | `sale_channel` | integer | 销售渠道 | | | `is_admin` | boolean | 是否管理员操作 | | | `refund_type` | integer | 退费类型 | | | `refund_method` | integer | 退费方式 | | | `created_at` | timestamptz | 创建时间 | | | `updated_at` | timestamptz | 更新时间 | | | `deleted_at` | timestamptz | 删除时间 | NULL=未删除 | --- ### 📦 bi_user_course_detail(用户课程明细表) > 🔔 **`expire_time` 不为空 = 正式课,为空 = 体验课。`deleted_at` 有值 = 已删除课程。** | 字段 | 类型 | 释义 | 计算说明 | |------|------|------|----------| | `id` | integer | 主键 | | | `account_id` | integer | 用户账号 ID | JOIN `bi_vala_app_account.id` | | `user_id` | integer | 角色 ID | JOIN `bi_vala_app_character.id` | | `course_level` | text | 课程等级 | L1 / L2 | | `active_time` | timestamptz | 激活时间 | | | `expire_time` | timestamptz | 过期时间 | **不为空=正式课,为空=体验课** | | `latest_unit_index` | integer | 最新单元索引 | | | `latest_lesson_index` | integer | 最新课时索引 | | | `learn_duration` | integer | 学习时长 | | | `last_learn_time` | timestamptz | 最后学习时间 | | | `created_at` | timestamptz | 创建时间 | | | `updated_at` | timestamptz | 更新时间 | | | `deleted_at` | timestamptz | 删除时间 | 有值=已删除,统计时过滤 | --- ### 📦 bi_level_unit_lesson(课程结构映射表) > 🔔 **`id` = `chapter_id`,直接关联播放记录表。课程匹配统一使用此表。示例:L1 S0 U00 L01 → id=343。** | 字段 | 类型 | 释义 | 计算说明 | |------|------|------|----------| | `id` | integer | chapter_id | 关联播放记录表的 `chapter_id` | | `course_level` | varchar | 课程等级 | L1 / L2(对应 A1/A2) | | `course_season` | varchar | 季度 | S0 / S1 / S2 ... | | `course_unit` | varchar | 单元 | U00 / U01 / U02 ... | | `course_lesson` | varchar | 课时 | L01 / L02 / L03 / L04 / L05 | **课程匹配 SQL 模板**: ```sql -- 查某课程的学习数据,先通过此表拿到 chapter_id SELECT id FROM bi_level_unit_lesson WHERE course_level = 'L1' AND course_season = 'S0' AND course_unit = 'U00' AND course_lesson = 'L01'; -- 返回 id = 343,然后用 343 去查播放记录 ``` --- ### 📦 bi_user_chapter_play_record_0 ~ bi_user_chapter_play_record_7(课时播放记录,8 张分表) > 🔔 **统计时必须用 `UNION ALL` 合并 8 张表。首次完成时间按 `user_id + chapter_id` 分组取 `MIN(created_at)`。`chapter_unique_id` 关联组件播放记录。** | 字段 | 类型 | 释义 | 计算说明 | |------|------|------|----------| | `id` | bigint | 主键 | | | `user_id` | bigint | 角色 ID | JOIN `bi_vala_app_character.id` | | `chapter_id` | bigint | 课时 ID | JOIN `bi_level_unit_lesson.id` | | `chapter_unique_id` | varchar | 完课唯一标识 | 关联 `bi_user_component_play_record.chapter_unique_id` | | `play_status` | smallint | 播放状态 | **1=正常完成**,0=未开始,2=中途退出 | | `created_at` | timestamptz | 创建时间 | 首次完成时间 = `MIN(created_at)` 分组 | | `updated_at` | timestamptz | 更新时间 | | | `deleted_at` | timestamptz | 删除时间 | NULL=未删除 | | `story_id` | bigint | 故事 ID | | | `level` | varchar | 等级 | | | `map_position` | text | 地图位置 | | | `dialog_info` | text | 对话信息 JSON | | **分表合并模板**: ```sql SELECT user_id, chapter_id, chapter_unique_id, play_status, MIN(created_at) AS first_finish_time FROM ( SELECT * FROM bi_user_chapter_play_record_0 WHERE deleted_at IS NULL UNION ALL SELECT * FROM bi_user_chapter_play_record_1 WHERE deleted_at IS NULL UNION ALL -- ... 2~7 同理 SELECT * FROM bi_user_chapter_play_record_7 WHERE deleted_at IS NULL ) AS all_records WHERE play_status = 1 -- 正常完成 GROUP BY user_id, chapter_id, chapter_unique_id, play_status ``` --- ### 📦 bi_user_component_play_record_0 ~ bi_user_component_play_record_7(组件播放记录,8 张分表) > 🔔 **`interval_time` 求和得课时总耗时(毫秒),÷60000 = 分钟。退出率计算:有效参与 = `play_status IN (1,2)`,退出 = `play_status=2`。`component_unique_code` 用于组件维度分组。** | 字段 | 类型 | 释义 | 计算说明 | |------|------|------|----------| | `id` | bigint | 主键 | | | `user_id` | bigint | 角色 ID | | | `chapter_id` | bigint | 课时 ID | | | `chapter_unique_id` | varchar | 完课唯一标识 | 关联课时播放记录 | | `component_unique_code` | varchar | 组件唯一编码 | 如 `core_reading_order-0101004`,组件维度统计的分组键 | | `component_id` | bigint | 组件 ID | | | `component_type` | smallint | 组件类型 | | | `comp_type` | varchar | 组件类型名 | | | `play_status` | smallint | 播放状态 | **1=完成,2=退出**(退出率:`IN (1,2)` 为有效参与) | | `play_result` | varchar | 练习结果 | **Perfect / Good / Oops** | | `interval_time` | integer | 播放耗时(毫秒) | 课时总耗时 = `SUM(interval_time) / 60000` 分钟 | | `pass_time` | integer | 通过时间 | | | `read_word_count` | integer | 阅读词数 | | | `speak_count` | integer | 口语次数 | | | `listen_sentence_count` | integer | 听力句数 | | | `write_word_count` | integer | 写作词数 | | | `created_at` | timestamptz | 创建时间 | | | `updated_at` | timestamptz | 更新时间 | | | `deleted_at` | timestamptz | 删除时间 | 统计需过滤 | | `session_id` | varchar | 会话 ID | | | `listen_time` | integer | 听力时间 | | | `skill_points` | integer | 技能点 | | | `finish_question_count` | integer | 完成题目数 | | **Oops率/退出率计算模板**: ```sql -- 组件 Oops率 SELECT component_unique_code, COUNT(CASE WHEN play_result = 'Oops' THEN 1 END) * 100.0 / COUNT(*) AS oops_rate FROM ( SELECT * FROM bi_user_component_play_record_0 WHERE deleted_at IS NULL UNION ALL ... -- 1~7 同理 ) AS all_comp WHERE play_status = 1 GROUP BY component_unique_code; -- 组件退出率 SELECT component_unique_code, COUNT(CASE WHEN play_status = 2 THEN 1 END) * 100.0 / COUNT(*) AS exit_rate FROM ( SELECT * FROM bi_user_component_play_record_0 WHERE deleted_at IS NULL UNION ALL ... -- 1~7 同理 ) AS all_comp WHERE play_status IN (1, 2) -- 有效参与 = 完成 + 退出 GROUP BY component_unique_code; ``` --- ### 📦 bi_user_unit_review_question_result(课程巩固 Review 源表) > 🔔 **巩固用时 = `play_time / 1000` 秒。巩固正确率 = `正确数 / 总题数 × 10000`(万分比)。`question_list` 为 JSON 数组,需解析 `isRight` 和 `isSkip`。** | 字段 | 类型 | 释义 | 计算说明 | |------|------|------|----------| | `id` | bigint | 主键 | | | `user_id` | bigint | 角色 ID | | | `story_id` | bigint | 故事 ID | | | `score` | integer | 得分 | | | `score_text` | varchar | 评分等级 | Perfect / Good / Oops | | `question_list` | text | 题目列表 JSON | 解析 `isRight`、`isSkip` 计算正确率 | | `play_time` | bigint | 播放时间(毫秒) | **巩固用时 = play_time / 1000(秒)** | | `created_at` | timestamptz | 创建时间 | | | `updated_at` | timestamptz | 更新时间 | | | `deleted_at` | timestamptz | 删除时间 | | | `sp_value` | integer | SP 值 | | | `exp` | integer | 经验值 | | | `level` | varchar | 等级 | | **正确率计算模板**: ```sql -- 巩固正确率(万分比) -- 先解析 question_list JSON 数组,统计 isRight=true 的数量 -- 正确率 = SUM(isRight=true) / COUNT(*) * 10000 ``` --- ### 📦 bi_user_unit_summary_km_result(单元强化 Summary 源表) > 🔔 **强化进入时间 = `created_at`。** | 字段 | 类型 | 释义 | 计算说明 | |------|------|------|----------| | `id` | bigint | 主键 | | | `user_id` | bigint | 角色 ID | | | `story_id` | bigint | 故事 ID | | | `km_id` | varchar | 知识模块 ID | | | `km_type` | varchar | 知识模块类型 | | | `score_text` | varchar | 评分等级 | | | `play_time` | bigint | 播放时间 | | | `created_at` | timestamptz | 首次进入时间 | **强化进入时间的基准** | | `updated_at` | timestamptz | 更新时间 | | | `deleted_at` | timestamptz | 删除时间 | | | `sum_explain` | text | 总结说明 | | | `sp_value` | integer | SP 值 | | | `level` | varchar | 等级 | |