2026/9/19 5:07:31

从Excel台账到数据资产:用SQLite和Python搭建中小企业HR管理系统

从Excel台账到数据资产:用SQLite和Python搭建中小企业HR管理系统 简介《论中小企业的人力资源管理》是一份面向工商管理专业学生、中小企业管理者及人力资源从业者的专业文档。资源从中小企业人才引进难、用人观念滞后、管理制度留人不易等现实问题切入系统梳理资金有限、资源不足、领导者魅力欠缺、传统用人观念束缚等深层成因并给出树立企业远景、注重企业文化、建立正确人才观、实施激励措施、加强培训与沟通等十条完整对策。文件为doc格式整包共1个文件约60KB内容紧凑包含中英文摘要与关键词便于直接阅读、引用或作为论文写作参考。目前已有66人学习浏览对撰写人力资源方向论文、改进中小企业留人机制具有较强的参考价值。1. 把「人事文档」变成「数据资产」中小企业HR管理的另一种解题思路一份标题叫《论文论中小企业的人力资源管理.doc》的文档放在任何一家公司的共享盘里都不违和但真正的问题是文档写完了制度挂在墙上表格散落在各个业务负责人的电脑里考勤靠微信接龙薪酬核算用Excel硬扛月底对不上数是常态。中小企业的HR管理难点从来不在「不知道怎么做」而在「没有一套能稳定喂数据的系统」。这篇内容我想和你聊的是把人力资源管理的落地路径从「写文档」切换到「建数据模型」用最轻量的数据库建模、ETL式的数据规整、可审计的计算脚本把入职、考勤、薪酬、绩效这些高频动作沉淀成结构化数据。适合正在被Excel台账折磨的HRBP、行政负责人也适合想低成本帮客户搭HR中台的外包技术团队。这里没有Slack时代的酷炫架构只有能跑起来的东西。2. 为什么中小企业的HR管理先死在「表结构」而不是死在「制度」2.1 纸质与Excel台账的本质问题没有主键就没有真相大多数中小企业的人力资源数据起点是一张《员工信息登记表》格式大致是「姓名、部门、岗位、入职日期、身份证号、联系方式」。听起来没什么问题但一落到Excel里就立刻出现三件事第一同一个员工可能在「离职台账」里手滑被删掉第二员工转岗之后原部门负责人还在用旧名单给人排班第三Excel允许你在姓名列里填空格于是一个人的名字在考勤表里是「张三」在薪酬表里是「 张三」VLOOKUP精准地给你返回#N/A。这不是管理意识问题是数据结构问题——你没有一个不可变的主键来锚定「这个人是谁」。常见做法是给每个员工编一个工号如E10001。但工号只是把人区分开真正有价值的是把员工视为一条「主数据记录」它在整个生命周期里经历INSERT、UPDATE但绝不允许DELETE。我一般会建议从第一天起就维护一张员工主档表工号作为逻辑主键身份证号作为自然键做唯一约束其余所有业务表都用工号做外键关联。这听起来像数据库理论课但实际上就是用Excel的「数据验证」「条件格式」也能达到八成效果只是如果你打算在下一个季度用脚本批量算绩效直接上SQLite或MySQL更值得。2.2 从「动作」倒推「字段」而不是从「表格」倒推「流程」人力资源管理的本质是一系列「动作」的记录招聘是候选人状态的迁移考勤是每日打卡事实的累积薪酬是规则对事实的计算结果绩效是评估人对目标的打分。任何一家的HR管理软件核心都是把「动作」存成行把「规则」写成可配置参数。中小企业的误区在于领导说「我们要上个考勤机」于是先买设备再导数据最后发现导出的是加密格式还得写个解析器。正确的顺序是反过来的先定义你需要回答什么问题再反推你要记录什么字段最后才决定用什么工具采集。比如「这个月每个部门的加班时长是多少」这个问题它背后需要的不是考勤机导出的漂亮报表而是「员工工号、日期、上班时间、下班时间、加班审批单号」这五个字段。把问题写下来你自然知道该建什么表。为了让你直观感受下面是一份我非常推荐的最小字段集参考业务动作核心表名必填字段说明员工入职employeeemp_id, id_card, name, dept, hire_dateid_card设唯一索引防重复每日考勤attendanceemp_id, work_date, check_in, check_out允许为空代表缺卡加班申请overtimeemp_id, ot_date, start_time, end_time, approve_id审批人也要存工号薪酬核算salaryemp_id, period, base, bonus, deductionperiod建议统一为YYYY-MM绩效评分performanceemp_id, period, kpi_score, okr_score, comment打分记录能溯源到评估人这套表结构的好处是每一张表都能独立审计且互相之间用工号串联。需要特别提醒的是不要在一张表里既存考勤又存薪酬那是单体架构时代的坏味道在HR数据里它会让你根本分不清「这个月谁没来」和「这个月该扣谁的钱」。2.3 为什么常用密码学思维来保障数据隐私且不为流程设置人为障碍员工数据是敏感数据薪资数据更是。很多中小企业会把薪酬表放在共享盘里全公司可见这是非常大的隐患。技术侧至少要做两件事薪酬表只保留工号不做姓名联查导出数据时对身份证号、手机号做脱敏处理。更稳妥的做法是把敏感字段单独拆一张表用工号做外键只有特定角色的账号能访问。这是权限设计更是数据伦理。在流程设计上我见过最糟糕的情况是企业为了「规范」要求员工每次考勤异常都要填纸质申请单再让主管签字。结果就是月底HR收到一摞皱巴巴的便签纸录入Excel时还要猜字。数字化工具的意义不是把线下流程搬到线上而是让审批这件事的「状态」可追踪提交→待审→通过→归档每一步都有时间戳和操作人。想做到这一点根本不用花钱买系统免费的飞书多维表格或者钉钉的审批应用都可以实现核心是把「结果导向」的数据模型和「状态机」的流程模型结合起来。3. 本地先跑通用SQLite搭建人力资源管理数据模型的落地步骤3.1 为什么是SQLite而不是MySQL或PostgreSQL中小企业不见得养得起专职DBA也不见得有运维精力去管一台MySQL的备份和账号权限。SQLite的优势在于零配置文件、单文件存储、标准SQL支持完善拿来存放HR数据不并发写入是绰绰有余的。哪怕你所在的公司最终要上云数据库SQLite也可以作为Schema设计的验证环境。这个取舍在「用最少成本跑通人力资源管理系统」这个目标下是最优选。在你自己的Windows/Mac/Linux电脑上只需要确定装了Python 3它自带sqlite3模块环境就齐了。下面这段脚本会创建我们前一节设计的五张核心表。import sqlite3 conn sqlite3.connect(hr_system.db) cursor conn.cursor() cursor.executescript( CREATE TABLE IF NOT EXISTS employee ( emp_id TEXT PRIMARY KEY, -- 工号逻辑主键 id_card TEXT NOT NULL UNIQUE, -- 身份证号唯一约束 name TEXT NOT NULL, dept TEXT NOT NULL, hire_date TEXT NOT NULL -- ISO格式 YYYY-MM-DD ); CREATE TABLE IF NOT EXISTS attendance ( emp_id TEXT NOT NULL, work_date TEXT NOT NULL, check_in TEXT, check_out TEXT, FOREIGN KEY (emp_id) REFERENCES employee(emp_id), PRIMARY KEY (emp_id, work_date) -- 一个员工一天只能有一行 ); CREATE TABLE IF NOT EXISTS overtime ( emp_id TEXT NOT NULL, ot_date TEXT NOT NULL, start_time TEXT NOT NULL, end_time TEXT NOT NULL, approve_id TEXT NOT NULL, FOREIGN KEY (emp_id) REFERENCES employee(emp_id), PRIMARY KEY (emp_id, ot_date) ); CREATE TABLE IF NOT EXISTS salary ( emp_id TEXT NOT NULL, period TEXT NOT NULL, -- 格式 YYYY-MM base REAL NOT NULL, bonus REAL DEFAULT 0, deduction REAL DEFAULT 0, FOREIGN KEY (emp_id) REFERENCES employee(emp_id), PRIMARY KEY (emp_id, period) ); CREATE TABLE IF NOT EXISTS performance ( emp_id TEXT NOT NULL, period TEXT NOT NULL, kpi_score REAL NOT NULL, okr_score REAL NOT NULL, comment TEXT, FOREIGN KEY (emp_id) REFERENCES employee(emp_id), PRIMARY KEY (emp_id, period) ); ) conn.commit() conn.close() print(HR schema created successfully.)这段脚本里有几个值得注意的参数与设计决策所有日期hire_date、work_date、period统一存成YYYY-MM-DD或YYYY-MM文本格式。不要存成2024/1/5或2024.1.5不同格式会导致排序和比较结果完全错乱。考勤表的主键是(emp_id, work_date)这从物理层面杜绝了一个员工同一天出现在两行里——这是Excel里最防不胜防的脏数据。身份证号设置UNIQUE约束可以在数据录入阶段就拦截重复建档。哪怕有一个员工入职两次离职再入职也应该用新工号但保留原身份证号便于未来做回溯分析。FOREIGN KEY在SQLite里默认不启用需要额外执行PRAGMA foreign_keys ON;。如果你希望数据库严格拒绝「考勤表里出现员工表里不存在的工号」必须开这个开关。3.2 数据导入别手工录入写个CSV ingestion脚本数据表建好之后真正的体力活是导入历史数据。常见做法是让相关部门按模板导出CSV然后脚本统一灌库。下面这段代码演示了如何把employee.csv稳妥地导入SQLite并处理可能的重复数据。import csv import sqlite3 conn sqlite3.connect(hr_system.db) conn.execute(PRAGMA foreign_keys ON;) cursor conn.cursor() with open(employee.csv, r, encodingutf-8-sig) as f: reader csv.DictReader(f) for row in reader: try: cursor.execute( INSERT INTO employee (emp_id, id_card, name, dept, hire_date) VALUES (?, ?, ?, ?, ?) , (row[emp_id], row[id_card], row[name], row[dept], row[hire_date])) except sqlite3.IntegrityError as e: print(fskip {row[emp_id]}: {e}) conn.commit() conn.close()代码里注意utf-8-sig编码它能自动剥掉Excel导出的UTF-8 BOM头这是CSV导入最常见的坑之一。遇到IntegrityError比如重复工号或重复身份证时选择跳过并打印而不是让整个脚本崩溃。你可以在这个基础上加一张import_log表记录每次导入的时间和被跳过的行数这样日后审计数据来源就有据可查了。3.3 人事数据一致性自查用SQL做数据质量报告数据导完不等于数据干净。哪怕有主键和唯一约束兜底业务语义上的问题依然存在例如一个人离职了但考勤还在继续或者加班审批人把自己审批了。我会写一套体检SQL定期跑在库上。做法是创建一个视图专门暴露「异常数据」CREATE VIEW IF NOT EXISTS anomaly_data AS SELECT a.emp_id, e.name, a.work_date, a.check_in, a.check_out FROM attendance a LEFT JOIN employee e ON a.emp_id e.emp_id WHERE a.check_in IS NULL OR a.check_out IS NULL; SELECT * FROM anomaly_data;这个视图把「当天缺卡」的员工全部揪出来。可以把结果导出给HR逐条确认确认完的补卡记录应保存在另一张attendance_fix表里而不是直接修改原始表。保留原表不动在修正表里记录「原始值→修正值→修正人→修正时间」是履行数据可追溯原则的最低成本做法。很多企业就是靠这一条在劳动仲裁里拿出自己清白的依据。4. 用Python实现考勤与薪酬核算脚本避开这8个参数陷阱4.1 从考勤原始记录到「有效工时」的计算链路考勤数据的计算链路通常是读取打卡记录→补齐缺卡→判断迟到早退→计算每日有效工时→按周/月汇总。常见的坑是「跨天班次」——晚上10点上班到第二天早上6点下班如果你只按自然日算工时一定出错。下面这个脚本展示了如何处理跨天from datetime import datetime, timedelta def calc_work_hours(check_in: str, check_out: str, next_day: bool False) - float: 计算有效工作时长支持跨天班次。 check_in: 当天打卡时间 HH:MM check_out: 下班打卡时间 HH:MM next_day: 是否次日下班 fmt %H:%M t_in datetime.strptime(check_in, fmt) t_out datetime.strptime(check_out, fmt) if next_day: t_out t_out timedelta(days1) raw_hours (t_out - t_in).total_seconds() / 3600.0 # 扣除固定午餐时间 1 小时仅当日时长超过 5 小时才扣 if raw_hours 5: raw_hours - 1 return round(raw_hours, 2)关于next_day这个布尔参数它决定了计算方向。判断是否跨天最可靠的依据是排班表的班次类型而不是拿打卡时间猜测。所谓参数陷阱就是你在函数参数里塞了太多的「潜规则」导致后边接手的同事完全不敢改动。尤其是round(raw_hours, 2)这一行浮点精度在薪资场景里是敏感的谨慎做法是用decimal.Decimal替换float。4.2 薪酬核算脚本固定月薪之外加班费和扣款怎么算薪酬核算需要三类数据员工主档确定基本工资和岗位、考勤汇总确定扣款、绩效分数确定奖金系数。下面是一个极简的计薪脚本import sqlite3 conn sqlite3.connect(hr_system.db) cursor conn.cursor() period 2024-05 # 获取本月每位员工的考勤异常天数 cursor.execute( SELECT emp_id, COUNT(*) AS abnormal_days FROM attendance WHERE strftime(%Y-%m, work_date) ? AND (check_in IS NULL OR check_out IS NULL) GROUP BY emp_id , (period,)) abnormal dict(cursor.fetchall()) # 获取员工薪酬 cursor.execute(SELECT emp_id, base, bonus FROM salary WHERE period ?, (period,)) for emp_id, base, bonus in cursor.fetchall(): daily_wage round(base / 21.75, 2) # 月薪 / 月平均计薪天数 absence_days abnormal.get(emp_id, 0) deduction round(daily_wage * absence_days, 2) total base bonus - deduction print(emp_id, base, bonus, deduction, total) conn.close()这里有一个非常经典的参数点21.75是法定月计薪天数由365天-104天休息日÷12个月得来。很多初创公司直接按30天算日薪对员工和企业都不公平。如果你们公司实行的是大小周或单休这个基数还要进一步调整。另一个参数点是strftime(%Y-%m, work_date)它取决于你的work_date存的是不是ISO格式如果你存的是中文格式这里匹配就会失效。SQLite的日期函数只认YYYY-MM-DD这是你必须遵守的存储约定。4.3 薪酬核算脚本的八个参数陷阱速查我梳理了薪酬核算里最常出现的八个问题它们不是代码bug而是业务参数设计层面的坑陷阱类型原因正确做法计薪天数写死统一用21.75未考虑当月实际工作日按法定工作日动态计算或明确制度依据加班费基数含奖金把绩效奖金计入加班费基数导致口径混乱提前定义名词基本工资、绩效工资、津贴缺勤扣款重复有缺卡记录又另存一份请假记录导致双扣明确「异常考勤」和「请假审批」互斥关系四舍五入位置错误每行扣款单独round汇总后又round先算精确值最后总额统一round时区问题分公司在不同时区打卡时间未做时区归一全部换算成UTC存储展示时再转本地时区特殊月份薪资期薪资周期是上月26日到本月25日定义统一的period字段与薪资周期对应社保基数调整基数每年7月变一次去年的数据可能被覆盖给社保明细建独立的历史表离职员工的薪资离职当月要不要发全月薪用离职日期计算当月在职天数并按比例折算4.4 数据回写把计算结果安全地写回数据库计算完之后结果不要直接UPDATE原表正确做法是写入一张独立的salary_calculation表保留计算快照。这样一旦后续规则变更你可以回溯「月初算的版本」和「月末调整的版本」。回写时注意这是「无符号数」也好、「高精度数」也罢在SQLite里都用REAL会有精度问题推荐用TEXT存十进制字符串或者用整数存「分」为单位。整数存分是最稳妥的方案能彻底避开浮点误差。CREATE TABLE IF NOT EXISTS salary_calculation ( emp_id TEXT NOT NULL, period TEXT NOT NULL, base_cents INTEGER NOT NULL, bonus_cents INTEGER NOT NULL, deduction_cents INTEGER NOT NULL, final_cents INTEGER NOT NULL, calc_version TEXT NOT NULL, created_at TEXT NOT NULL DEFAULT (datetime(now, localtime)), PRIMARY KEY (emp_id, period, calc_version) );所有金额字段以分为单位存整数这样你永远不会看到0.1 0.2 0.30000000000000004这种尴尬。5. 进阶玩法用一张汇总视图把HR数据变成管理层一眼能看懂的驾驶舱数据模型的终极意义是服务决策。中小企业的管理层不看明细表只看汇总数和趋势。与其每次临时用Excel透视不如在数据库里固化一张hr_dashboard视图把所有关键指标直接算好CREATE VIEW hr_dashboard AS SELECT e.dept, COUNT(DISTINCT e.emp_id) AS headcount, SUM(CASE WHEN a.abnormal_days 0 THEN 1 ELSE 0 END) AS abnormal_count FROM employee e LEFT JOIN ( SELECT emp_id, COUNT(*) AS abnormal_days FROM attendance WHERE strftime(%Y-%m, work_date) 2024-05 GROUP BY emp_id ) a ON e.emp_id a.emp_id WHERE e.hire_date 2024-05-31 GROUP BY e.dept;这个视图的作用是把「部门人数」和「考勤异常人数」放在一行里。如果abnormal_count比例超过20%说明这个部门的排班或者考勤制度存在问题需要进一步下钻分析。视图的价值在于固化口径——如果每次都在Python里临时JOIN不同人写出来的count可能差出几个数。常看这种驾驶舱的人会逐渐培养出「用数据提问」的直觉比如投产比异常、离职率与部门得分的关系、加班工时与项目进度的相关性等。数据可视化方面如果你不想搭BI工具可以用Python的pandasmatplotlib周期生成静态PNG发到管理群如果想做得更顺手把SQLite数据库的只读副本挂到Superset做一个面向HR的数据看板按部门维度切一切就够了。我强烈建议做两个「小实验」来验证模型质量第一手动挑一个月把考勤、薪酬、绩效三张表的结果算出来和上个月的报表做差异比对第二选一个离职员工把他在职期间的考勤异常数、薪酬变动、绩效评分全部串起来看看数据能不能自洽。跑通这两条就算体系建成了。最后还想提一个技巧如果你所在的企业还在用共享盘传Excel可以考虑用sqlite3 .dump定期导出备份再配合git做版本管理。每一版数据结构、每一次数据修正都有记录出了问题可以git diff到具体是哪个环节谁动过数据。这一手是很多中型企业都还没做到的但它只需要你花二十分钟把脚本挂在cron上。本文还有配套的精品资源点击获取