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

11 KiB
Raw Permalink Blame History

多公司、多类型数据存储与展示 — 数据库与程序设计

一、需求归纳

数据类型 示例 结构特点 公司覆盖
三大报表 资产负债表、利润表、现金流量表 报告期 × 项目 → 数值,项目由 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

-- 公司
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 的键值里,无需为每种类型或每个公司加列或建新表。
  • 三大表:dimensionsitem(及可选的 category)。
  • 主营业务构成:用 segmentnameregion 等。
  • 梯媒数量:用 metricsub_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 对应一个 ParserBalanceSheetParser, 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组装出 dimensionsvalue

这样,程序与数据库都只依赖「公司 + 数据集类型 + 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。

这样即可在不增加新表、不迁库的前提下,支持三大表、主营业务构成、分众梯媒数量以及未来更多公司、更多类型的数据存储与图表展示。