如何从表中返回“缺失”的行 - 员工缺席报告

时间:2012-12-24 16:33:11

标签: java mysql sql

我有两张桌子,如下:

master
---------
empcode    INT PRIMARY KEY
name       VARCHAR
dept       VARCHAR 

emp_tx
----------
empcode    INT        references MASTER(empcode)
s_date     DATETIME

emp_tx表记录员工“in”和“out”事务。列s_date存储“in”或“out”事件发生时的时间(作为DATETIME值)。交易记录从办公区域(通过指纹生物识别系统。)

来自emp_tX表的示例数据:

    empcode   s_datetime
    -------   ------------------    
    1110      2012-12-12 09:31:42  (employee in  time to the office)
    1110      2012-12-12 13:34:17  (employee out time for lunch)
    1110      2012-12-12 14:00:17  (employee in  time after lunch)
    1110      2012-12-12 18:00:12  (employee out time after working hours)
    1112
    etc.

注意: 如果某个员工在某一天没有离开办公室,那么该日期的emp_tx交易表中不会插入任何行。在给定日期缺少员工将由该员工的“缺失”行和该日期表示。

任何人都可以帮助我获取一个返回员工缺席日期的SQL查询,以生成员工缺席报告吗?

查询的输入将是两个DATE值,一个“from”日期和一个“to”日期,它指定一个日期范围。查询应该返回所有出现的“缺席”(或者,非事件,而不是,当EMP_TX表中的empcode表中没有在“来自”和“之间的任何日期的任何日期到“约会。

预期输出:

如果我们输入'2012-12-12'作为“from”日期,并将'2012-12-20'作为“to”日期输入,则查询应返回如下行:

Empcode  EmpName  Department  AbsentDate  TotalNoofAbsent days
-------  -------  ----------  ----------- --------------------
1110     ABC      Accounts    2012-12-12
1110     ABC      Accounts    2012-12-14                     2   
1112     xyz      Software    2012-12-19
1112     xyz      Software    2012-12-17                     2

我已经尝试过这个查询,我确信它没有返回我想要的行:

select tx.date from Emp_TX as tx where Date(S_Date) not between '2012-12-23' and '2012-12-30'

感谢。

2 个答案:

答案 0 :(得分:2)

如果“缺席”被定义为特定日期(日期=午夜至午夜24小时)的特定emp_tx的{​​{1}}表中的行不出现,并且...

如果对于该日期empcode表中没有任何交易的日期(即排除该日期不存在所有代码的日期),可以接受不显示“缺席”,那么......

您可以使用以下查询获取指定结果集的前四列:(未经测试)

emp_tx

获得在同一结果集中返回的第五列SELECT m.empcode AS `EmpCode` , m.name AS `EmpName` , m.dept AS `Department` , d.dt AS `AbsentDate` FROM ( SELECT DATE(t.s_date) AS dt FROM emp_tx t WHERE t.s_date >= '2012-12-12' AND t.s_date < DATE_ADD( '2012-12-20' ,INTERVAL 1 DAY) GROUP BY DATE(t.s_date) ORDER BY DATE(t.s_date) ) d CROSS JOIN master m LEFT JOIN emp_tx p ON p.s_date >= d.dt AND p.s_date < d.dt + INTERVAL 1 DAY AND p.empcode = m.empcode WHERE p.empcode IS NULL ORDER BY m.empcode , d.dt 是可能的,但它会使该查询非常混乱。在处理返回的结果集时,可以在客户端更有效地处理此详细信息。


查询的工作原理

作为TotalNoofAbsent别名的内联视图为我们提供了一组我们正在检查的“日期”值。使用d表作为这些“日期”值的来源是一种方便的方法。 emp_tx函数不仅仅返回DATETIME参数的“date”部分;我们使用DATE()来获取不同的日期列表(即没有重复的值)。 (我们所使用的内联视图查询是在作为参数传递的两个值之间的一组不同的DATE值。还有其他更复杂的方法来生成DATE值列表。)

只要您将视为“缺席”的每个“日期”值出现在表格的某个位置(即,至少有一个GROUP BY在每个感兴趣的日期都有一个交易),并且只要empcode表中的行数不多,那么内联视图查询就能很好地工作。

(注意:内联视图中的查询可以单独运行,以验证结果是否正确并且正如我们所期望的那样。)

下一步是从内联视图中获取结果并执行emp_tx操作(生成笛卡尔积)以将每个CROSS JOIN与从{4}返回的每个empcode匹配内联视图。此操作的结果表示每次可能出现的“出勤”。

查询的最后一步是使用dateLEFT JOIN谓词执行“反加入”操作。 WHERE IS NULL(外部联接)返回每个可能的出席事件(从左侧),包括那些没有来自LEFT JOIN表的匹配行(出勤记录)。

“技巧”是包含一个谓词(在WHERE子句中),该谓词丢弃找到匹配的考勤记录的所有行,因此我们剩下的就是emp_tx和{的所有组合{1}}(可能的出勤事件),没有匹配的出勤交易。

(注意:我故意在谓词中将对s_date(DATETIME)列的引用保留为“bare”,并使用范围谓词。这将允许MySQL有效地使用包含该列的适当索引。)

如果我们将列引用包装在函数内的谓词中,例如empcode,那么MySQL将无法有效利用date列上的索引。


正如其中一条评论(关于你的问题)所指出的那样,我们没有对将员工标记为“进入”或“外出”的交易进行任何区分。我们只是在给定的24小时“午夜到午夜”期间寻找该代码的交易。


还有其他方法可以获得相同的结果集,但“反连接”模式通常可以提供大集合的最佳性能。

为了获得最佳性能,您可能需要覆盖索引:

DATE(p.s_date)

答案 1 :(得分:0)

不幸的是,您的查询将为您带来大量结果......它将始终返回您所提供范围之外的员工的所有日期。您想检查NOT EXISTS您的日期之间的记录。

可以在纯SQL中执行此操作...如果不使用游标或特定于DB的某些内容,我无法想到一种方法。这个Java伪代码将为您提供1名员工的缺席:

List<Date> findAbsences(int empCode, Date inDate, Date outDate) {

    List<Date> result = new LinkedList<Date>();

    Calendar c = new Calendar();
    c.setTime(new Date(2012,12,12));

    while (!c.getTime().after(outDate)) {
        // run query for EMP_TX records between inDate & outDate
        //SELECT 1 FROM EMP_TX WHERE EmpCode = :empid AND S_Date BETWEEN :in AND :out;

        if (!query.hasNext()) {
            result.add(c.getTime);
        }

        c.add(Calendar.DATE, 1);
    }


}