简介:这是一套考勤登记管理系统的完整项目资料包,适合正在做课程设计、毕业设计或初入职场的开发人员参考。资源同时提供前后端实现源码、可交互原型以及数据库脚本,能够帮助学习者快速理解考勤业务从页面设计到数据存储的完整链路。压缩包共4个文件,主要包含sql与bak两种数据库备份文件,分别用于还原数据表结构与初始记录,pdf用于查看整体设计方案和流程说明,zip内为可直接运行或二次开发的项目源码。据后台统计,已有716人学习下载。通过这套资料,读者可以拿到数据库建表脚本、系统原型图以及核心功能实现代码,便于对照学习打卡登记、请假审批、出勤统计等常见模块的开发方式,也能减少从零搭建环境的时间成本。整体包体仅963KB,文件组织精简,适合快速部署和本地调试。
1. 考勤登记管理系统的“源码+原型+数据库”三件套,能帮你省掉什么
接手一套考勤登记管理系统,最常见的交付形态就是标题里这三个词:源码、原型、数据库。源码管业务逻辑,原型管页面和流程对齐,数据库管时间、人和状态。这套组合能解决的问题很直接——中小团队内部考勤、外包项目的快速交付、还有课程设计里永远不缺的“考勤系统”题目。反直觉的一点是:代码反而是三件套里最省事的部分,真正让项目返工的是数据库字段和原型里的状态机没对齐。这篇文章按我自己做考勤系统的顺序讲:先画原型,再定库,最后写接口,顺带把几个翻车点摆出来。全程不依赖特定框架,逻辑换 Java、PHP 都成立。
2. 先画原型再写代码:考勤系统的页面流转与数据字典怎么定
2.1 为什么考勤系统必须先画原型再建表
考勤登记管理系统看起来需求清晰,实际是“每个人脑子里都有一套”的典型业务。上班时间几点算迟到、迟到几分钟内不算、补卡要不要审批、请假按小时还是按天扣——这些规则不定清楚,数据库表结构一定改到怀疑人生。我见过最离谱的案例是开发直接建表,上线两周后产品说要支持“一天打两次上班卡”的弹性班次,整张考勤记录表推倒重来。
所以现在我做这类系统,第一件事永远是画原型。原型的价值不是给领导看界面,而是把页面字段、按钮、状态变化画出来,让所有人对着同一组页面说话。画原型时重点标出三类内容:输入字段(打卡时间、工号、请假事由)、展示字段(出勤天数、迟到次数)、状态值(正常、迟到、早退、缺卡、待审批)。这三类字段列齐全,数据库的表结构基本就定了七八成。原型设计阶段花三天,数据库设计阶段就能省三个星期。
2.2 原型页面清单:六张页面把角色和流程钉死
考勤系统按角色切,最少六张页面能跑通主流程。我做原型时不会一上来画高保真,先用一张页面清单把每个页面的角色、核心字段、跳转关系列出来,确认无误再动手画图。页面清单长这样:
| 页面 | 角色 | 核心字段 | 状态流转 |
|---|---|---|---|
| 登录页 | 全员 | 工号、密码 | 登录成功进入打卡页或管理页 |
| 打卡页 | 员工 | 打卡按钮、当前时间、班次、打卡结果 | 正常 / 迟到 / 早退 / 重复打卡 |
| 考勤记录页 | 员工、管理员 | 日期范围、上下班时间、状态 | 按天展示,可筛选月份 |
| 请假申请页 | 员工 | 假期类型、开始时间、结束时间、时长、事由 | 待审批 → 通过 / 驳回 |
| 请假审批页 | 管理员 | 申请单列表、审批意见 | 通过 / 驳回 |
| 统计页 | 管理员 | 月份、出勤天数、迟到次数、请假时长 | 汇总报表,可导出 |
这张清单里最容易被漏掉的是“重复打卡”的状态。很多考勤系统原型只画了“打上班卡 → 打下班卡”的直线流程,没考虑员工手滑多打了一次、或者早上打了卡又出去再回来。我在打卡页原型上会专门加一个提示区:当天该类型已打卡时,页面显示“如需修改请走补卡流程”,而不是静默失败。
2.3 打卡异常流和补卡流:原型里最容易漏的状态机
正常打卡流程谁都会画,真正体现原型功底的是异常流。考勤状态我一般拆成五态:正常、迟到、早退、缺卡、请假。其中缺卡不是人手动选的,是系统在日终批处理时自动算出来的——当天只有一条上班卡没有下班卡,状态就置为缺卡。这个逻辑必须在原型里写清楚,否则开发会做成“状态手动改”。
补卡流程是另一个高频踩坑点。原型里补卡不能设计成“直接改记录”,否则员工可以自己把迟到改成正常。我一般这样设计:打卡页提供“补卡申请”入口,提交后生成一条补卡单,状态为待审批,管理员审批通过后才把考勤记录的状态修正。补卡单上要带原始打卡时间和补卡原因,审批页能看到这两条信息。这套流程对应数据库里的补卡记录表和审批状态字段,原型不画这一步,数据库就少一张表。
2.4 从页面字段到数据字典:这一步决定数据库会不会返工
原型确认后,我会把每张页面的字段整理成数据字典,一个页面字段对应一个表的字段。比如打卡页的“打卡时间”对应考勤记录表的 clock_time,“打卡结果”对应 status 字段;“班次”在原型里如果出现下拉框,数据库就必须有考勤规则表或班次表。整理的时候我会用一张字段对照表,把页面原型区域的字段名、数据库表名、字段类型、是否必填写在一起,发给开发评审。
数据字典里最容易漏的是“操作人”和“操作时间”。考勤记录谁录的、什么时候录的,审批单谁批的、什么时候批的,这些问题在原型上不体现,到对账的时候才发现查不出来。所以我在每张业务表里都会预留 created_at、updated_at,在审批表里额外加 approver_id 和 approve_time。这四个月段字段在原型阶段就要写进数据字典,不要等上线后补。
3. 数据库设计:考勤登记系统的表结构、索引与时区选型
3.1 事件流水表 vs 天级宽表:考勤数据存哪里更合理
考勤系统的表结构设计,第一个岔路口就是选“流水表”还是“宽表”。宽表是一天一行,字段里直接放上班时间、下班时间,查询汇总时很直观;流水表是每个动作一行,上班打卡插一条、下班打卡插一条,补卡再插一条。我第一次做考勤系统选了宽表,后来支持补卡和弹性班次时改到崩溃——宽表一旦要记“第二次上班卡”,要么加字段,要么改造成子表,哪种都别扭。
现在我做考勤登记,一律用事件流水表。考勤本质是事件流:上班是一个事件,下班是一个事件,补卡是另一个事件。流水表核心字段就三个:谁、哪一天、什么类型,再加一个打卡时间。请假、外勤、补卡都能往里面插记录,不需要改表结构。代价是汇总时要多做一次行转列或条件聚合,但对现代数据库来说这点计算量可以忽略。经验是:业务状态越多、异常类型越杂,越应该选流水表。
3.2 三张核心表与用户表的建表SQL
数据库最少四张表:用户表、考勤记录表、请假表、补卡表。用户表管员工和角色,考勤记录表存流水,请假表存请假单,补卡表存补卡单。先看用户表和考勤记录表的建表语句:
CREATE TABLE sys_user ( id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, emp_no VARCHAR(20) NOT NULL COMMENT '工号', emp_name VARCHAR(50) NOT NULL COMMENT '姓名', dept_id INT UNSIGNED DEFAULT 0 COMMENT '部门ID', role TINYINT NOT NULL DEFAULT 2 COMMENT '1管理员 2员工', status TINYINT NOT NULL DEFAULT 1 COMMENT '1在职 0离职', created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_emp_no (emp_no) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表'; CREATE TABLE att_record ( id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, employee_id INT UNSIGNED NOT NULL COMMENT '用户ID', work_date DATE NOT NULL COMMENT '考勤日期', clock_type TINYINT NOT NULL COMMENT '1上班 2下班 3补卡', clock_time DATETIME NOT NULL COMMENT '实际打卡时间', status TINYINT NOT NULL DEFAULT 0 COMMENT '0正常 1迟到 2早退 3缺卡', source TINYINT NOT NULL DEFAULT 1 COMMENT '1打卡机 2手动录入', remark VARCHAR(255) DEFAULT '' COMMENT '备注', created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_emp_date_type (employee_id, work_date, clock_type), KEY idx_emp_date (employee_id, work_date), KEY idx_work_date (work_date) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='考勤记录表';逻辑说明:att_record 表用 clock_type 区分上下班和补卡,每条记录是一个独立事件。status 字段在插入时就计算好,后面汇总直接累加,避免月底大量重算。注意唯一索引 uk_emp_date_type,这是防重复打卡的关键,后面避坑章节会细说。参数说明:employee_id 用整型关联 sys_user.id,不用工号做外键,因为工号可能变更,而 ID 不会变;clock_time 用 DATETIME 而不是 TIMESTAMP,原因见 3.4。
请假表和补卡表是同一套审批结构,可以合成一张流程表,也可以拆两张。我习惯拆开,避免一张表里混两种业务导致查询条件复杂。请假表至少要有:开始时间、结束时间、请假类型、时长(小时)、原因、审批状态、审批人、审批时间。补卡表则要带原始打卡时间和补卡原因。
3.3 索引设计:唯一索引是防重复打卡的第一道闸
索引设计决定了考勤系统在数据量上来后还能不能流畅查。考勤记录表的查询模式基本固定:查某人某月、查某部门某天、统计某月异常次数。所以索引不是越多越好,而是对着查询模式建。我维护三条索引就够:唯一索引 uk_emp_date_type 同时承担防重和按员工按日期查两张用途;普通索引 idx_emp_date 用于按月范围查询;idx_work_date 用于全局按天查询。
插入时的判重就靠 uk_emp_date_type。打卡接口执行 INSERT 时,如果同一个人同一天同一类型已经存在,数据库直接报 Duplicate Entry,代码捕获后返回“已打卡”提示。这里有个并发细节:先 SELECT 再 INSERT 在高并发下存在竞态,两个人同时提交时两个 SELECT 都查不到,然后两个 INSERT 都成功。解决办法是让唯一索引做最终防线,代码层先查一次只是为了友好提示,真正拦截靠索引。补卡记录也要纳入同一套唯一键,否则会出现一天两条类型为 3 的补卡记录。
3.4 时间字段与时区:DATETIME、TIMESTAMP和连接串参数
考勤系统的时间字段最容易翻车。业务上我们要的是“打卡那一刻的本地时间”,不关心时区转换。TIMESTAMP 类型在 MySQL 中会按会话时区做转换,一旦应用服务器和数据库服务器的时区不一致,查出来的时间可能差 8 小时。我统一用 DATETIME 存业务时间,DATETIME 不做任何时区转换,存什么就是什么,适合考勤这种强本地属性的业务。创建时间和更新时间也建议用 DATETIME,配合 DEFAULT CURRENT_TIMESTAMP 使用。
数据库连接串里的时区参数必须显式指定。以 MySQL 为例,JDBC 连接串要加 serverTimezone=Asia/Shanghai;用 Python 的 SQLAlchemy 则在 URL 里加 charset=utf8mb4,并在 engine 创建时传 timezone 相关配置。SQLite 没有时区问题,本地演示可以用它,但正式环境我建议直接上 MySQL 或 PostgreSQL,因为考勤汇总语句里用到的日期函数在各数据库行为不同。另一个坑是 MySQL 的 sql_mode 里如果有 NO_ZERO_DATE,插入“0000-00-00”会直接报错,初始化数据时要注意默认时间不要写成零值日期。
4. 源码落地:Flask 实现考勤打卡和月度汇总
4.1 环境与依赖:轻量后端 + 连接池
代码实现我选用 Flask + SQLAlchemy 做演示,因为能最快跑通增删改查。逻辑换了 Spring Boot 或 PHP 一样成立。考勤系统对后端的要求不高,但对数据库连接的管理很敏感,尤其是几十号人同时打卡的时段。我用 SQLAlchemy 的 create_engine 统一管理连接池,不在函数里手动创建连接,避免连接泄漏:
from sqlalchemy import create_engine import os DATABASE_URL = os.getenv( "DATABASE_URL", "mysql+pymysql://root:password@127.0.0.1:3306/attendance?charset=utf8mb4" ) engine = create_engine( DATABASE_URL, pool_size=10, max_overflow=20, pool_pre_ping=True, pool_recycle=3600 )参数说明:pool_size 是连接池常驻连接数,设为 10 基本够中小团队用;max_overflow 是峰值时额外创建的连接上限,20 意味着最多 30 个并发;pool_pre_ping 会在每次取连接前做一次轻量探测,避免拿到已经断开的连接;pool_recycle 让超过 3600 秒的连接被回收重建,绕开 MySQL 的 wait_timeout 断连问题。这组参数是我做了多个考勤系统后固定下来的起点。
4.2 打卡接口:判重、迟到标记和补卡入口
打卡接口是考勤系统的核心,代码要同时处理三件事:判断当天该类型是否已打卡、确定状态(正常/迟到/早退)、写入流水。先看实现:
from flask import Flask, request, jsonify from datetime import datetime, date, time from sqlalchemy import text app = Flask(__name__) def get_clock_type(now): # 12点前算上班卡,12点后算下班卡。真实系统应按班次配置判断 return 1 if now.hour < 12 else 2 def calc_status(clock_type, clock_time): # 上班卡晚于 09:00 标迟到,下班卡早于 18:00 标早退 # 宽限分钟可以放到配置表里,这里先写死 if clock_type == 1 and clock_time.time() > time(9, 5): return 1 if clock_type == 2 and clock_time.time() < time(17, 55): return 2 return 0 @app.route("/api/clock", methods=["POST"]) def clock_in(): data = request.get_json(silent=True) or {} emp_id = data.get("emp_id") if not emp_id: return jsonify({"code": 1, "msg": "emp_id不能为空"}), 200 now = datetime.now() work_date = date.today() clock_type = get_clock_type(now) status = calc_status(clock_type, now) with engine.begin() as conn: try: conn.execute( text( "INSERT INTO att_record " "(employee_id, work_date, clock_type, clock_time, status) " "VALUES (:e, :d, :t, :now, :s)" ), {"e": emp_id, "d": work_date, "t": clock_type, "now": now, "s": status} ) except Exception: # 唯一索引冲突说明当天已打过同类卡 return jsonify({"code": 1, "msg": "当天该类型已打卡,如需修改请走补卡流程"}), 200 return jsonify({"code": 0, "msg": "ok", "clock_time": now.strftime("%Y-%m-%d %H:%M:%S")})逻辑说明:先用 get_clock_type 判断是上班还是下班卡,再用 calc_status 计算状态,最后用 INSERT 写入。注意这里没有先 SELECT 再 INSERT,而是直接插入,靠唯一索引捕获重复——这样并发下也不会出现两条重复卡。status 在入库时就算好,月底报表直接 SUM,不用重算。参数说明:迟到宽限 5 分钟、早退宽限 5 分钟在演示里写死在 time(9,5) 和 time(17,55),真实项目要拆到考勤规则表里,按部门甚至按人配置。
补卡入口走单独接口,生成补卡单而不是改原记录:
@app.route("/api/clock/retro", methods=["POST"]) def retro_apply(): data = request.get_json(silent=True) or {} emp_id = data.get("emp_id") work_date = data.get("work_date") clock_type = data.get("clock_type") reason = data.get("reason", "") if not all([emp_id, work_date, clock_type]): return jsonify({"code": 1, "msg": "参数不完整"}), 200 with engine.begin() as conn: conn.execute( text( "INSERT INTO att_retro " "(employee_id, work_date, clock_type, reason, status) " "VALUES (:e, :d, :t, :r, 0)" ), {"e": emp_id, "d": work_date, "t": clock_type, "r": reason} ) return jsonify({"code": 0, "msg": "补卡申请已提交"}), 200补卡接口只插入申请单,状态为待审批,原考勤记录不动。这样审批通过后,我们可以在审批逻辑里把原记录状态修正为正常,同时保留申请单作为审计痕迹。这套设计对应原型里的补卡流程,避免员工直接篡改打卡时间。
4.3 月度汇总:把状态在入库时算好,报表期不用重算
月度汇总用一条 SQL 完成。考勤登记管理系统最常见的报表需求是:某月出勤几天、迟到几次、早退几次、请假几小时。因为状态在插入时就写好了,这里只需要按状态计数:
SELECT DATE_FORMAT(work_date, '%Y-%m') AS month, employee_id, COUNT(DISTINCT work_date) AS attend_days, SUM(CASE WHEN clock_type = 1 AND status = 1 THEN 1 ELSE 0 END) AS late_times, SUM(CASE WHEN clock_type = 2 AND status = 2 THEN 1 ELSE 0 END) AS early_times, SUM(CASE WHEN status = 3 THEN 1 ELSE 0 END) AS miss_days FROM att_record WHERE work_date BETWEEN :month_start AND :month_end AND clock_type IN (1, 2) GROUP BY month, employee_id;逻辑说明:attend_days 用 COUNT(DISTINCT work_date),因为一天有两条记录(上班+下班),直接 COUNT(*) 会把出勤天数算成两倍。迟到只统计上班卡(clock_type=1),早退只统计下班卡(clock_type=2),用条件聚合把两类状态分开。缺卡统计的是状态为 3 的记录数,这个状态由日终批处理写入。参数说明:month_start 和 month_end 是月初和月末的日期,查询接口里用时间字符串传进来即可。如果数据量超过百万行,建议给 work_date 加普通索引,并限制只能查最近 12 个月。
4.4 和原型/数据库怎么对得上:接口字段对照表
代码写完后,我会做一次“原型字段 → 表字段 → 接口字段”的对照检查,防止前后端各说各话。比如打卡页原型的“打卡时间”,对应数据库 att_record.clock_time,对应接口返回值里的 clock_time;“打卡结果”对应 status 的 0/1/2/3。接口返回的 JSON 字段名和数据库字段名保持一致,能省掉一层转换。我用对照表的形式把三张核心表的字段和接口映射列在一起,发给前端同事对齐,比口头沟通高效得多。
| 原型页面字段 | 数据库字段 | 接口返回字段 | 示例值 |
|---|---|---|---|
| 打卡时间 | att_record.clock_time | clock_time | 2025-03-17 09:00:12 |
| 打卡结果 | att_record.status | status | 1(迟到) |
| 请假类型 | att_leave.leave_type | leave_type | 年假 / 事假 / 病假 |
| 审批状态 | att_leave.status | status | 0待审 1通过 2驳回 |
| 补卡原因 | att_retro.reason | reason | 早上地铁故障 |
这套对照表做完,前后端联调基本一遍过。我见过太多项目返工是因为同一个字段在数据库叫 status,在接口里叫 clock_result,在页面上叫“考勤结果”,看起来是小事,实际排查问题时要绕一大圈。
5. 常见问题与避坑:考勤系统从原型到上线的5个翻车点
5.1 一天打五张卡:唯一索引没建
现象:员工早上对着打卡机多按了几次,考勤记录表里同一个人同一天出现 5 条上班卡记录,月底统计迟到次数直接翻倍。
原因:建表时没在 (employee_id, work_date, clock_type) 上建唯一索引,接口里也没有判重逻辑。打卡机连续触发、前端重复提交时,数据库照单全收。
解决:建表时就把 uk_emp_date_type 唯一索引加上,接口直接用 INSERT 捕获 Duplicate Entry 异常返回提示。已产生的脏数据先按 employee_id、work_date、clock_type 分组保留最早一条,其余删除。注意补卡记录也要参与唯一约束,否则会出现多条补卡单同时生效。
5.2 打卡时间凭空少8小时:时区参数不统一
现象:数据库里的 clock_time 存的是 01:00,员工实际打卡时间是 09:00。或者查出来显示 1970-01-01 08:00 这种奇怪时间。
原因:应用服务器时区是 UTC,数据库时区是 Asia/Shanghai,TIMESTAMP 类型在存储时做了时区转换。还有一种是连接串里没指定 serverTimezone,JDBC 驱动用了 JVM 默认时区去解析 TIMESTAMP。
解决:业务时间字段全部用 DATETIME,不碰 TIMESTAMP。DATETIME 存什么就是什么,不依赖时区配置。连接串显式加 serverTimezone=Asia/Shanghai(JDBC)或 charset=utf8mb4(SQLAlchemy/PyMySQL)。历史数据如果已经乱了,按 8 小时差值 UPDATE 修正,但前提是确认原来存的是 UTC 时间。
5.3 给考勤表加字段,线上入库全卡住
现象:上线后要加一个“外勤地点”字段,执行 ALTER TABLE att_record ADD COLUMN location VARCHAR(100),结果打卡接口全部超时,页面转圈。
原因:大表 ALTER TABLE 在 MySQL 5.7 及更早版本会锁全表,DML 操作全部阻塞。考勤表如果有几百万行,加字段的操作可能持续几分钟到几十分钟。
解决:小表(百万行以内)低峰期直接 ALTER,先看表行数再动手。大表用 pt-osc 或 gh-ost 做在线变更,原理是建影子表、同步增量、最后切换。日常开发时就把字段设计完整,原型阶段的数据字典多花点时间,数据库结构尽量一次定稿。应急处理时可以先新加一张扩展表,用 employee_id + work_date 关联,避免阻塞业务。
5.4 数据库连一会儿就 Too many connections
现象:打卡高峰期接口报 “Too many connections”,数据库 CPU 不高但连接数打满,重启后过一阵又复现。
原因:代码里每次请求手动创建数据库连接,用完没 close。或者 SQLAlchemy 的 session 没有关闭,连接没有归还到连接池。还有一种情况是连接池被长时间占用的慢查询拖死。
解决:统一用 SQLAlchemy engine 管理连接,禁止在函数里裸调 pymysql 连接。写操作使用 with engine.begin() 事务块,事务结束连接自动归还。配置 pool_pre_ping=True 和 pool_recycle=3600,把死连接和老连接清理掉。慢查询单独排查,给 work_date、employee_id 建索引,避免全表扫描把连接占住不放。
5.5 Excel批量导入考勤记录,日期变成数字
现象:用 Excel 模板批量导入考勤机导出的 xlsx,日期列变成 45000.123 这种数字,中文列变成乱码,导致考勤记录错乱。
原因:xlsx 里的日期本质是序列值,1900 系统下以 1899-12-30 为 0 起算,45000 对应 2023-03-15。代码如果用 Excel 单元格的原始值直接入库,就会存成数字。CSV 文件则可能被 Excel 以 ANSI 编码另存,代码按 UTF-8 读直接乱码。
解决:用 openpyxl 读取时判断 cell 的数据类型,如果是日期类型或 is_date 为 True,用 cell.value 直接拿到 datetime 对象,再格式化成字符串。读取 CSV 时强制以 utf-8-sig 编码打开,兼容 Excel 的带 BOM UTF-8 和 ANSI 两种情况。导入前做校验:打卡时间列必须是合法日期格式,工号必须存在于 sys_user 表,否则整行拒绝并给出错误行号。批量导入的 SQL 用 executemany 批量插入,遇到重复数据按唯一索引跳过而不是中断。
6. 进阶:考勤数据交给工资系统前,先做这三件事
考勤登记管理系统做到能打卡、能请假、能出报表,只能算跑通。真正检验设计的是月底把考勤数据交给工资系统那一刻。第一件事是汇总裁断:工资计算期间不允许员工再补卡或改审批,系统在每月最后一天跑一个批处理,把所有待审批的补卡单和请假单冻结,生成快照表。这个快照表就是工资系统的数据源,之后的历史修改一律不进快照,避免工资算完又变。我一般把快照表命名为 salary_att_snapshot,字段照抄汇总结果,加一个 snapshot_time 标记版本。
第二件事是请假天数合并扣减。考勤汇总只算出勤天数和迟到次数,但工资系统需要知道员工当月请了几天假。请假表存的是开始时间和结束时间,可能是按小时请的,要和考勤记录按天对齐。我的做法是:把请假单展开成“每天一条”的临时表,再和考勤记录表按 employee_id + work_date 关联,请假当天的出勤状态标记为请假,扣减出勤天数。这一步在 SQL 里用日期递归或数字辅助表实现,不要在应用层循环逐天判断。
第三件事是输出一张工资系统能直接消费的宽表。工资系统不关心你内部是流水表还是状态机,它只想要一张“员工、月份、出勤天数、迟到次数、请假小时数、应扣款”的宽表。我最后汇总结算时会生成一张视图,把三张表的数据 JOIN 好,工资系统直接 SELECT 就行。如果担心月底大查询拖慢业务库,可以用现成的数据库同步软件把考勤库只读副本同步到报表库,在副本上跑汇总。这是我做了三个考勤系统后才总结出来的流程,前两次都是工资系统跑一半发现补卡单还在审批流里,结果数据对不上,整个部门陪着查账。现在先把这三件事固化进代码里,月末流程基本不用人盯。希望帮到你。
本文还有配套的精品资源,点击获取