接手泛微 OA 的二次开发或者报表需求时,大概率会碰到这个场景:DBA 给了一个只读账号,打开数据库一看,几百张表摆在面前,第一反应是头皮发麻。泛微 E-cology(E8/E9)这类产品,底层基于 SQL Server 或 Oracle,表设计得相当细,光以 Hrm 开头的就有几十张,工作流相关的更是上百张。但只要你不是去重构这套系统,只是想查数据、写报表、做接口集成,真正高频使用的核心表其实不超过二十张。这篇文章就把我日常用得最多的一组数据表整理出来,按“组织人员—流程引擎—业务表单—附件文档日志—自定义表”这条主线讲,附带可以直接改改就用的 SQL。适合正在做泛微报表、数据迁移、接口联调的同学参考。
1. 泛微数据库全景:几百张表,先分清三大阵营
先把结论放前面:泛微这套系统(以 E-cology E8/E9 为代表)的表虽然多,但按照命名前缀基本能分成三块。
- Hrm 开头:组织人事、权限、个人办公相关。
- workflow 开头:流程引擎、节点、实例、审批日志。
- formtable_、Doc、imagefile 等:业务表单数据、文档、附件。
另外还有一堆系统参数表、日志表,命名不统一,碰上了单独看。
底层数据库常见两种:SQL Server 和 Oracle。同一个功能,在两种库里的表名和字段名基本一致,但写法有差异(分页、日期函数、递归语法)。下面 SQL 我按 SQL Server 写,Oracle 环境把系统日期函数、递归语法换掉即可。
实际经验是:你不需要把这几百张表全搞清楚。做报表、写接口、排查审批卡住,核心链路就一条——「找到人、找到流程、找到业务数据」。人走 Hrm 表,流程走 workflow 表,业务数据在 formtable 表。把这条链走通,百分之八十的需求都能落地。
我还见过不少人一上来就钻到某张具体表里研究字段,结果越看越乱。正确顺序是先建立地图:知道哪张表是主表、哪张表是中间表、表和表靠什么字段关联。比如 HrmResource.id 这个值,几乎贯穿所有业务表,它就是整张网的中心节点。
2. 组织人事表:一切查询的起点
2.1 HrmResource:人员主表
HrmResource 是最重要的一张表,没有之一。它存的是系统里所有人员的账号和基本信息。
列个常用字段清单:
| 字段 | 含义 | 说明 |
|---|---|---|
| id | 人员唯一ID | 全系统关联的核心键 |
| loginid | 登录名 | 唯一 |
| lastname / firstname | 姓 / 名 | 人员姓名可能是拼接的 |
| workcode | 工号 | 各企业可能为空 |
| departmentid | 部门ID | 关联 HrmDepartment.id |
| subcompanyid | 分部/公司ID | 关联 HrmSubCompany.id |
| jobtitle | 岗位ID | 关联 HrmJobTitles.id |
| joblevel | 职级ID | 关联 HrmJobRank.id |
| mobile / email | 手机 / 邮箱 | 非必填 |
| accountstatus | 账号状态 | 过滤离职/禁用要用它,别只看姓名 |
| startdate / enddate | 入职/离职日期 | 离职人员的 enddate 有值 |
最容易踩的坑是:有人拿 workcode 当唯一键去关联业务表,结果工号在系统里根本不全,或者人员调整后工号重发。记住默认一律用 id。
查询在职人员列表的 SQL 很典型:
SELECT r.id, r.loginid, r.lastname + r.firstname AS personName, r.workcode, d.departmentname, s.subcompanyname, j.jobtitlename AS jobName FROM HrmResource r LEFT JOIN HrmDepartment d ON r.departmentid = d.id LEFT JOIN HrmSubCompany s ON r.subcompanyid = s.id LEFT JOIN HrmJobTitles j ON r.jobtitle = j.id WHERE r.accountstatus = '1' ORDER BY d.departmentname;注意:姓名在有的版本里 lastname 为空、firstname 就是全名,保险的写法是先SELECT TOP 100 lastname, firstname FROM HrmResource看一眼再拼。accountstatus 的具体值不同版本不一样,有的用 1 代表正常,有的用 0,查数据前先确认一下状态位的含义。
2.2 部门、分部、岗位、职级:组织架构的四张支撑表
这四张表很轻,但联查必用。
- HrmDepartment:部门表。核心字段 id、departmentname、supdepid,supdepid 指向上一级部门,用这个字段可以把树形结构递归出来。
- HrmSubCompany:分部/公司表。多法人、多分部的单位会拆成多条,HrmResource.subcompanyid 指向它。
- HrmJobTitles:岗位表,存岗位名称。
- HrmJobRank:职级表,存职级名称。
做组织架构树时,SQL Server 用递归 CTE:
WITH deptTree AS ( SELECT id, departmentname, supdepid, CAST(departmentname AS VARCHAR(500)) AS path FROM HrmDepartment WHERE supdepid = 0 OR supdepid IS NULL UNION ALL SELECT d.id, d.departmentname, d.supdepid, t.path + ' / ' + d.departmentname FROM HrmDepartment d INNER JOIN deptTree t ON d.supdepid = t.id ) SELECT * FROM deptTree;Oracle 环境换成CONNECT BY PRIOR语法。递归查询在组织层级超过三四级时最容易写错,建议先在测试库验证。
2.3 汇报关系与权限相关表
泛微里有几张表专门管“谁的上级是谁”“谁能看到什么”,做数据权限类需求时会碰到:
- HrmUserManager:汇报关系表,常见字段是 userid 和 managerid,表示 managerid 是 userid 的直接上级。
- HrmRole:角色表。
- HrmRoleMembers:角色成员表,关联 roleid 和 userid。
- HrmRoleRights:角色权限表,定义角色能访问哪些模块/资源。
我的建议是:查“某人有哪些角色”这种需求直接用 HrmRoleMembers 联 HrmRole 就够;但涉及“某个菜单能不能看到”这种权限需求,不要自己从这几张表拼逻辑。泛微的权限模型还叠加了数据权限、按部门授权、安全级别等,很容易拼错。更稳的办法是看系统里角色配置,或查询系统自带的角色视图。
小结:组织这几张表的核心玩法就是拿 HrmResource.id 去和其他表关联。先把这个字段用熟,后面流程表、表单表全都会和它见面。
3. 流程引擎核心链路:一张申请单从发起到归档
泛微最强的就是工作流。流程相关表比人员表复杂,但顺着“发起→审批→归档”这条线,核心表就四张半。
3.1 流程定义与节点:workflow_base、workflow_node
workflow_base 是流程定义主表,一条流程在这里有一条记录。常用字段:id(流程ID)、workflowname(流程名称)、formid(关联表单)。
workflow_node 是流程节点表,一个流程的所有审批节点都在这里:nodeid、workflowid、nodename。比如“部门经理审批”“总经理审批”这些节点名,就是从这里读出来的。
想搞清“某个流程一共有哪些节点”,这条 SQL 最常用:
SELECT wf.id AS workflowId, wf.workflowname, wn.nodeid, wn.nodename FROM workflow_base wf LEFT JOIN workflow_node wn ON wf.id = wn.workflowid WHERE wf.workflowname LIKE '%请假%';注意:有些版本的节点表主键叫 id,有些叫 nodeid,联查前先确认。还有一张 workflow_nodebase 之类的基础设置表,存节点的操作按钮、字段权限等,一般报表用不到,先不管。
3.2 流程实例表:workflow_requestbase
每次有人发起一条流程,workflow_requestbase 里就会多一条记录。这张表是整个工作流的数据中枢。
关键字段:
| 字段 | 含义 |
|---|---|
| requestid | 请求ID,全局唯一,贯穿所有流程相关表 |
| workflowid | 对应 workflow_base.id |
| nodeid | 当前停留节点 |
| creater | 发起人,对应 HrmResource.id |
| createdate | 发起时间 |
| requestname | 请求标题 |
| status | 状态,区分在途、归档等 |
查“某个人这段时间发起了多少条流程”:
SELECT r.creater, hr.lastname + hr.firstname AS personName, COUNT(*) AS requestCount FROM workflow_requestbase r LEFT JOIN HrmResource hr ON r.creater = hr.id WHERE r.createdate >= '2025-01-01' AND r.createdate < '2025-02-01' GROUP BY r.creater, hr.lastname, hr.firstname ORDER BY requestCount DESC;这里有个经验:查询流程数据时,一定要拿 creater 去联 HrmResource,不要相信 requestname 里的字符串姓名。人员改名、部门调整后,字符串会不准,id 不会变。
3.3 流转记录与当前待办:workflow_requestLog、workflow_currentoperator
workflow_requestLog 是流转日志表,每一条审批动作都会落一条:谁在什么时候接到单、谁在什么时候处理完、处理结果是什么。查“流程卡在哪个环节”基本靠它。
workflow_currentoperator 是当前操作人员表,存的是“此刻谁有待办”。字段一般有 requestid、nodeid、userid(有的版本叫 operator)、receivedate。
统计“当前待办积压在哪些人手里”:
SELECT userid, hr.lastname + hr.firstname AS personName, COUNT(*) AS pendingCount FROM workflow_currentoperator wo LEFT JOIN HrmResource hr ON wo.userid = hr.id GROUP BY userid, hr.lastname, hr.firstname ORDER BY pendingCount DESC;排障时最常用的动作:给出一条 requestid,先查 requestbase 看当前节点,再查 requestLog 看最近几步动作,基本就能定位是卡在审批人没处理,还是流转配置有问题。
特别提醒:线上流程卡住了,千万不要直接去 UPDATE workflow_currentoperator 或者删记录来“帮”流程走下去。泛微有催办、转办、撤销等正规手段,改表的后果轻则审批链路错乱,重则流程无法归档,而且这类问题很难恢复。真到了非改不可的境地,先在测试环境完整模拟一遍,再备份相关表。
3.4 和表单数据怎么串起来
流程实例和表单数据靠 requestid 关联。也就是说,你想看一条流程对应的业务内容,不能只查 workflow_requestbase,还要去 formtable_main_xxx 表里按 requestid 把业务字段取出来。下一节专门讲这个。
4. 表单数据存哪:formtable_main 动态表规则
很多初学者在 workflow_requestbase 里找业务字段,比如请假天数、报销金额,结果找不到——因为这些字段根本不在这张表。泛微对每个表单都动态建了一张物理表,规则是formtable_main_加一个数字后缀。
4.1 workflow_bill 是表单字典
哪张表单对应哪张物理表,查 workflow_bill 就知道了:
SELECT billid, billtablename, billname FROM workflow_bill ORDER BY billid;结果里能看到一堆formtable_main_8、formtable_main_15这样的表名。billname 就是你在界面看到的表单名称。
如果已知一张数据表名,想反查它是哪个表单、属于哪个流程,同样从 workflow_bill 入手:
SELECT * FROM workflow_bill WHERE billtablename = 'formtable_main_8';4.2 动态表里有什么
formtable_main_xxx 这张表,除了平台自动加的 id、requestid,剩下的就是表单设计器里拖出来的业务字段。字段名可能是 field0001、field0002 这种,中文含义要去字段定义表里查(常见是 workflow_billfield,里面 fieldlabel 是中文名,fieldname 是物理字段名)。
把流程实例和业务数据关联起来,标准写法是:
SELECT r.requestid, r.requestname, f.field0001 AS leaveDays, f.field0002 AS leaveReason, hr.lastname + hr.firstname AS applicant FROM workflow_requestbase r LEFT JOIN formtable_main_8 f ON r.requestid = f.requestid LEFT JOIN HrmResource hr ON r.creater = hr.id WHERE r.workflowid = 12;这里几件事特别值得注意:
第一,表单表和流程表的关联键是 requestid,但如果你用id = id去关联就错了。表单表里每条业务数据对应唯一 requestid,一条流程也可能有多个明细行(明细表是formtable_detail_前缀),要注意一对多导致的重复。
第二,涉及跨表单汇总时,不要用字段名猜业务含义。先去 workflow_billfield 把中文标签查出来,或者去表单设计器里核对,省得把“申请事由”当成“备注”。
第三,对 formtable 动态表做增删改要格外谨慎。这类表是平台自动维护的,你直接 ALTER TABLE 加字段、改类型,版本升级时平台做表结构比对可能会报错。
4.3 定位业务数据的实战套路
遇到“用户说某条报销数据不对,帮我看下库里是啥”这种需求,最快的定位顺序是:
- 在审批记录里拿到 requestid。
- 从 workflow_requestbase 查 workflowid。
- 从 workflow_base 查 formid。
- 从 workflow_bill 查 billtablename。
- 去对应 formtable_main_xxx 按 requestid 查业务数据。
这套链路我给很多同事讲过,五分钟就能定位一条数据。反过来,如果用户只给了“表单名称”和“某个字段的中文名”,就用 workflow_bill 和 workflow_billfield 反查,同样能一步到位。
5. 附件、文档与日志:做报表和排障离不开的三类表
流程和表单搞明白后,再往下就是数据背后的附件、文档和日志。这三类表在报表统计和问题排查里出场率不低。
5.1 imagefile:附件文件表
泛微的附件统一存在 imagefile 表里,字段大体有 imagefileid(附件ID)、filename(文件名)、filerealpath(物理路径)、fileext(扩展名)。流程附件、表单附件、甚至头像都可能是这张表。
统计近一个月新增附件大小,可以这样:
SELECT DATEPART(year, i.uploaddate) AS yr, DATEPART(month, i.uploaddate) AS mt, SUM(i.filesize) AS totalSize, COUNT(*) AS fileCount FROM imagefile i WHERE i.uploaddate >= '2025-01-01' GROUP BY DATEPART(year, i.uploaddate), DATEPART(month, i.uploaddate);注意:附件文件本身存在服务器磁盘上,库里只是索引信息。清理附件时如果只删库记录不删物理文件,磁盘会继续增长;反过来只删文件不删记录,前台点开就是“文件不存在”。做归档类操作时两条线要一起处理。
5.2 文档中心:DocMain 等文档相关表
泛微文档模块涉及多张表,核心的一张是 DocMain,记录文档标题、创建人、创建时间、分类等信息。文档正文可能拆在内容表里,分类在独立的分类表。坦白说文档这块各版本差异比流程表大,我每次接手新环境都会先花十分钟看后台文档模型,再决定查哪几张表。
做知识库统计时,最稳的做法是先用界面功能导出报表验证一下自己的 SQL 对不对,再拿去跑全量。文档表和流程表一样,能用 id 关联就别用字符串。
5.3 日志表与“登录时长设置”那点事
日志类表常见的有登录日志、操作日志、异常日志,表名常带 Log 后缀(具体名称版本差异很大)。它们平时做不了什么业务关联,但排查“用户登录不上”“系统凌晨崩了”“谁在什么时候删了数据”这类问题非常有用。
顺便说个很多人问的点:泛微 OA 登录时长(会话超时)怎么设置。这类配置在后台界面上就能改,一般路径是“后端应用中心 → 系统设置 → 安全/登录策略”,里面能找到会话超时时间,单位通常是分钟。配置项最终会落到某个系统参数表里,但我不建议直接改库——不同版本参数表命名不一致,而且这类参数多数有缓存,改了不清理缓存可能不生效,极端情况服务都起不来。标准操作是:界面改配置,重启对应后端服务,再用一个账号挂机验证超时是否按预期生效。
日志表会快速增长,尤其是流程多的系统。建议定期归档清理,清理前先备份,不然等磁盘满了再处理就很被动。
6. 创建自定义表与安全写入数据:二次开发必守的规矩
很多人拿到泛微环境,第一反应是“我直接建几张表,把外部数据导进去”。这个需求本身合理,但落地方式有讲究。
6.1 优先用平台数据建模功能
泛微后台有数据建模(也叫自定义表/建模引擎)之类的功能,可视化建表,自动生成维护页面、权限和字段控件的配套。建议优先用它,好处有三点:
- 表结构由平台统一管理,后续升级兼容性好。
- 自带权限体系,不用自己再写一套增删改查页面。
- 字段变更留痕,出了问题能追溯。
用建模功能建的表,物理上也是真的数据库表,你可以直接查询。但写入尽量通过建模生成的页面或接口,避免绕开平台的校验逻辑。
6.2 手写建表需要守的规矩
确实有场景必须自己写 SQL 建表,比如做集成中间表、临时报表表。这时候要守几条规矩:
- 表名前缀别用系统保留前缀(Hrm、workflow、formtable、Doc 这类),建议用你公司的缩写,比如 my_、tmp_。
- 每张表必须有自增主键 id,方便后续对接和排重。
- 时间字段统一用 datetime/timestamp,别用字符串存日期,否则后续统计全是坑。
- 列名避开数据库保留字,像 user、order、desc、size 这些能不用就不用。
一个标准的示例:
CREATE TABLE my_sync_temp ( id INT IDENTITY(1,1) PRIMARY KEY, emp_code VARCHAR(50), emp_name NVARCHAR(100), department_name NVARCHAR(200), sync_date DATETIME DEFAULT GETDATE() );6.3 插入数据的正确姿势
临时表可以放心 INSERT,但涉及主数据或者要进入流程的数据,别直接 INSERT 到 HrmResource、workflow 系列表里。我说句实在话:直接往 HrmResource 插一条“看起来没问题”的人员记录,大概率会踩密码表、部门缓存、人员索引不同步的连环坑。正确姿势是走泛微的接口,或者先从界面导入功能走一遍,验证逻辑无误后再批量。
自定义表插入数据,没什么特殊的,标准 SQL 就行:
INSERT INTO my_sync_temp (emp_code, emp_name, department_name) VALUES ('E001', '张三', '产品部'); SELECT * FROM my_sync_temp WHERE emp_code = 'E001';但有几个习惯建议养成:
- 写数据前先备份目标表,就算只是临时表,也留一条退路。
- 大批量操作拆成小批次,每批几百条,跑完查一下行数和异常数据再继续。
- 所有写操作尽量在事务里做,要么全成功要么全回滚。
如果系统里有集成需求,比如把外部 ERP 的人员同步进来,我更推荐这种模式:外部数据先写入中间自定义表,经过清洗、去重、校验后再通过泛微接口或平台工具落到正式表。这个模式的好处是你在中间表可以随便折腾,折腾错了不影响生产数据。
7. 几张高频 SQL 直接抄
最后把日常最高频的几个查询整理出来。字段名在不同版本可能有出入,跑之前先小范围验证。
7.1 某个流程的发起量趋势
SELECT CONVERT(VARCHAR(7), r.createdate, 120) AS month, COUNT(*) AS cnt FROM workflow_requestbase r WHERE r.workflowid = 12 AND r.createdate >= '2025-01-01' GROUP BY CONVERT(VARCHAR(7), r.createdate, 120) ORDER BY month;7.2 表单数据明细(含人员、部门)
SELECT r.requestid, r.requestname, f.field0001 AS applyDays, hr.lastname + hr.firstname AS applicant, d.departmentname FROM workflow_requestbase r LEFT JOIN formtable_main_8 f ON r.requestid = f.requestid LEFT JOIN HrmResource hr ON r.creater = hr.id LEFT JOIN HrmDepartment d ON hr.departmentid = d.id WHERE f.field0001 > 5;7.3 待办超过 N 天的流程
SELECT r.requestid, r.requestname, wo.userid, hr.lastname + hr.firstname AS ownerName, DATEDIFF(DAY, wo.receivedate, GETDATE()) AS pendingDays FROM workflow_currentoperator wo LEFT JOIN workflow_requestbase r ON wo.requestid = r.requestid LEFT JOIN HrmResource hr ON wo.userid = hr.id WHERE DATEDIFF(DAY, wo.receivedate, GETDATE()) > 3;7.4 某部门人数统计
SELECT d.departmentname, COUNT(r.id) AS empCount FROM HrmDepartment d LEFT JOIN HrmResource r ON d.id = r.departmentid WHERE r.accountstatus = '1' GROUP BY d.departmentname ORDER BY empCount DESC;用这些 SQL 之前,有两条建议:一是申请只读账号,不要在开发环境之外的地方动写操作;二是涉及大表(workflow_requestLog、imagefile)的查询务必加时间范围和分页,这俩表一旦涨到千万级,全表扫一遍能把生产环境拖慢。
我在项目上见过太多“报表 SQL 写得没问题但一跑就卡死”的情况,大多不是因为 SQL 不对,而是没加过滤条件。泛微这种系统的核心表数据量增长非常快,特别是流程日志和附件表,养成“先限定范围再查询”的习惯,比会写多复杂的关联都重要。
我个人的体会是:泛微的表结构虽然庞大,但核心链路就那么几条。把 HrmResource 当成中心,沿 workflow_requestbase → formtable_main_xxx 这条线走下去,再配合 imagefile、日志表做补充,大部分日常需求都能覆盖。真遇到拿不准的表,先在测试环境查一下结构和数据分布,比直接在生产库上试错稳得多。