Files
organize_excel_data/docs/数据库与架构设计-多类型数据.md
2026-03-05 22:02:57 +08:00

195 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 多公司、多类型数据存储与展示 — 数据库与程序设计
## 一、需求归纳
| 数据类型 | 示例 | 结构特点 | 公司覆盖 |
| -------- | -------------------------------- | ---------------------------------------- | ---------- |
| 三大报表 | 资产负债表、利润表、现金流量表 | 报告期 × 项目 → 数值,项目由 config 决定 | 所有公司 |
| 业务构成 | 主营业务构成(产品/地区/业务线) | 报告期 × 维度(产品、地区等) → 数值/占比 | 部分公司 |
| 业务指标 | 分众梯媒数量、门店数、产能等 | 报告期 × 指标名(或细分维度) → 数值 | 某公司特有 |
共性:**不同公司、不同类型的数据,维度与指标各异,且类型会持续增加**。
目标:**一套程序 + 一套库表** 支持所有类型,便于落库与图表展示。
---
## 二、设计原则
1. **类型可扩展**:新增一种数据(如「门店数量」)不新增表、尽量不迁库。
2. **公司可扩展**:新公司只需配置「用哪些数据类型 + 解析/展示配置」。
3. **存储统一**:所有「公司 + 数据类型 + 时间 + 维度 → 数值」走同一套存储模型。
4. **查询与图表友好**:能按公司、类型、时间范围取出结构化数据,方便 ECharts 等前端作图。
---
## 三、数据库选型结论
- **仍推荐 PostgreSQL**。
- 原因:需要按公司/类型/时间筛选、聚合、多公司对比;维度用 **JSONB** 表达兼顾灵活性与查询GIN 索引、jsonb 运算符);同一套表即可覆盖三大表 + 主营业务构成 + 梯媒数量等所有当前与未来类型。
---
## 四、核心表结构设计
### 4.1 概念模型(统一抽象)
所有「公司 × 数据类型 × 时间」下的数据,统一视为:
- **数据集类型**dataset type如 balance_sheet、income_statement、main_business_composition、elevator_media_count …
- **维度**dimensions因类型而异用键值对表示例如
- 三大表:`{ "category": "类现金", "item": "货币资金" }` 或仅 `{ "item": "资产总计" }`
- 主营业务构成:`{ "segment": "产品", "name": "空调", "region": "国内" }`
- 梯媒数量:`{ "metric": "梯媒数量", "sub_type": "电梯电视" }` 或仅 `{ "metric": "梯媒数量" }`
- **数值**value统一为数值型金额亿、数量、占比等
即:**一条记录 = 某公司、某数据集类型、某报告期、若干维度、一个数值**。
### 4.2 表结构PostgreSQL
```sql
-- 公司
CREATE TABLE companies (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(200) NOT NULL,
code VARCHAR(50) UNIQUE, -- 可选:股票/内部编码
created_at TIMESTAMPTZ DEFAULT now()
);
-- 数据集类型(注册所有「可用的数据类型」)
CREATE TABLE dataset_types (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
code VARCHAR(80) UNIQUE NOT NULL, -- balance_sheet, main_business_composition, elevator_media_count
name VARCHAR(200) NOT NULL, -- 资产负债表、主营业务构成、梯媒数量
description TEXT,
-- 用于解析与展示的默认结构(可选)
default_dimension_keys JSONB, -- ["category","item"] 或 ["segment","region","metric"]
created_at TIMESTAMPTZ DEFAULT now()
);
-- 某公司对某数据集类型的「解析/展示」配置(对应现在的 config/公司名/*.json
CREATE TABLE company_dataset_configs (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
company_id UUID NOT NULL REFERENCES companies(id) ON DELETE CASCADE,
dataset_type_id UUID NOT NULL REFERENCES dataset_types(id),
config JSONB NOT NULL, -- 与现有 JSON 一致categories / rows_to_delete / columns_to_keep / 自定义映射
UNIQUE(company_id, dataset_type_id),
created_at TIMESTAMPTZ DEFAULT now(),
updated_at TIMESTAMPTZ DEFAULT now()
);
-- 统一数据表:所有类型的数据都落在这里
CREATE TABLE company_data (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
company_id UUID NOT NULL REFERENCES companies(id) ON DELETE CASCADE,
dataset_type_id UUID NOT NULL REFERENCES dataset_types(id),
report_period DATE NOT NULL, -- 报告期(季末/年末等)
dimensions JSONB NOT NULL, -- 维度键值对,如 {"category":"类现金","item":"货币资金"} 或 {"metric":"梯媒数量"}
value NUMERIC(20,4), -- 数值(亿、数量、比率等)
created_at TIMESTAMPTZ DEFAULT now(),
UNIQUE(company_id, dataset_type_id, report_period, dimensions)
);
-- 便于按公司+类型+时间查询
CREATE INDEX idx_company_data_lookup
ON company_data(company_id, dataset_type_id, report_period);
-- 便于按 dimensions 内键查询(如按 item / metric 筛选)
CREATE INDEX idx_company_data_dimensions ON company_data USING GIN (dimensions);
```
- **不同公司、不同数据类型的「字段差异」**:全部体现在 `dimensions` 的键值里,无需为每种类型或每个公司加列或建新表。
- 三大表:`dimensions``item`(及可选的 `category`)。
- 主营业务构成:用 `segment``name``region` 等。
- 梯媒数量:用 `metric``sub_type` 等。
- 以后新增类型:只需在 `dataset_types` 增加一行,并在解析逻辑里往 `dimensions` 填对应键即可。
### 4.3 示例数据
**资产负债表美的集团2024-12-31**
| company_id | dataset_type_id | report_period | dimensions | value |
| ---------- | --------------- | ------------- | --------------------------------------- | ------- |
| 美的-uuid | balance_sheet | 2024-12-31 | {"category":"类现金","item":"货币资金"} | 140.41 |
| 美的-uuid | balance_sheet | 2024-12-31 | {"item":"资产总计"} | 6043.52 |
**主营业务构成某公司2024-12-31**
| company_id | dataset_type_id | report_period | dimensions | value |
| ----------- | ------------------------- | ------------- | ------------------------------------------------ | ------ |
| 某公司-uuid | main_business_composition | 2024-12-31 | {"segment":"产品","name":"空调","region":"国内"} | 1200.5 |
**梯媒数量(分众传媒)**
| company_id | dataset_type_id | report_period | dimensions | value |
| ---------- | -------------------- | ------------- | ------------------------------------------- | ----- |
| 分众-uuid | elevator_media_count | 2024-12-31 | {"metric":"梯媒数量","sub_type":"电梯电视"} | 80.2 |
---
## 五、程序架构设计NestJS
### 5.1 分层与职责
- **数据集类型**:只做「类型注册」和「默认维度/展示提示」,不承载具体解析逻辑。
- **公司 + 配置**:每个公司对每种类型有一份 config对应现有 JSON决定如何从 Excel 解析出 (report_period, dimensions, value)。
- **解析与写入**:按「类型 + 公司 config」驱动输出统一格式 (company_id, dataset_type_id, report_period, dimensions, value),写入 `company_data`
- **查询与展示**:按 company / dataset_type / 时间范围查 `company_data`,转成前端/ECharts 需要的结构(如按 item 的时序、按 segment 的饼图等)。
### 5.2 模块划分建议
```
src/
├── companies/ # 公司 CRUD
├── dataset-types/ # 数据集类型注册、列表
├── company-configs/ # 公司 × 类型的 config CRUD对应 config/公司名/*.json
├── import/ # 上传 Excel + 解析 + 落库
│ ├── parsers/ # 按类型分balance-sheet.parser, income-statement.parser, cash-flow.parser,
│ │ # main-business-composition.parser, elevator-media.parser, ...
│ ├── import.service # 选公司、类型、文件 → 调对应 parser → 写 company_data
│ └── import.controller
├── data/ # 查询接口:按公司/类型/时间范围返回数据,供图表用
└── chart-presets/ # 可选:按 dataset_type 的图表预设(折线、饼图、柱状等)
```
### 5.3 解析层设计(应对「类型持续增加」)
- **策略模式**:每种 `dataset_types.code` 对应一个 Parser`BalanceSheetParser`, `MainBusinessParser`, `ElevatorMediaParser`)。
- Parser 输入:**文件流 + 该公司该类型的 configJSONB**。
- Parser 输出:**数组 of { report_period, dimensions, value }**,由 `ImportService` 统一写入 `company_data`
- 新增类型时:在 `dataset_types` 插入一条;新增一个 Parser 并注册到工厂/映射表即可,无需改表结构。
### 5.4 配置与维度的约定(程序侧)
- 三大表:沿用现有 config 结构categories / rows_to_delete / columns_to_keep解析后
- `dimensions = { "item": "货币资金" }``{ "category": "类现金", "item": "货币资金" }`
- 主营业务构成、梯媒数量等:在 `company_dataset_configs.config` 里约定字段,例如
- 主营业务:`{ "sheet_name": "主营业务构成", "row_header": "产品", "col_period": true, "value_key": "收入" }`
- 梯媒:`{ "sheet_name": "业务数据", "metrics": ["梯媒数量","电梯电视","电梯海报"], "period_col": "报告期" }`
解析器按 config 读 Excel组装出 `dimensions``value`
这样,**程序与数据库都只依赖「公司 + 数据集类型 + config」**,不依赖具体有哪些列名,能适应不同公司、不同指标。
---
## 六、查询与图表展示
- **按公司 + 类型 + 时间范围**
`SELECT report_period, dimensions, value FROM company_data WHERE company_id = ? AND dataset_type_id = ? AND report_period BETWEEN ? AND ? ORDER BY report_period, dimensions;`
- 前端或后端将结果按 `dimensions` 转成 ECharts 所需格式,例如:
- 折线图dimensions.item 或 dimensions.metric 为系列report_period 为 X 轴;
- 饼图:某报告期的 dimensions.segment / dimensions.name 为扇区value 为数值;
- 可为每种 `dataset_type` 配置「图表预设」(折线/饼/柱状、默认维度),减少前端重复逻辑。
---
## 七、小结
| 维度 | 选择与设计 |
| -------- | -------------------------------------------------------------------------------------------------------- |
| 数据库 | PostgreSQL用「公司 + 数据集类型 + 报告期 + dimensions(JSONB) + value」统一存所有类型。 |
| 表设计 | companies / dataset_types / company_dataset_configs / company_data不按公司或按数据类型拆表。 |
| 程序 | NestJS按 dataset_type 的 Parser 策略 + 统一 Import 写 company_dataconfig 按公司×类型存 JSONB。 |
| 扩展方式 | 新数据类型:新增 dataset_types 一行 + 新 Parser新公司新增 companies + 若干 company_dataset_configs。 |
这样即可在**不增加新表、不迁库**的前提下,支持三大表、主营业务构成、分众梯媒数量以及未来更多公司、更多类型的数据存储与图表展示。