我的电脑操作系统版本为Win7旗舰版(ServicePack1),Oracle版本为Oracle11g
程序使用的jar包有:mybatis-3.2.2.jar、ojdbc14-10.2.0.2.0.jar
本例中使用的配置文件mybatis-config.xml,可以参见我的另一篇Blog《一个简单的MyBatis连接Oracle数据库的例子》(http://my.oschina.net/Tsybius2014/blog/626206)
本文还是一个使用MyBatis进行查询的练习之作。
现有三张表:
1)OPERATOR表(签到人员信息表)记录了人员的ID和名称,该表中每个打卡人都作为一条数据存入
2)WORKDAY表(工作日历表)记录了工作日历,该表中每日都作为一条数据存入,INFO_DATE列为日期,IS_WORK_DAY列为1时为工作日,为0时为非工作日
3)CLOCKING_IN_DATA表(打卡数据表)记录了考勤打卡情况,每次打卡向该表中插入一条数据,OPER_DATE为打卡日期,OPER_TIME为打卡时间,OPER_ID为打卡人ID,OPER_NAME为打卡人姓名
要求根据这三张表,计算出指定人的出勤情况。
使用下面的SQL脚本,可以向Oracle数据库中插入基础数据:
-- 签到人员信息表 CREATE TABLE OPERATOR ( ID NUMBER(12, 0) PRIMARY KEY, OPER_ID VARCHAR(10), OPER_NAME VARCHAR(10) ); INSERT INTO OPERATOR (ID, OPER_ID, OPER_NAME) VALUES (1, '0001', 'Tsybius'); INSERT INTO OPERATOR (ID, OPER_ID, OPER_NAME) VALUES (2, '0002', 'Galatea'); -- 工作日历表 CREATE TABLE WORKDAY ( ID NUMBER(12, 0) PRIMARY KEY, INFO_DATE NUMBER(10, 0), IS_WORK_DAY CHAR ); -- 2016年2月1-9日的工作日(1-5、8、9是工作日,6、7不是工作日) INSERT INTO WORKDAY (ID, INFO_DATE, IS_WORK_DAY) VALUES (1, 20160201, '1'); INSERT INTO WORKDAY (ID, INFO_DATE, IS_WORK_DAY) VALUES (2, 20160202, '1'); INSERT INTO WORKDAY (ID, INFO_DATE, IS_WORK_DAY) VALUES (3, 20160203, '1'); INSERT INTO WORKDAY (ID, INFO_DATE, IS_WORK_DAY) VALUES (4, 20160204, '1'); INSERT INTO WORKDAY (ID, INFO_DATE, IS_WORK_DAY) VALUES (5, 20160205, '1'); INSERT INTO WORKDAY (ID, INFO_DATE, IS_WORK_DAY) VALUES (6, 20160206, '0'); INSERT INTO WORKDAY (ID, INFO_DATE, IS_WORK_DAY) VALUES (7, 20160207, '0'); INSERT INTO WORKDAY (ID, INFO_DATE, IS_WORK_DAY) VALUES (8, 20160208, '1'); INSERT INTO WORKDAY (ID, INFO_DATE, IS_WORK_DAY) VALUES (9, 20160209, '1'); -- 打卡数据表 CREATE TABLE CLOCKING_IN_DATA ( ID NUMBER(12, 0) PRIMARY KEY, OPER_DATE NUMBER(10, 0), OPER_TIME NUMBER(10, 0), OPER_ID VARCHAR(10), OPER_NAME VARCHAR(10) ); -- Tsybius 的上下班数据 20160201-20160207 -- 正常上下班1 20160201 INSERT INTO CLOCKING_IN_DATA (ID, OPER_DATE, OPER_TIME, OPER_ID, OPER_NAME) VALUES (1, 20160201, 85531, '0001', 'Tsybius'); INSERT INTO CLOCKING_IN_DATA (ID, OPER_DATE, OPER_TIME, OPER_ID, OPER_NAME) VALUES (2, 20160201, 180103, '0001', 'Tsybius'); -- 正常上下班2 20160202 INSERT INTO CLOCKING_IN_DATA (ID, OPER_DATE, OPER_TIME, OPER_ID, OPER_NAME) VALUES (3, 20160202, 85814, '0001', 'Tsybius'); INSERT INTO CLOCKING_IN_DATA (ID, OPER_DATE, OPER_TIME, OPER_ID, OPER_NAME) VALUES (4, 20160202, 180809, '0001', 'Tsybius'); -- 迟到 20160203 INSERT INTO CLOCKING_IN_DATA (ID, OPER_DATE, OPER_TIME, OPER_ID, OPER_NAME) VALUES (5, 20160203, 90302, '0001', 'Tsybius'); INSERT INTO CLOCKING_IN_DATA (ID, OPER_DATE, OPER_TIME, OPER_ID, OPER_NAME) VALUES (6, 20160203, 180809, '0001', 'Tsybius'); -- 早退 20160204 INSERT INTO CLOCKING_IN_DATA (ID, OPER_DATE, OPER_TIME, OPER_ID, OPER_NAME) VALUES (7, 20160204, 84513, '0001', 'Tsybius'); INSERT INTO CLOCKING_IN_DATA (ID, OPER_DATE, OPER_TIME, OPER_ID, OPER_NAME) VALUES (8, 20160204, 155324, '0001', 'Tsybius'); -- 迟到 + 早退 20160205 INSERT INTO CLOCKING_IN_DATA (ID, OPER_DATE, OPER_TIME, OPER_ID, OPER_NAME) VALUES (9, 20160205, 103501, '0001', 'Tsybius'); INSERT INTO CLOCKING_IN_DATA (ID, OPER_DATE, OPER_TIME, OPER_ID, OPER_NAME) VALUES (10, 20160205, 175000, '0001', 'Tsybius'); -- 旷工 - 只打了一次卡 20160208 INSERT INTO CLOCKING_IN_DATA (ID, OPER_DATE, OPER_TIME, OPER_ID, OPER_NAME) VALUES (11, 20160208, 83902, '0001', 'Tsybius'); -- 旷工 - 一次卡都没打 20160209 -- Galatea 的上下班数据 20160201-20160207 -- 加班到晚上11点 20160201 INSERT INTO CLOCKING_IN_DATA (ID, OPER_DATE, OPER_TIME, OPER_ID, OPER_NAME) VALUES (12, 20160201, 85531, '0002', 'Galatea'); INSERT INTO CLOCKING_IN_DATA (ID, OPER_DATE, OPER_TIME, OPER_ID, OPER_NAME) VALUES (13, 20160201, 235344, '0002', 'Galatea'); -- 加班到午夜12点,但12点前没有打卡 20160202 INSERT INTO CLOCKING_IN_DATA (ID, OPER_DATE, OPER_TIME, OPER_ID, OPER_NAME) VALUES (14, 20160202, 85349, '0002', 'Galatea'); INSERT INTO CLOCKING_IN_DATA (ID, OPER_DATE, OPER_TIME, OPER_ID, OPER_NAME) VALUES (15, 20160203, 3023, '0002', 'Galatea'); -- 正常上下班1 20160203 INSERT INTO CLOCKING_IN_DATA (ID, OPER_DATE, OPER_TIME, OPER_ID, OPER_NAME) VALUES (16, 20160203, 85958, '0002', 'Galatea'); INSERT INTO CLOCKING_IN_DATA (ID, OPER_DATE, OPER_TIME, OPER_ID, OPER_NAME) VALUES (17, 20160203, 180004, '0002', 'Galatea'); -- 系统升级一早就要去看着 20160204 INSERT INTO CLOCKING_IN_DATA (ID, OPER_DATE, OPER_TIME, OPER_ID, OPER_NAME) VALUES (18, 20160204, 53824, '0002', 'Galatea'); INSERT INTO CLOCKING_IN_DATA (ID, OPER_DATE, OPER_TIME, OPER_ID, OPER_NAME) VALUES (19, 20160204, 180213, '0002', 'Galatea'); -- 正常上下班2 20160205 INSERT INTO CLOCKING_IN_DATA (ID, OPER_DATE, OPER_TIME, OPER_ID, OPER_NAME) VALUES (20, 20160205, 84509, '0002', 'Galatea'); INSERT INTO CLOCKING_IN_DATA (ID, OPER_DATE, OPER_TIME, OPER_ID, OPER_NAME) VALUES (21, 20160205, 180329, '0002', 'Galatea'); -- 正常上下班3 20160208 INSERT INTO CLOCKING_IN_DATA (ID, OPER_DATE, OPER_TIME, OPER_ID, OPER_NAME) VALUES (22, 20160208, 85944, '0002', 'Galatea'); INSERT INTO CLOCKING_IN_DATA (ID, OPER_DATE, OPER_TIME, OPER_ID, OPER_NAME) VALUES (23, 20160208, 180114, '0002', 'Galatea'); -- 最后一天加班到半夜12点,12点前打了一次卡 20160209 INSERT INTO CLOCKING_IN_DATA (ID, OPER_DATE, OPER_TIME, OPER_ID, OPER_NAME) VALUES (24, 20160209, 84509, '0002', 'Galatea'); INSERT INTO CLOCKING_IN_DATA (ID, OPER_DATE, OPER_TIME, OPER_ID, OPER_NAME) VALUES (25, 20160209, 235603, '0002', 'Galatea'); INSERT INTO CLOCKING_IN_DATA (ID, OPER_DATE, OPER_TIME, OPER_ID, OPER_NAME) VALUES (26, 20160210, 2003, '0002', 'Galatea'); COMMIT; /
为统计出指定人的考勤打卡情况,我写了一个SQL,用于查询编号为0001的人(Tsybius)的出勤记录
统计规则如下:
1、一日的时间从当日0点起,至当日235959点止,每日统计最早的一次和最晚的一次打卡记录
2、第一次打卡事件晚于9点钟判定为迟到,最后一次打卡时间早于18点钟算早退
3、一天打卡次数少于2次、不少于2次但2次都在12点前或12点后,按旷工计算
其中条件3在实际查询中可以转换为:不打卡按旷工计算,仅上午或仅下午有打卡记录按旷工计算
查询结果约定如下:0为正常考勤,1为迟到,2为早退,3为迟到+早退,4为旷工
SELECT A.INFO_DATE, A.IS_WORK_DAY, C.OPER_ID, C.OPER_NAME, B.MIN_TIME, B.MAX_TIME, NVL(B.ATTENDANCE, 4) AS ATT_STATUS FROM (SELECT INFO_DATE, IS_WORK_DAY FROM WORKDAY) A LEFT JOIN (SELECT CI_DATA.*, (CASE WHEN CI_DATA.MIN_TIME < 90000 AND CI_DATA.MAX_TIME > 180000 THEN '0' ELSE CASE WHEN CI_DATA.MIN_TIME > 120000 OR CI_DATA.MAX_TIME < 120000 THEN '4' ELSE CASE WHEN CI_DATA.MIN_TIME > 90000 AND CI_DATA.MAX_TIME > 180000 THEN '1' ELSE CASE WHEN CI_DATA.MIN_TIME < 90000 AND CI_DATA.MAX_TIME < 180000 THEN '2' ELSE '3' END END END END) AS ATTENDANCE FROM (SELECT OPER_DATE, OPER_ID, OPER_NAME, MIN(OPER_TIME) AS MIN_TIME, MAX(OPER_TIME) AS MAX_TIME FROM CLOCKING_IN_DATA WHERE OPER_ID = '0001' GROUP BY OPER_DATE, OPER_ID, OPER_NAME) CI_DATA) B ON A.INFO_DATE = B.OPER_DATE, (SELECT OPER_ID, OPER_NAME FROM OPERATOR WHERE OPER_ID = '0001') C ORDER BY INFO_DATE
在PL/SQL中执行这个SQL语句,查询出的结果集如下:
现使用MyBatis查询这些数据,Java代码如下:
import java.io.InputStream; import java.text.MessageFormat; import java.util.Iterator; import java.util.List; import java.util.Map; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; /** * MyBatis使用测试 * @author Tsybius2014 * @date 2016年3月20日 * @time 下午5:40:55 * @remark * */ public class MyBatisTest { public static void main(String[] args) { try { String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); SqlSession session = sqlSessionFactory.openSession(); try { ClockingInMapper mapper = session.getMapper(ClockingInMapper.class); //按operId获取考勤记录 printAttrStatus(mapper.selectAttStatusByOperId("0001")); printAttrStatus(mapper.selectAttStatusByOperId("0002")); } finally { session.close(); } } catch (Exception ex) { ex.printStackTrace(); } } /** * 输出考勤记录 * @param attrStatusList 考勤记录表 */ public static void printAttrStatus(List<Map<String, Object>> attrStatusList) { if (attrStatusList == null || attrStatusList.size() == 0) { return; } System.out.println("******** 考勤记录 START ********"); Iterator<Map<String, Object>> iter = attrStatusList.iterator(); while (iter.hasNext()) { Map<String, Object> attrStatus = iter.next(); String infoDate = attrStatus.get("infoDate").toString(); String isWorkDay = attrStatus.get("isWorkDay").toString(); String operId = attrStatus.get("operId").toString(); String operName = attrStatus.get("operName").toString(); String minTime = attrStatus.get("minTime") != null ? attrStatus.get("minTime").toString() : ""; String maxTime = attrStatus.get("maxTime") != null ? attrStatus.get("maxTime").toString() : ""; String attStatus = attrStatus.get("attStatus").toString(); String attResult = ""; if (isWorkDay.equals("0")) { attResult = "非工作日不考勤"; } else if (isWorkDay.equals("1")) { if (attStatus.equals("0")) { attResult = "考勤正常"; } else if (attStatus.equals("1")) { attResult = "迟到"; } else if (attStatus.equals("2")) { attResult = "早退"; } else if (attStatus.equals("3")) { attResult = "迟到,早退"; } else if (attStatus.equals("4")) { attResult = "旷工"; } else { attResult = "非法数据"; } } else { attResult = "非法数据"; } String result = MessageFormat.format( "日期: {0},人员:{1}({2}),考勤结果:{3},{4}-{5}", infoDate, operName, operId, attResult, minTime, maxTime); System.out.println(result); } System.out.println("******** 考勤记录 END ********"); } }
ClockingInMapper.java代码如下:
import java.util.List; import java.util.Map; public interface ClockingInMapper { List<Map<String, Object>> selectAttStatusByOperId(String operId); }
ClockingInMapper.xml代码如下:
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="ClockingInMapper"> <resultMap id="AttendanceMap" type="java.util.Map" > <result column="INFO_DATE" property="infoDate" jdbcType="DECIMAL" /> <result column="IS_WORK_DAY" property="isWorkDay" jdbcType="CHAR" /> <result column="OPER_ID" property="operId" jdbcType="VARCHAR" /> <result column="OPER_NAME" property="operName" jdbcType="VARCHAR" /> <result column="MIN_TIME" property="minTime" jdbcType="DECIMAL" /> <result column="MAX_TIME" property="maxTime" jdbcType="DECIMAL" /> <result column="ATT_STATUS" property="attStatus" jdbcType="CHAR" /> </resultMap> <select id="selectAttStatusByOperId" parameterType="java.lang.String" resultMap="AttendanceMap" > SELECT A.INFO_DATE, A.IS_WORK_DAY, C.OPER_ID, C.OPER_NAME, B.MIN_TIME, B.MAX_TIME, NVL(B.ATTENDANCE, '4') AS ATT_STATUS FROM (SELECT INFO_DATE, IS_WORK_DAY FROM WORKDAY) A LEFT JOIN (SELECT CI_DATA.*, (CASE WHEN CI_DATA.MIN_TIME <![CDATA[<]]> 90000 AND CI_DATA.MAX_TIME <![CDATA[>]]> 180000 THEN '0' ELSE CASE WHEN CI_DATA.MIN_TIME <![CDATA[>]]> 120000 OR CI_DATA.MAX_TIME <![CDATA[<]]> 120000 THEN '4' ELSE CASE WHEN CI_DATA.MIN_TIME <![CDATA[>]]> 90000 AND CI_DATA.MAX_TIME <![CDATA[>]]> 180000 THEN '1' ELSE CASE WHEN CI_DATA.MIN_TIME <![CDATA[<]]> 90000 AND CI_DATA.MAX_TIME <![CDATA[<]]> 180000 THEN '2' ELSE '3' END END END END) AS ATTENDANCE FROM (SELECT OPER_DATE, OPER_ID, OPER_NAME, MIN(OPER_TIME) AS MIN_TIME, MAX(OPER_TIME) AS MAX_TIME FROM CLOCKING_IN_DATA WHERE OPER_ID = #{operId, jdbcType = VARCHAR} GROUP BY OPER_DATE, OPER_ID, OPER_NAME) CI_DATA) B ON A.INFO_DATE = B.OPER_DATE, (SELECT OPER_ID, OPER_NAME FROM OPERATOR WHERE OPER_ID = #{operId, jdbcType = VARCHAR}) C ORDER BY INFO_DATE </select> </mapper>
这段Java代码的运行结果如下:
******** 考勤记录 START ******** 日期: 20160201,人员:Tsybius(0001),考勤结果:考勤正常,85531-180103 日期: 20160202,人员:Tsybius(0001),考勤结果:考勤正常,85814-180809 日期: 20160203,人员:Tsybius(0001),考勤结果:迟到,90302-180809 日期: 20160204,人员:Tsybius(0001),考勤结果:早退,84513-155324 日期: 20160205,人员:Tsybius(0001),考勤结果:迟到,早退,103501-175000 日期: 20160206,人员:Tsybius(0001),考勤结果:非工作日不考勤,- 日期: 20160207,人员:Tsybius(0001),考勤结果:非工作日不考勤,- 日期: 20160208,人员:Tsybius(0001),考勤结果:旷工,83902-83902 日期: 20160209,人员:Tsybius(0001),考勤结果:旷工,- ******** 考勤记录 END ******** ******** 考勤记录 START ******** 日期: 20160201,人员:Galatea(0002),考勤结果:考勤正常,85531-235344 日期: 20160202,人员:Galatea(0002),考勤结果:旷工,85349-85349 日期: 20160203,人员:Galatea(0002),考勤结果:考勤正常,3023-180004 日期: 20160204,人员:Galatea(0002),考勤结果:考勤正常,53824-180213 日期: 20160205,人员:Galatea(0002),考勤结果:考勤正常,84509-180329 日期: 20160206,人员:Galatea(0002),考勤结果:非工作日不考勤,- 日期: 20160207,人员:Galatea(0002),考勤结果:非工作日不考勤,- 日期: 20160208,人员:Galatea(0002),考勤结果:考勤正常,85944-180114 日期: 20160209,人员:Galatea(0002),考勤结果:考勤正常,84509-235603 ******** 考勤记录 END ********
END