设备台账重构:从一张表到三层模型

2022 年 9 月我接手设备台账的时候,表里有 140 个字段、1432 行记录。这张表是 2017 年建的,起初 86 个字段,谁要统计一个指标就加一列,五年加了 54 列,其中 19 列的名字后面带着数字,比如"电机功率2""备注3"。

用起来是什么体验:点开一台空压机,屏幕要往下滚三屏,一行里一半是空的。想查这台机器换过几次油分离器,没有地方记,只能去翻维修工单的备注。想看同类设备的故障率对比,也没有地方归类,因为"类别"这一列有三种写法,空压机、空气压缩机、螺杆机各占一部分。

结论先摆:台账烂掉不是因为字段少,是因为一张表同时想干三件事------描述设备类别、记录这台设备、记录设备身上的部件。这三件事的生命周期完全不同,混在一张表里,加一列就多一处空值。我把它拆成三层,1432 行变成 1387 台设备加 6800 多条部件记录,字段总数从 140 降到 96。

一、一张表是怎么烂掉的

1.1 字段只增不减

加字段的人没有恶意,每次都是合理需求:要管润滑,加"润滑油牌号";要管能耗,加"额定功率""实测功率""功率因数";要管安全,加"检验日期""检验结论""下次检验日期"。

问题在于这些字段对某些设备根本不适用。一台手动压机没有功率因数,一台老冲床没有润滑牌号。字段加上去,不适用的行就是空值。140 个字段里,空值率超过六成的有 71 个,整张表像个筛子。

1.2 一物多账

麻烦的是同一台设备存在多条记录。梳理的时候按设备位号去重,1432 行里重复的有 45 组,涉及 96 行。同一台空压机,动力部门建了一条,设备部门建了一条,财务固定资产又建了一条,位号写法差一个横杠。

三条记录各有各的用处:动力那条记运行参数,设备那条记维修,财务那条记原值。谁都不肯删自己那条,因为删了就等于把自己的账丢了。这种情况靠开会协调没用,得让三条记录能挂到同一个身份上。

1.3 参数没有归处

还有一类问题是参数。同一种设备要记的参数是一样的,不同种设备完全不同。空压机要记排气量、压力、比功率,行车要记跨度、起重量、工作级别。这些参数在旧表里被摊成通用列,导致一种设备用三列,另一种设备用另外五列。

新设备进厂,填表的人不知道该填哪几列,索性都空着。参数空着,后面的能耗分析、故障对比都做不了。这也是后来决定把参数收进模板的原因。

二、三层怎么切

2.1 模型层:一类设备一条

模型层解决"这类设备该有哪些参数"。一类设备一条记录,存类别、名称和参数模板,模板里写清参数名、单位、数据类型、是否必填。

模型不是给每台设备单独建的。全厂 1432 台设备归成 9 类模型:空压机、冲床、行车、数控机床、注塑机、焊机、输送线、检测设备、公用动力。9 条模型记录,管住了所有参数定义。

模型层还有个隐性好处:新设备进厂,选模型就是选参数清单,填表的人不用想该填什么。填错的概率降下来,数据的可比性才成立。

2.2 资产层:只放身份和归属

资产层一条记录对应一台实物设备,只放身份字段:资产编号、位号、模型、安装位置、投产日期、状态、原值。

运行参数不放这里。参数值放在资产记录的一个 JSON 字段里,键名来自模型模板。这么做的原因是参数会变、会加,如果每个参数一列,又回到字段膨胀的老路。

资产编号是唯一键,位号不唯一。这一点当初争论过:车间认位号,财务认资产编号。后来按资产编号做主键,位号允许改,改一次留一条变更记录,因为设备移位、产线调整都会改位号。

2.3 部件层:有寿命的东西单独记

部件层记的是装在设备上的东西:主轴、电机、油分离器、滤芯、皮带。一条记录表示一个部件在一个位置上的一次安装,带安装日期、拆卸日期、设计寿命、累计运行小时。

部件层的关键是"位置"这个概念。一台空压机有三个滤芯位,位号固定,装在位上的滤芯会换。同一个位置同时只有一条未拆卸的记录,靠一个部分唯一索引保证。换滤芯不是改记录,是关掉旧记录、开一条新的。

这么设计之后,"这台机器换过几次滤芯""这个位置的滤芯平均用多少小时"这类问题才有答案。以前这些问题只能靠工单备注猜。

三、DDL 与迁移

3.1 三张表

下面是当时建表用的语句,三层各一张,唯一约束和部分索引都写进去了。

sql 复制代码
CREATE TABLE eq_model (
    model_id    serial PRIMARY KEY,
    model_code  varchar(24) NOT NULL UNIQUE,
    model_name  varchar(64) NOT NULL,
    category    varchar(24) NOT NULL,
    param_tpl   jsonb NOT NULL DEFAULT '{}'::jsonb,
    created_at  timestamptz NOT NULL DEFAULT now(),
    CONSTRAINT ck_param_tpl CHECK (jsonb_typeof(param_tpl) = 'object')
);

CREATE TABLE eq_asset (
    asset_id     bigserial PRIMARY KEY,
    asset_code   varchar(32) NOT NULL UNIQUE,
    tag_code     varchar(32),
    model_id     int NOT NULL REFERENCES eq_model(model_id),
    site_code    varchar(16) NOT NULL,
    install_date date,
    status       varchar(8) NOT NULL DEFAULT '在用',
    params       jsonb NOT NULL DEFAULT '{}'::jsonb,
    retired_at   timestamptz,
    CONSTRAINT ck_asset_status CHECK (status IN ('在用','停用','封存','报废'))
);
CREATE INDEX ix_asset_model ON eq_asset (model_id) WHERE retired_at IS NULL;

CREATE TABLE eq_component (
    comp_id      bigserial PRIMARY KEY,
    asset_id     bigint NOT NULL REFERENCES eq_asset(asset_id),
    slot_code    varchar(24) NOT NULL,
    part_code    varchar(32),
    installed_at date NOT NULL,
    removed_at   date,
    life_hours   int,
    run_hours    int NOT NULL DEFAULT 0,
    CONSTRAINT ck_comp_life CHECK (life_hours IS NULL OR life_hours > 0),
    CONSTRAINT ck_comp_span CHECK (removed_at IS NULL OR removed_at >= installed_at)
);
CREATE UNIQUE INDEX ux_comp_active
    ON eq_component (asset_id, slot_code) WHERE removed_at IS NULL;

ux_comp_active 那个部分索引是整张表的定盘星。同一个位置只能有一条在装记录,装第二个的时候数据库直接报错,前端把报错翻成"该位置已有在装部件,请先办理拆卸"。没有这条约束,两个人同时登记换件就会多出一条在装记录,寿命统计算出来是错的。

3.2 参数为什么放 JSON

参数放 JSON 不是图省事,是因为参数集合随模型变化。放成列,9 类设备要 9 套表,或者一张表几百列。放成键值行,一次查询要拼十几行,页面渲染要写聚合。

JSON 折中:一条资产一个字段,取一次就够,按模型模板校验键名。代价是不能对某个参数建普通索引,需要筛选时可以建表达式索引,比如按排气量筛空压机。我们只对三个高频参数建了表达式索引,其余不建,查询走模型加状态就够了。

3.3 迁移和去重

迁移分两步:先建 9 条模型,把 1432 行按类别映射到模型上;再按资产编号和位号做去重,重复的 45 组保留一条主记录,其余转成"历史记录"挂在主记录下,不删。

去重不能只看编号。有 12 组是编号不同但物理上是同一台,靠安装位置加投产日期加铭牌功率三个条件匹配出来的。这 12 组逐台到现场看了铭牌,确认之后才合并。

部件层是从工单里挖出来的。四年工单里带"更换""更换完毕"字样的有 1.1 万条,清洗出 6800 多条有效换件记录,按设备和部位归位。挖的过程不完美,有 900 多条对不上位置,先挂在"未定位"位置上,等下次保养时补。

四、用起来之后

4.1 报修选设备变成三级

报修页面原来是下拉框里 1432 个选项,滚半天找不到。现在改成车间、产线、设备三级联动,选到设备之后自动带出模型和位置。

这个改动看着小,效果直接:报修单里"设备选错"的比例从百分之十四降到百分之二。选错的原因以前是名字太像,同一产线三台同型号冲床,编号只差一位。

4.2 备件消耗能追到位置

部件层建起来之后,备件消耗和设备的对应关系才成立。原来只知道这个月用了 40 个滤芯,不知道装在哪。现在每个滤芯的领用单都带位置,能算出单个位置的平均更换周期。

算出来一件事挺意外:三号空压机的油分离器平均 1900 小时就换,同类设备均值是 3400 小时。查下来是散热不良,环境温度高,后来加了一段导风管,周期回到 3100 小时。这种结论在旧表里永远算不出来。

4.3 还没解决的部分

有些东西没做完。设备的技术文档、图纸、验收报告还没挂进来,仍然在共享盘按年份归档,查一台设备的资料要翻三个目录。

另外模型层现在 9 类,偏粗。数控机床里加工中心和车床的参数差别很大,共用一套模板,导致有些参数在加工中心上是空的。下一步想拆成 14 类,但要重新过一遍参数定义,牵扯到报表口径,暂时压着。

常见问题

问:三层模型是不是一定要三张表?

答:不一定,但两层是下限。模型和资产必须分开,否则参数定义又会退回成宽表。部件层可以晚点建,等换件记录积累到一定量再建,收益才明显。我们部件层是上线四个月后才补的,那四个月靠工单备注过渡。

问:参数放 JSON 之后报表怎么做?

答:按模型出报表,一个模型一套查询,把 JSON 里的键取出来当列。9 类模型就是 9 个视图,视图里固定列名,报表工具照旧用。跨模型的对比报表只用通用字段,比如状态、投产年限、原值,不硬凑参数。

问:一物多账合并时,历史工单怎么处理?

答:工单不跟着合并,工单表里保留原设备编号,另加一列指向合并后的资产。查一台设备的历史工单时,按新旧编号一起查。这样既不动历史单据,又能看到全量记录。合并记录本身也要留档,写清哪几条并入哪一条、依据是什么。

问:部件寿命数据不准怎么办?

答:先接受它不准。运行小时靠人工抄或者靠采集,两种都有误差。我们的做法是标数据来源,采集来的记 collected,人工填的记 manual,统计算均值时分开看。等采集覆盖到八成设备,再把人工那部分降权,不追求一次到位。

相关推荐
萧瑟余晖1 小时前
Spring缓存抽象详解
java·spring·缓存
贾伟康1 小时前
【共创稿事节】一个 UserDataManager 管八类数据:Preferences 持久化的克制用法
java·服务器·前端·harmonyos·arkts·鸿蒙
卓豪终端管理1 小时前
【无标题】
java·服务器·前端
智_永无止境1 小时前
又找到一个纯Java的OCR项目,识别的格式还挺多
java·开发语言·ocr
学编程就要猛2 小时前
MCP(Model Context Protocol)
java·springai
用户072367066602 小时前
Apollo 配置热更新落地:让 Spring Boot @ConfigurationProperties Bean 不重启即时生效
java
2601_970703832 小时前
中国除湿机与湿度控制行业研究报告
java·大数据·人工智能
YH_Analytics2 小时前
2026-2032 年中央鸡蛋输送机市场规模与增长预测
网络·人工智能
蒸鱼Yuzheng2 小时前
移动端 CPU 调度分析:线程亲和性、大小核迁移与卡顿
android·perfetto·性能分析·线程优化·cpu调度