I have a MySQL table where employee login and logout timings are recorded. Here in the in-out column 1-represents login and 0-represents logout.
[id] [User_id] [Date_time] [in_out]
1 1 2011-01-20 18:01:03 1
2 1 2011-01-20 19:30:43 0
3 1 2011-01-20 20:46:23 1
4 1 2011-01-21 00:42:45 0
Is it possible to retrieve total hours worked in a day (between 2 days) by a user using single query?
The same Question it's a copy of Get total hours worked in a day mysql and solution:
SELECT `User_id`, time(sum(`Date_time`*(1-2*`in_out`)))
FROM `whatever_table` GROUP BY `User_id`;
But the solution needs to be different when the employee start working in a day and go out on the next day.
You can achieve this using given stored procedure. Consider your table name EventLog.
DELIMITER $$
CREATE PROCEDURE `GET_TOTAL_LOGIN_TIME`(
IN startDate DATETIME,
IN endDate DATETIME,
IN userId INT(11)
)
BEGIN
select
(sum(
case when(e2.Date_time <= startDate) then 0 else
case when(e1.Date_time >= endDate) then 0 else
case when(e1.Date_time >= startDate && e2.Date_time <= endDate) then
TIME_TO_SEC(TIMEDIFF(e2.Date_time, e1.Date_time))/60 else
case when(e1.Date_time <= startDate && e2.Date_time <= endDate) then
TIME_TO_SEC(TIMEDIFF(e2.Date_time, startDate))/60 else
case when(e1.Date_time >= startDate && e2.Date_time >= endDate) then
TIME_TO_SEC(TIMEDIFF(endDate,e1.Date_time))/60
end end end end end
)) as loginTimeInMin
from
((EventLog e1
left join EventLog e2 ON (((e1.User_id = e2.User_id) and (e2.in_out = 0) and (e1.Date_time < e2.Date_time))))
left join EventLog e3 ON (((e1.User_id = e3.User_id) and (e1.Date_time < e3.Date_time) and (e3.Date_time < e2.Date_time))))
where
((e1.in_out = 1) and isnull(e3.Date_time)) and e2.Date_time is not null
AND e1.User_id = userId
AND userRole.userRoleId = roleId
AND userRole.userLoginId = userId
group by e1.User_id;
END;
You can get the number of seconds worked like this:
SELECT `User_id`, sum(unix_timestamp(`Date_time`)*(1-2*`in_out`))
FROM `whatever_table`
GROUP BY `User_id`;
Then you can convert the seconds to whatever you want.
Related
I am writing queries for some KPIs (Key Performance Indicators) to track user engagement. One such KPI is "Churn Rate", which I am calculating for a given month by:
Churn rate = (Total users deleted in month)/(Total users on the 1st of month)
I am using a users table with the following columns:
created_at, deleted_at
My process is to get all relevant months of user activity (in this case, based on "created_at" column, since we are getting several new users per month. We also have an activity log table which might technically be more accurate to use but doesn't go back as far) and then loop over them in a stored procedure. For each month, I'm calculating who was deleted that month and who was active on the first of that month (created on or before the 1st of the month and either not deleted or deleted after the first of that month). Then I'm dividing them to find churn rate and inserting into a temporary table. Here is my stored procedure:
DROP PROCEDURE ChurnRate;
DELIMITER $$
CREATE PROCEDURE ChurnRate()
BEGIN
DECLARE start_date DATETIME;
DECLARE end_date DATETIME;
DECLARE cur_date DATETIME;
DECLARE current_month VARCHAR(255);
DECLARE end_month VARCHAR(255);
DECLARE deleted_count BIGINT;
DECLARE active_user_count BIGINT;
DECLARE churn_rate FLOAT;
SELECT created_at FROM users ORDER BY created_at ASC LIMIT 1 INTO start_date;
SELECT created_at FROM users ORDER BY created_at DESC LIMIT 1 INTO end_date;
SET cur_date = start_date;
SET current_month = SUBSTR(cur_date,1,7);
SET end_month = SUBSTR(end_date,1,7);
DROP TEMPORARY TABLE IF EXISTS churn_table;
CREATE TEMPORARY TABLE churn_table
(
user_month VARCHAR(255),
deleted_count BIGINT,
active_user_count BIGINT,
churn_rate FLOAT
);
loop_label: LOOP
SELECT COUNT(U.id) FROM users AS U WHERE SUBSTR(U.deleted_at,1,7) = current_month INTO deleted_count;
SELECT COUNT(U.id) FROM users AS U
WHERE (U.deleted_at >= DATE_ADD(DATE_ADD(LAST_DAY(cur_date),INTERVAL 1 DAY),INTERVAL -1 MONTH) OR U.deleted_at IS NULL)
AND SUBSTR(U.created_at,1,7) <= current_month
INTO active_user_count;
INSERT INTO churn_table (user_month, deleted_count, active_user_count, churn_rate) VALUES (current_month, deleted_count, active_user_count, (deleted_count/active_user_count));
SET cur_date = DATE_ADD(cur_date, INTERVAL 1 MONTH);
SET current_month = SUBSTR(cur_date,1,7);
IF current_month <= end_month THEN
ITERATE loop_label;
END IF;
LEAVE loop_label;
END LOOP;
SELECT * FROM churn_table;
END$$
DELIMITER ;
CALL ChurnRate();
Here is a sample of some data that was produced:
user_month
churn_rate_percentage
2019-12
0
2020-01
0.0396982
2020-02
0
2020-03
0
2020-04
0
2020-05
0.112116
2020-06
0.59691
2020-07
0.26689
2020-08
0.144374
2020-09
0.141767
2020-10
0.125
2020-11
0.272904
2020-12
0.14937
My problem is this: I am using an API that requires this to be a select query. I have previously tried writing select queries for this, but they have been flawed. Grouping by "deleted_at" will not work because we will not show months for which no users have been deleted. Grouping by "created_at" and using subqueries ends up being extremely slow, as we have about 50k users. Is there a clean, efficient way to write this as a select query without affecting performance?
If there is not, I will have to write a chron to run this procedure and export the data.
Thank you
You shouldn't use loops in SQL that is often an indication you are doing something wrong.
Here is how to do this in a single query:
-- recursive CTE to create list of months of interest
with RECURSIVE base_months(d,y,m) AS
(
SELECT DateSerial(Year(min(create_at)), Month(min(create_at)), "1"),
min(create_at) , year(min(create_at)) , month(min(create_at))
FROM users
UNION ALL
SELECT data_add(d INTERVAL 1 MONTH) , year(data_add(d INTERVAL 1 MONTH)) , month(data_add(d INTERVAL 1 MONTH))
FROM base_months
WHERE YEAR(d) <= YEAR(CURDATE()) && MONTH(d) <= MONTH(CURDATE())
)
select
b.y as year,
b.m as month,
count(u.created_at) as total_user
sum(case when month(u.deleted_at) = b.m and year(u.deleted_at = b.y) then 1 else 0 end) as left_this_month
from base_months b
-- for each month join to the users table
join user u on u.created_at < b.d and (u.deleted_at > b.d or u.deleted_at is null)
group by b.y, b.m
If this isn't clear, first we use a recursive CTE to get all the months and years of interest -- you could do a non-recursive query on the table with a group by if only want to include create date months that are in the table -- but I think that would give you interesting results since months that don't have anyone created in that month would not be included.
Then I join that back to the users table with filters on the join to only include the rows we want to count for the given year and month. We use group by and aggregation functions to find the results.
Looping is likely to be terribly slow.
Is this how you decide if a user exists on Nov 1, 2020?
WHERE created_at < '2020-11'
AND deleted_at > '2020-11'
Hence, a COUNT(*) with that test would give that count?
For deletions for that month:
WHERE LEFT(deleted_at, 7) = '2020-11'
Putting those together into a single query or all months:
SELECT LEFT(created_at, 7) AS yyyymm,
( SELECT COUNT(*)
FROM users
WHERE created_at < yyyymm
AND deleted_at > yyyymm
) AS new_users,
( SELECT COUNT(*)
FROM users
WHERE deleted_at >= yyyymm
AND deleted_at < CONCAT(yyyymm, '-01')
) AS deleted_users
FROM users
GROUP BY yyyymm
ORDER BY yyyymm
That gives you 3 columns; check it out. To get the churn:
SELECT LEFT(created_at, 7) AS yyyymm,
( SELECT ... ) / ( SELECT ... ) AS churn
FROM users
GROUP BY yyyymm
ORDER BY yyyymm
I'm trying to find to which shift belongs a datetime field.
Shifts are defines as time, and I have a startingHour and endingHour.
The query
SELECT * FROM shifts WHERE TIME('2009-11-20 06:35:00') BETWEEN '06:00:00' and '19:00:00'
works perfect, but when the shift is set to start 19:00:00 to 06:00:00 and the time is 23:35:00 it doesn't return anything
WHERE TIME('2009-11-20 23:35:00') BETWEEN '19:00:00' and '06:00:00'
that line isn't returning anything though I do have records on the table
Thanks
That's the shifts table.
if I query this:
SELECT
a.ID,
b.Nombre
FROM turnos a
JOIN operarios b ON a.oID = b.oId
WHERE a.uId = 1
AND (TIME('2019-11-22 18:23:00') BETWEEN a.horaInicio AND a.horaFin )
LIMIT 1
I get the proper result, but when I query this:
SELECT
a.ID,
b.Nombre
FROM turnos a
JOIN operarios b ON a.oID = b.oId
WHERE a.uId = 1
AND (TIME('2019-11-22 02:45:00') BETWEEN a.horaInicio AND a.horaFin )
LIMIT 1
I get no result.
These are two cases: start time < end time and start time > end time. You need something like this:
where (start_time < end_time and $t >= start_time and $t < end_time)
or (start_time >= end_time and ($t < start_time or $t >= end_time))
Since '19:00:00' is greater than '06:00:00' then:
BETWEEN '19:00:00' and '06:00:00'
returns 0 (false) and you get no rows.
One way to get the results that you want is to use CASE like this:
.................
AND 1 = CASE
WHEN a.horaInicio <= a.horaFin THEN TIME('2019-11-22 02:45:00') BETWEEN a.horaInicio AND a.horaFin
ELSE (TIME('2019-11-22 02:45:00') BETWEEN a.horaInicio AND '23:59:59')
OR (TIME('2019-11-22 02:45:00') BETWEEN '00:00:00' AND a.horaFin)
END
i have to table 2 in same structure .i already get the result by using one table using following query
SELECT *,COUNT(Time),
CASE WHEN COUNT(Time) = 1
THEN CASE WHEN Time > '00:00:00' && Time <= '12:15:00'
THEN Time END ELSE MIN(Time)
END as time_in,
CASE WHEN COUNT(Time) = 1
THEN CASE WHEN Time > '12:15:00' && Time <= '23:59:59'
THEN Time END ELSE NULLIF(MAX(Time), MIN(Time))
END as time_out
FROM attendancedata
INNER JOIN nonacadamic
ON attendancedata.EnrolledID = nonacadamic.emp_id
WHERE nonacadamic.emp_id = '".$_POST["emp_id"]."' AND Date LIKE '$currentMonth%'
GROUP BY EnrolledID,Date
this query will time devide in to the 2 part(time in and time out) .it work fine.now want to get the data from anther table also.it also have same structure attendancedata table.
attendancedata table structure
EnrolledID Date Time
23 2019-09-09 16:19:00
53 2019-08-27 08:19:00
tempattendancedata table structure
EnrolledID Date Time
23 2019-09-09 16:19:00
23 2019-09-20 08:19:00
i want get the result consider above table record and then split time in to the two part .how can i do this task? actual requirement is tempattendancedata table data also need considering for the time split
you include the results of your temp table using UNION ALL then, put it in a subquery for your select case statement
SELECT *,COUNT(Time)
,CASE WHEN COUNT(Time) = 1 THEN
CASE WHEN Time > '00:00:00' && Time <= '12:15:00'
THEN Time END ELSE MIN(Time)
END as time_in,
CASE WHEN COUNT(Time) = 1 THEN
CASE WHEN Time > '12:15:00' && Time <= '23:59:59'
THEN Time END ELSE NULLIF(MAX(Time), MIN(Time))
END as time_out
FROM
(SELECT *
FROM attendancedata
INNER JOIN nonacadamic
ON attendancedata.EnrolledID = nonacadamic.emp_id
WHERE nonacadamic.emp_id = '".$_POST["emp_id"]."' AND Date LIKE '$currentMonth%'
UNION ALL
SELECT *
FROM tempattendancedata
INNER JOIN nonacadamic
ON attendancedata.EnrolledID = nonacadamic.emp_id
WHERE nonacadamic.emp_id = '".$_POST["emp_id"]."' AND Date LIKE '$currentMonth%') t1
GROUP BY t1.EnrolledID, t1.Date
I need to bring to the customer a discount depending on how long it took a training course.
To find out the date of passage must first know the group ID to which the client belongs.
When I know Id of group, I can find date when started training course.
And then, determined the size of discounts by finded date
Here's a script that works great in MS SQL, but in MySql it doesn't works.
CREATE DEFINER=`root`#`localhost` PROCEDURE `GetBonus`(
idStudent CHAR(36)
)
BEGIN
IF (SELECT COUNT(*) FROM (SELECT IdGroupe FROM GroupeStudent WHERE IdStudent = idStudent) groupeCourse) = 1 THEN
BEGIN
DECLARE CourseGroupeDate datetime;
SET CourseGroupeDate = (SELECT Date FROM CourseGroupe WHERE Id = (SELECT IdGroupe FROM GroupeStudent WHERE IdStudent = idStudent));
IF (CourseGroupeDate - INTERVAL NOW() MONTH) < 3 AND
(CourseGroupeDate - INTERVAL NOW() MONTH) > 0
THEN SELECT * FROM Discounts WHERE Id = '00000000-0000-0000-0000-000000000000';
ELSEIF (CourseGroupeDate - INTERVAL NOW() MONTH) < 6 AND
(CourseGroupeDate - INTERVAL NOW() MONTH) > 3
THEN SELECT * FROM Discounts WHERE Id = '00000000-0000-0000-0000-000000000001';
ELSEIF (CourseGroupeDate - INTERVAL NOW() MONTH) < 12 AND
(CourseGroupeDate - INTERVAL NOW() MONTH) > 6
THEN SELECT * FROM Discounts WHERE Id = '00000000-0000-0000-0000-000000000002';
END IF;
END;
END IF;
END
CREATE PROCEDURE `GetBonus`(
idStudent CHAR(36)
)
BEGIN
IF (SELECT COUNT(groupeCourse.IdGroupe) FROM (SELECT IdGroupe FROM GroupeStudent WHERE GroupeStudent.IdStudent = idStudent) groupeCourse) = 1 THEN
BEGIN
SET #CourseGroupeDate := (SELECT Date FROM CourseGroupe WHERE Id = (SELECT GroupeStudent.IdGroupe FROM GroupeStudent WHERE GroupeStudent.IdStudent = idStudent));
IF (TIMESTAMPDIFF(MONTH, #CourseGroupeDate, now())) < 3 AND
(TIMESTAMPDIFF(MONTH, #CourseGroupeDate, now())) > 0
THEN SELECT * FROM Discounts WHERE Id = '00000000-0000-0000-0000-000000000000';
ELSEIF (TIMESTAMPDIFF(MONTH, #CourseGroupeDate, now())) < 6 AND
(TIMESTAMPDIFF(MONTH, #CourseGroupeDate, now())) > 3
THEN SELECT * FROM Discounts WHERE Id = '00000000-0000-0000-0000-000000000001';
ELSEIF (TIMESTAMPDIFF(MONTH, #CourseGroupeDate, now())) < 12 AND
(TIMESTAMPDIFF(MONTH, #CourseGroupeDate, now())) > 6
THEN SELECT * FROM Discounts WHERE Id = '00000000-0000-0000-0000-000000000002';
END IF;
END;
END IF;
END
All in the details , for example , it was necessary to specify the table name about the field GroupeStudent.IdStudent = idStudent
I have check room is exists between two time for particular date.
I have try following two query its run some time rights but, when i select 10:00 AM to 12:00 PM at time wrong results means not return any records.
QUERY-1 :
SELECT 1 FROM `timetable_details` WHERE (
((`td_from` <= '10:00:00') AND (`td_to` > '10:00:00'))
OR
((`td_from` < '12:20:00') AND (`td_to` >= '12:20:00'))
) AND ((`td_room`='1') AND (`td_date`='2016-01-25'))
QUERY-2 :
SELECT 1 FROM `timetable_details` WHERE (
(`td_from` > '07:00:00') AND (`td_to` < '08:00:00')
) AND ((`td_room`='1') AND (`td_date`='2016-01-25'))
I have get td_id = 4 number row but is not returns.
You can use between with OR condition for both columns as below :
SELECT 1 FROM `timetable_details` WHERE (((((`td_from` BETWEEN '10:00:00' AND '12:30:00') OR (`td_to` BETWEEN '10:00:00' AND '12:30:00')) AND ((`td_room`='1') AND (`td_date`='2016-01-25') AND (`td_status` IS NULL))) AND (`td_from` <> '12:30:00')) AND (`td_to` <> '10:00:00'))