我正在开发一个简单的时间跟踪应用程序。
我创建了一个记录员工IN和OUT时间的表。
以下是我的数据目前的示例:
E_ID | In_Out | Date_Time
------------------------------------
3 | I | 2012-08-19 15:41:52
3 | O | 2012-08-19 17:30:22
1 | I | 2012-08-19 18:51:11
3 | I | 2012-08-19 18:55:52
1 | O | 2012-08-19 20:41:52
3 | O | 2012-08-19 21:50:30
我试图创建一个查询,将员工的IN和OUT时间分成一行,如下所示:
E_ID | In_Time | Out_Time
------------------------------------------------
3 | 2012-08-19 15:41:52 | 2012-08-19 17:30:22
3 | 2012-08-19 18:55:52 | 2012-08-19 21:50:30
1 | 2012-08-19 18:51:11 | 2012-08-19 20:41:52
我希望我清楚自己要在这里实现的目标。 基本上我想生成一个报告,将进出时间合并为一行。
对此的任何帮助将不胜感激。 提前谢谢。
答案 0 :(得分:3)
我能想到三种基本方法。
一种方法是使用MySQL用户变量,一种方法使用theta JOIN,另一种方法使用SELECT列表中的子查询。
θ-JOIN
一种方法是使用theta-JOIN。这种方法是一种通用的SQL方法(没有MySQL特定的语法),可以与多个RDBMS一起使用。
N.B。对于大量行,此方法可能会创建一个非常大的中间结果集,这可能会导致性能问题。
SELECT o.e_id, MAX(i.date_time) AS in_time, o.date_time AS out_time
FROM e `o`
LEFT
JOIN e `i` ON i.e_id = o.e_id AND i.date_time < o.date_time AND i.in_out = 'I'
WHERE o.in_out = 'O'
GROUP BY o.e_id, o.date_time
ORDER BY o.date_time
这样做与匹配每个'I'行的员工的每个'O'行匹配,然后我们使用MAX汇总来选择具有最接近日期时间的'I'记录。
这适用于完美配对的数据;可能会为不完美的对产生奇怪的结果......(两个连续的'O'记录没有中间的'I'行,都会匹配到同一个'I'行等等)
SELECT列表中的相关子查询
另一种方法是在SELECT列表中使用相关子查询。这可能具有次优性能,但有时可行(并且有时是返回指定结果集的最快方法...当我们在外部查询中返回有限数量的行时,此方法效果最佳。)
SELECT o.e_id
, (SELECT MAX(i.date_time)
FROM e `i`
WHERE i.in_out = 'I'
AND i.e_id = o.e_id
AND i.date_time < o.date_time
) AS in_time
, o.date_time AS out_time
FROM e `o`
WHERE o.in_out = 'O'
ORDER BY o.date_time
用户变量
另一种方法是使用MySQL用户变量。 (这是一种特定于MySQL的方法,是“缺失”分析函数的一种解决方法。)
此查询的作用是按e_id排序所有行,然后按date_time排序,以便我们按顺序处理它们。每当我们遇到'O'(out)行时,我们使用前一行'I'行中date_time的值作为'in_time')
N.B。:MySQL用户变量的这种用法取决于MySQL以特定顺序执行操作,这是一个可预测的计划。内联视图(或MySQL派生词中的“派生表”)的使用为我们提供了可预测的执行计划。但是这种行为在未来的MySQL版本中可能会发生变化。
SELECT c.e_id
, CAST(c.in_time AS DATETIME) AS in_time
, c.out_time
FROM (
SELECT IF(@prev_e_id = d.e_id,@in_time,@in_time:=NULL) AS reset_in_time
, @in_time := IF(d.in_out = 'I',d.date_time,@in_time) AS in_time
, IF(d.in_out = 'O',d.date_time,NULL) AS out_time
, @prev_e_id := d.e_id AS e_id
FROM (
SELECT e_id, date_time, in_out
FROM e
JOIN (SELECT @prev_e_id := NULL, @in_time := NULL) f
ORDER BY e_id, date_time, in_out
) d
) c
WHERE c.out_time IS NOT NULL
ORDER BY c.out_time
这适用于您拥有的数据集,它需要更彻底的测试和调整,以确保您获得所需的结果集,当行未完美配对时(例如,两个'O'行没有'我在它们之间划线,一个'I'行,后面没有'O'行等等。)
答案 1 :(得分:2)
不幸的是,MySQL没有SQL Server这样的ROW_NUMBER() OVER(PARTITION BY ORDER BY()
功能,或者这非常容易。
但是,有一种方法可以在MySQL中执行此操作:
set @num := 0, @in_out := '';
select emp_in.id,
emp_in.in_time,
emp_out.out_time
from
(
select id, in_out, date_time in_time,
@num := if(@in_out = in_out, @num + 1, 1) as row_number,
@in_out := in_out as dummy
from mytable
where in_out = 'I'
order by date_time, id
) emp_in
join
(
select id, in_out, date_time out_time,
@num := if(@in_out = in_out, @num + 1, 1) as row_number,
@in_out := in_out as dummy
from mytable
where in_out = 'O'
order by date_time, id
) emp_out
on emp_in.id = emp_out.id
and emp_in.row_number = emp_out.row_number
order by emp_in.id, emp_in.in_time
基本上,这会创建两个子查询,每个子查询为该特定记录生成一个row_number - 一个子查询用于in_time,另一个子查询用于out_time。
然后,您JOIN
和emp_id
row_number
两个查询