How do I aggregate from multiple rows - mysql

I have the below table:
Person_ID | Timestamp | Action
1 | 01-01-2020 00:01:00 | LOGGED ON
1 | 01-01-2020 00:02:00 | ON BREAK
1 | 01-01-2020 00:02:30 | OFF BREAK
1 | 01-01-2020 00:04:00 | LOGGED OFF
1 | 01-01-2020 00:04:30 | LOGGED ON
1 | 01-01-2020 00:05:30 | ON BREAK
1 | 01-01-2020 00:06:00 | OFF BREAK
1 | 01-01-2020 00:08:00 | LOGGED OFF
2 | 01-02-2020 00:04:00 | LOGGED ON
2 | 01-02-2020 00:10:00 | LOGGED OFF
My goal is to write an SQL view to get the below result:
Person_ID | LoggedInTime_hours | OnBreakTime_hours
1 | 6.5 | 1
2 | 6 | 0
For each Person_ID, calculate the difference in hours between each pair of occurrences of LOGGED IN/LOGGED OUT and ON BREAK/OFF BREAK respectively.
Thanks a lot for your expertise and help.

1st of all LoggedInTime_hours for PERSON_ID = 1 should be 5.5 hours not 6.5 hours.
Let us assume the table name is TEST.
Database : MySQL
SELECT
PERSON_ID,
IFNULL(SUM(CASE WHEN PREV_ACTIVE_IND - ACTIVE_IND > 0 THEN TIMESTAMPDIFF(SECOND, PREV_TIMESTAMP, TIMESTAMP)/3600 END),0) LoggedInTime_hours,
IFNULL(SUM(CASE WHEN PREV_ACTIVE_IND - ACTIVE_IND = 0 THEN TIMESTAMPDIFF(SECOND, PREV_TIMESTAMP, TIMESTAMP)/3600 END),0) OnBreakTime_hours
FROM
(SELECT
PERSON_ID,
PREV_ACTION,
ACTION,
CASE WHEN PREV_ACTION = 'LOGGED OFF' THEN 1
WHEN PREV_ACTION LIKE '%BREAK' THEN 2
WHEN PREV_ACTION IS NULL THEN NULL
ELSE 3
END AS PREV_ACTIVE_IND,
CASE WHEN ACTION = 'LOGGED OFF' THEN 1
WHEN ACTION LIKE '%BREAK' THEN 2
WHEN ACTION IS NULL THEN NULL
ELSE 3
END AS ACTIVE_IND,
PREV_TIMESTAMP,
TIMESTAMP
FROM
(SELECT PERSON_ID,
TIMESTAMP,
LAG(TIMESTAMP,1) OVER (PARTITION BY PERSON_ID) PREV_TIMESTAMP,
LAG(ACTION,1) OVER (PARTITION BY PERSON_ID) PREV_ACTION,
ACTION
FROM TEST) T) T2
GROUP BY PERSON_ID
Hope this query will help.

Try the following query using MySQL v8.0:
With CTE As
(
Select Person_ID, Action_,
TIME_TO_SEC(
TIMEDIFF(
Lead(Timestamp_) Over (Partition By Person_ID
Order By Case When Action_ In 'LOGGED ON','LOGGED OFF')
Then 1 Else 2 End,Timestamp_), Timestamp_
))/3600 As DIFF
From MyData
)
Select Person_ID,
Sum(Case When Action_='LOGGED ON' Then DIFF Else 0 End) As LoggedInTime_hours,
Sum(Case When Action_='ON BREAK' Then DIFF Else 0 End) As OnBreakTime_hours
From CTE
Group By Person_ID
See a demo from db-fiddle.
The Lead() function will find the next action date related to the current action i.e. (Logged In -> Logged Off), the actions are ordered according to their types; ('LOGGED ON','LOGGED OFF') ordered with 1, and ('ON BREAK','OFF BREAK') ordered with 2, then ordered by Timestamp of the action. Doing so, will ensure that each 'LOGGED ON' is followed by it's 'LOGGED OFF' action, and each 'ON BREAK' is followed by it's 'OFF BREAK' action.
Using TIMEDIFF we can find the difference between the actions times for each pair of actions ('LOGGED ON','LOGGED OFF'), ('ON BREAK','OFF BREAK'). Then the sum of these differences will be LoggedInTime_hours for logged in actions, and OnBreakTime_hours for on break actions.
The TIME_TO_SEC()/3600 is used to convert time differences to hours.

Related

Finding total active hours by calculating difference between TimeDate records

I have a table to register users logs every one minute and other activities using DateTime for each user_id
This is a sample data of my table
id | user_id | log_datetime
------------------------------------------
1 | 1 | 2016-09-25 13:01:08
2 | 1 | 2016-09-25 13:04:08
3 | 1 | 2016-09-25 13:07:08
4 | 1 | 2016-09-25 13:10:08
5 | 2 | 2016-09-25 13:11:08
6 | 1 | 2016-09-25 13:13:08
7 | 2 | 2016-09-25 13:13:09
8 | 2 | 2016-09-25 13:14:10
I would like to calculate the total active time on the system
UPDATE: Expected Output
For Example user_id 1 his total available time should be 00:12:00
Since his hours and seconds are same so I'll just subtract last log from previous then previous from next previous and so on then I'll sum all subtracted values
this a simple for
Simply I want to loop through the data from last record to first record with in my range
this is a simple formula I hope that make my question clear
SUM((T< n > - T< n-1 >) + (T< n-1 > - T< n-2 >) ... + (T< n-x > - T< n-first >))
Since user_id 1 his hours and seconds are the same then I'll calculate the minutes only.
(13-10)+(10-7)+(7-4)+(4-1) = 12
user_id | total_hours
---------------------------------
1 | 00:12:00
2 | 00:03:02
I did this code
SET #start_date = '2016-09-25';
SET #start_time = '13:00:00';
SET #end_date = '2016-09-25';
SET #end_time = '13:15:00';
SELECT
`ul1`.`user_id`, SEC_TO_TIME(SUM(TIME_TO_SEC(`dl1`.`log_datetime`))) AS total_hours
FROM
`users_logs` AS `ul1`
JOIN `users_logs` AS `ul2`
ON `ul1`.`id` = `ul2`.`id`
WHERE
`ul1`.`log_datetime` >= CONCAT(#start_date, ' ', #start_time)
AND
`ul2`.`log_datetime` <= CONCAT(#end_date, ' ', #end_time)
GROUP BY `ul1`.`user_id`
But this code Sum all Time not getting the difference. This is the output of the code
user_id | total_hours
---------------------------------
1 | 65:35:40
2 | 39:38:25
How can I calculate the Sum of all difference datetime, then I want to display his active hours every 12 hours (00:00:00 - 11:59:59) and (12:00:00 - 23:59:59) with in selected DateTime Period at the beginning of the code
So the output would look like this (just an dummy example not from given data)
user_id | total_hours | 00_12_am | 12_00_pm |
-------------------------------------------------------
1 | 10:10:40 | 02:05:20 | 08:05:20 |
2 | 04:10:20 | 01:05:10 | 03:05:30 |
Thank you
So you log every minute and if a user is available there is a log entry.
Then count the logs per user, so you have the number of total minutes.
select user_id, count(*) as total_minutes
from user_logs
group by user_id;
If you want them displayed as time use sec_to_time:
select user_id, sec_to_time(count(*) * 60) as total_hours
from user_logs
group by user_id;
As to conditional aggregation:
select
user_id,
count(*) as total_minutes,
count(case when hour(log_datetime) < 12 then 1 end) as total_minutes_am,
count(case when hour(log_datetime) >= 12 then 1 end) as total_minutes_pm
from user_logs
group by user_id;
UPDATE: In order to count each minute just once count distinct minutes, i.e. DATE_FORMAT(log_datetime, '%Y-%m-%d %H:%i'). This can be done with COUNT(DISTINCT ...) or with a subquery getting distinct values.
The complete query:
select
user_id,
count(*) as total_minutes,
count(case when log_hour < 12 then 1 end) as total_minutes_am,
count(case when log_hour >= 12 then 1 end) as total_minutes_pm
from
(
select distinct
user_id,
date_format(log_datetime, '%y-%m-%d %h:%i') as log_moment,
hour(log_datetime) as log_hour
from.user_logs
) log
group by user_id;

Rows to columns in mysql

I'm doing the select:
select id,
status,
count(status) as qtd
from user
group by id, status;
The return is:
id | status | qtd
1 YES 5
1 NO 3
2 YES 3
2 NO 1
I want this:
id | YES | NO
1 5 3
2 3 1
Thanks.
NOTE:
you can use case logic to do what you want.. basically you want to pivot the results and to pivot them you have to use aggregates with conditionals to fake a pivot table since mysql doesn't have a way to accomplish that
QUERY:
SELECT
id,
SUM(CASE status WHEN 'Yes' THEN 1 ELSE 0 END) as 'YES',
SUM(CASE status WHEN 'No' THEN 1 ELSE 0 END) as 'NO'
FROM user
GROUP BY id;
DEMO
OUTPUT:
+----+-----+----+
| id | YES | NO |
+----+-----+----+
| 1 | 5 | 3 |
| 2 | 3 | 1 |
+----+-----+----+
You can do so,using expression in sum() like sum(status ='Yes') will result as boolean (0/1) and thus you can have your count based on your criteria you provide in sum function
select id,
sum(status ='Yes') as `YES`,
sum(status ='No') as `NO`
from user
group by id;

How to put empty set in inner join

I am a complete beginner in MySql and I wanted you to help me. I want a DTR User Interface where there is time in and time out. So I have a fields like -id_biometrics,empno,datecreated,time_created,status,device the problem is if the employee forgot to time in or time out, I want to put the time in or time out to "NULL" here is what I coded.
SELECT start_log.empno AS "Employee Number", start_log.date_created, start_log.time_created AS "Time In", end_log.time_created AS "Time Out"
FROM biometrics AS start_log
INNER JOIN biometrics AS end_log ON start_log.empno = end_log.empno
WHERE start_log.status =0
AND end_log.status =1
AND start_log.empno =2
GROUP BY start_log.date_created, start_log.empno
And what my output here if he completes his time in and time out for the day is.
Employee Number|date_created |Time_in | Time_Out
2 | 2013-07-15 | 11:08:07 | 15:00:00
And if he/she forgots to Time Out or time in it shows MySQL returned an empty result set (i.e. zero rows). ( Query took 0.0016 sec )
What I wanted is
Employee Number | date_created |Time_in | Time_Out
3 | 2013-07-15 | 11:50:00 | Null
Please help me with this. I needed it badly thanks.
Are you looking for this?
SELECT empno, date_created,
MIN(CASE WHEN status = 0 THEN time_created END) time_in,
MIN(CASE WHEN status = 1 THEN time_created END) time_out
FROM biometrics
GROUP BY empno, date_created
Sample output:
+-------+--------------+----------+----------+
| empno | date_created | time_in | time_out |
+-------+--------------+----------+----------+
| 2 | 2013-07-15 | 11:08:07 | 15:00:00 |
| 3 | 2013-07-15 | 11:50:00 | NULL |
| 4 | 2013-07-15 | NULL | 16:00:00 |
+-------+--------------+----------+----------+
Here is SQLFiddle demo
UPDATE If you may have multiple in or out records and you always want to fetch first in and last out then just use MAX() aggregate for time_out
SELECT empno, date_created,
MIN(CASE WHEN status = 0 THEN time_created END) time_in,
MAX(CASE WHEN status = 1 THEN time_created END) time_out
FROM biometrics
GROUP BY empno, date_created
Here is SQLFiddle demo

combining multiple different sql into one

cusID | Name | status | Date
---------------------------------
1 | AA | 0 | 2013-01-25
2 | BB | 1 | 2013-01-23
3 | CC | 1 | 2013-01-20
SELECT COUNT(cusID) FROM customer WHERE STATUS=0;
SELECT COUNT(cusID) FROM customer WHERE STATUS=1;
Is there a way of combing such two sql and return the results as one. Because want to avoid calling to DB everytime. I tried UNION of two statments, but only showing one result.
This is the shortest possible solution in MySQL.
SELECT SUM(status = 1) totalActive,
SUM(status = 0) totalInactive
FROM tableName
SQLFiddle Demo
and this is the CASE version
SELECT SUM(CASE WHEN status = 1 THEN 1 ELSE 0 END) totalActive,
SUM(CASE WHEN status = 0 THEN 1 ELSE 0 END) totalInactive
FROM tableName
SQLFiddle Demo

group by date, confirm, pending

im trying to build chart for users for each day with total confirmed user and total pending users for that each day and i need data like following:
date | confirmed | pending
2010-01-05 | 5 | 2
2010-01-06 | 10 | 3
following is the structure of the table:
user_id
username
confirm enum(0,1)
date date
SELECT Date, SUM(CASE Confirm WHEN 1 THEN 1 ELSE 0 END) Confirmed,
SUM(CASE Confirm WHEN 0 THEN 1 ELSE 0 END) Pending,
FROM Table
GROUP BY Date