师兄面试遇到这条 SQL 数据分析题,差点含泪而归!
一、背景
师兄在面试时遇到了这条SQL题,回来我帮他参谋了下,觉得非常有意思,让我们一起来看看这道差点吊打师兄的笔试题吧!🤒
二、派工记录表
例如:
要求7:00,则6:59或者7:00:59 都不算迟到;7:01则视为迟到1分钟
2、行1中“派工结束日期”为null,表示此人的工作结束时间尚未确定,还在搬砖中; 行2中派工结束日期为2020-02-15,表示派工于02-15日结束。
3、假设员工名字不重复,每人只有一条派工信息
建表语句如下:
在员工每次按指纹考勤时 都会生成一条记录
建表语句如下:
create table sign_log ( worker_name varchar(10), sign_time datetime ) insert into sign_log values (张三,2020-02-16 04:01), (张三,2020-02-16 05:02), (张三,2020-02-16 06:03), (王五,2020-02-16 07:03), (王五,2020-02-16 08:03), (王五,2020-02-16 09:03)
四、需求
写一个查询语句,输入参数:日期(date),输出表格如下:
五、思路
但在实际写查询语句时,我发现那样会过于繁琐,变考虑将上述步骤通过 SQL 中自定义函数来实现。先来复习下MySQL中的自定义函数。
自定义函数实例:
先来一个简单的,创建一个函数将2009-06-23 00:00:00这样格式的datetime时间转化为2009年6月23日0时0分0秒这样的格式:
DELIMITER $$ DROP FUNCTION IF EXISTS `sp_test`.`getdate`$$ CREATE FUNCTION `sp_test`.`getdate`(gdate datetime) RETURNS varchar(255) BEGIN DECLARE x VARCHAR(255) DEFAULT ; SET x= date_format(gdate,%Y年%m月%d日%h时%i分%s秒); RETURN x; END $$ DELIMITER ;
解析:
第一句是定义一个结束标识符,因为MySQL默认是以分号作为SQL语句的结束符的,而函数体内部要用到分号,所以会跟默认的SQL结束符发生冲突,所以需要先定义一个其他的符号作为SQL的结束符;
第二句是如果这个函数已经存在了,就删除掉,sp_test是数据库的名字,函数是跟数据库相关联的,getdate是函数的名字;
第三句是创建一个函数,()里是参数的名字和类型,RETURNS 定义这个函数返回值的类型;
函数体必须放在BEGIN END之间;
DECLARE 是定义函数体的变量,这里定义一个变量x,默认是空,然后SET给x变量赋值;
RETURN 是返回值,这里把变量x返回,x的类型必须与第三句中定义的返回类型一致。
调用:
SELECT getdate(2009-06-23 00:00:00);
返回 2009年06月23日00时00分00秒’
六、最终答案
我们先来完成时间处理的自定义函数,代码如下:
DELIMITER $$ DROP FUNCTION IF EXISTS func_date_sub $$ -- d1 6:00 d2 2020-2-16 4:5:0 CREATE FUNCTION func_date_sub(d1 VARCHAR(20),d2 DATETIME) RETURNS INT BEGIN IF d2 IS NULL THEN RETURN -1440; ELSE RETURN CEIL((UNIX_TIMESTAMP(CONCAT(2020-2-16 ,d1))-UNIX_TIMESTAMP(d2))/60); END IF; END $$ DELIMITER ;
SQL 查询语句如下:
select res.worker_name,res.attend,if(res.latetime<0,是,否) as isLate,if( res.latetime<0,abs(res.latetime),0) as latetime from (select ck.worker_name,ck.attend,func_date_sub(ck.sign_time,ck.st) latetime from (select e.worker_name,e.sign_time,2020-02-16 as attend,k.st from (select * from work_plan where datediff(end_date,2020-2-16)>0 or end_date is null) e left join (select worker_name,min(sign_time) st from sign_log group by worker_name) k on e.worker_name = k.worker_name) ck)res;
最终结果还是非常完美的,如果小伙伴有更好的意见,欢迎留言讨论~