Calculate total spent time exclude pauses - mysql

I have kind of log table. It hold records with tasks, status and time when triggered:
Table tblwork:
+-------------+------------+---------------------+-----+
| task | status | stime | id |
+-------------+------------+---------------------+-----+
| A | 1 | 2018-03-07 20:00:00 | 1 |
| A | 2 | 2018-03-07 20:30:00 | 2 |
| A | 1 | 2018-03-07 21:00:00 | 3 |
| A | 3 | 2018-03-07 21:30:00 | 4 |
+-------------+------------+---------------------+-----+
Status 1 means start, 2 - pause, 3 - end.
So far I try something like this:
SELECT x1.stime, SUM(TIMEDIFF(x2.stime, x1.stime))
FROM tblwork AS x1
LEFT JOIN tblwork AS x2
ON x1.id = x2.id + 1
WHERE x1.`status` = 1 OR x1.`status` = 3
But this gave result -6.000?!?!
I need to calculate total time spent for task but to exclude pause. So the final result should be 01:00:00. Is it possible to do that on this way or should I change table and logic?
UPDATE : SOLUTION
I think I found right way to do exact what I want:
SELECT id, stime,
SUM(TIMESTAMPDIFF(SECOND,
(SELECT MAX(stime) FROM tblwork WHERE stime < t.stime),
stime
)) AS TotalTime
FROM tblwork as t
where (t.status = 1 or t.status = 3)

Looking to your data you should join only the x1.status = 1 and left join the x2.status=2 or 3
SELECT x1.stime, SUM(TIMEDIFF(x2.stime, x1.stime))
FROM tblwork AS x1
LEFT JOIN tblwork AS x2 ON x1.id = x2.id + 1
AND (x2.status = '2' OR x2.status = '3')
WHERE x1.`status` = 1

Related

Question about Self Joins. Seeing if two users interacted

I was given a SQL assignment that is the following table simulating a dating app: Table name = dating
user_id | viewing_profile_id | date | liked
1 2 x yes
1 3 x yes
2 1 x yes
2 3 x no
3 1 x no
3 2 x no
essentially I want to see the % of profiles that match each other. a match is when both profiles like each other.
I think I know what I have to do. Perform a Self join onto the table itself and use a case when for when the profiles like each other.
select t.date, sum(t.match) / count(t.match) as p_match
from (
select s1.user_id, s1.viewing_profile_id,
date_trunc('day',s1.date) as date, case when s1.liked = 'yes'
and s2.liked = 'yes' then 1 else 0 end as match
from dating s1
left join dating s2 on s2.ser_id = s1.viewing_profile_id
group by 1,2) t
group by 1
Since this is just a made up table, and I do not have any data execute this on, I was wondering if I can get some insight into whether or not this would work.
I am expecting the subquery to produce something like the following:
user_id | viewing_profile_id | date | match
1 2 x 1
1 3 x 0
2 1 x 1
2 3 x 0
3 1 x 0
3 2 x 0
You need to left self join the table on the conditions you have:
select
d1.*,
case when d2.user_id is null then 0 else 1 end `match`
from dating d1 left join dating d2
on
d1.user_id = d2.viewing_profile_id
and d1.viewing_profile_id = d2.user_id
and d1.liked = 'yes' and d2.liked = 'yes'
order by d1.user_id, d1.viewing_profile_id
See the demo.
Results:
| user_id | viewing_profile_id | date | liked | match |
| ------- | ------------------ | ---- | ----- | ----- |
| 1 | 2 | | yes | 1 |
| 1 | 3 | | yes | 0 |
| 2 | 1 | | yes | 1 |
| 2 | 3 | | no | 0 |
| 3 | 1 | | no | 0 |
| 3 | 2 | | no | 0 |
/* for matched users*/
SELECT *
FROM user viewed, user viewing
WHERE viewed.user_id = viewing.viewing_profile_id
and viewed.liked='yes'
and viewing.liked='yes'
/* for Match_Percentage */
SELECT
(SELECT count(*)
FROM user viewed, user viewing
WHERE viewed.user_id = viewing.viewing_profile_id
and viewed.liked='yes'
and viewing.liked='yes')
/
(SELECT count(*) FROM user)
*100 as Match_Percentage
FROM dual;
If you just want a flag specifying whether the reverse matches, then:
select d.*,
(exists (select 1
from dating d2
where d2.user_id = d.viewing_profile_id and
d2.viewing_profile_id = d.user_id and
d2.liked = 'yes'
) and
d.liked = 'yes'
) as match
from dating d;

selecting only newest row with specific value

Table:
person | borrow_date | is_borrowed | SN | date | id
1 | 2019-01-10...| 1 | 20 |2019-01-10...| 6
3 | 2019-01-09...| 3 | 10 |2019-01-09...| 5
1 | 2019-01-08...| 1 | 10 |2019-01-08...| 4
2 | 2019-01-08...| 1 | 10 |2019-01-08...| 3
1 | NULL | 2 | 20 |2019-01-07...| 2
1 | NULL | 2 | 10 |2019-01-07...| 1
My wanted output is to select newest rows where "is_borrowed" equals 1 and grouped by SN, so that when the query is executed with person=2 or person=3 then it would retrieve empty set. Whereas for person=1 it would give back two rows.
Wanted output (where person=1):
person | borrow_date | is_borrowed | SN | date |id
1 | 2019-01-10...| 1 | 20 | 2019-01-10...|6
1 | 2019-01-08...| 1 | 10 | 2019-01-08...|4
Wanted output (where person=2):
EMPTY SET
Wanted output (where person=3):
EMPTY SET
This is my current query and it sadly doesn't work.
SELECT a.SN, a.is_borrowed,a.max(date) as date, a.person
FROM table a
INNER JOIN (SELECT SN, MAX(date) as date, osoba from table where person like
"2" group by SN) as b
ON a.SN=b.SN and a.date=b.date
WHERE a.person like "2" and a.is_borrowed=1
If I correctly understood you from the question and the comment you made under it, here's one way to do it without specifying the person:
select *
from TableName as p
inner join (select max(borrow_date) as borrow_date,
SN
FROM TableName
where is_borrowed = 1
group by SN) as p2
on p.borrow_date = p2.borrow_date and p.SN = p2.SN
This should give you the result you're looking for. Here's a demo.
Note that I had to change the borrowed_date values in the table since yours contain hours and minutes while I didn't add those.
You can always specify it for each person by adding a where clause after the join.
select p.person,
p.borrow_date,
p.is_borrowed,
p.SN,
p.date,
p.id
from TableName as p
inner join (select max(borrow_date) as borrow_date,
SN
FROM TableName
where is_borrowed = 1
group by SN) as p2
on p.borrow_date = p2.borrow_date and p.SN = p2.SN
where p.person = '1'
Output:
person | borrow_date | is_borrowed | SN | date | id
1 | 2019-01-10 | 1 | 20 | 2019-01-10 | 6
1 | 2019-01-08 | 1 | 10 | 2019-01-08 | 4
While where p.person = '2' and where p.person = '3' will return empty sets.

Calculating difference on datetime row betwen rows on the same table

I have table that holds records with tasks, status and time when triggered:
Table tblwork:
+-------------+------------+---------------------+-----+
| task | status | stime | id |
+-------------+------------+---------------------+-----+
| A | 1 | 2018-03-07 20:00:00 | 1 |
| A | 2 | 2018-03-07 20:30:00 | 2 |
| A | 1 | 2018-03-07 21:00:00 | 3 |
| A | 3 | 2018-03-07 21:30:00 | 4 |
| B | 1 | 2018-03-07 22:30:00 | 5 |
| B | 3 | 2018-03-07 23:30:00 | 6 |
+-------------+------------+---------------------+-----+
Status 1 means start, 2 - pause, 3 - end
Then I need to calculate how much time is spent for each task excluding pause (status = 2). This is how I do it:
SELECT t1.id, t1.task,
SUM(timestampdiff(second,IFNULL(
(SELECT MAX(t2.stime) FROM tblwork t2 WHERE t2.task='B' AND t2.stime< t1.stime) ,t1.stime),t1.stime)) myTimeDiffSeconds
FROM tblwork t1
WHERE t1.task='B' and (t1.status = 1 or t1.status = 3);
Now I want to get table for all tasks
SELECT t1.id, t1.task,
SUM(timestampdiff(second,IFNULL(
(SELECT MAX(t2.stime) FROM tblwork t2 WHERE t2.stime< t1.stime) ,t1.stime),t1.stime)) myTimeDiffSeconds
FROM tblwork t1
WHERE (t1.status = 1 or t1.status = 3) GROUP BY t1.taks
I get this result:
+-------------+------------+---------------------+
| task | id | mytimedifference |
+-------------+------------+---------------------+
| A | 1 | 3600 |
| B | 3 | 2421217 |
+-------------+------------+---------------------+
Calculation for A is correct B is wrong, it should be 3600 second but i don't understand why.
Assuming there is always a start for each pause and end, wouldn't something like this be more direct?
SELECT t.task
, SUM(TO_SECONDS(t.stime)
* CASE WHEN t.status IN (1) THEN -1
WHEN t.status IN (2, 3) THEN 1
ELSE 0
END
) AS totalTimeSecs
FROM tblwork AS task
GROUP BY t.task
I'm not quite sure offhand how big the values that come out of TO_SECONDS() are for current timestamps; but if they are an issue when being summed, if could be changed to
, SUM((TO_SECONDS(t.stime) - some_constant_just_before_or_at_your_earliest_seconds)
* CASE WHEN t.status IN (1) THEN -1
WHEN t.status IN (2, 3) THEN 1
ELSE 0
END
) AS totalTimeSecs
You can detect "abnormal" data by adding the following to the select expression list
, CASE WHEN SUM(CASE
WHEN t.status IN (1) THEN -1
WHEN t.status IN (2, 3) THEN 1
ELSE 0 END
) = 0
THEN 'OK'
ELSE 'ABNORMAL'
END AS integrityCheck
Note: any "unclosed" intervals will be marked as abnormal; without much more complicated and expensive start and end checking for intervals to differentiate "open" from "invalid", it's probably the best that can be done. The sum used for additonal "integrityCheck" equaling -1 might hint at an open ended interval, but could also indicate an erroneous double-start.

Calculate time difference for panel data in mysql

I have a table (called "Sessions") that looks like this:
user_id | action | datetime
1 | 1 | 2015-12-06 20:15:46
1 | 2 | 2015-12-06 20:15:56
2 | 1 | 2015-12-06 10:01:36
2 | 2 | 2015-12-06 10:01:39
1 | 1 | 2015-12-07 18:17:46
1 | 2 | 2015-12-07 18:17:56
2 | 1 | 2015-12-07 14:03:46
2 | 2 | 2015-12-07 14:03:49
I'd like to use mysql to calculate the number of seconds each user spends on each activity ("duration"), which is the difference between datetime for a given user_id on a given day, getting:
user_id | action | datetime | duration
1 | 1 | 2015-12-06 20:15:46 | 10
1 | 2 | 2015-12-06 20:15:56 | NaN
2 | 1 | 2015-12-06 10:01:36 | 3
2 | 2 | 2015-12-06 10:01:39 | NaN
I can make it work for a series but not the panel. Thank you!
The solution consists in crossing the same table twice, one will correspond to the "first action" that happens and the other will correspond with the "next action". The requisites then can be expressed in the "on" part of the query:
select first_action.user_id,
first_action.action,
first_action.datetime,
(next_action.datetime - first_action.datetime) duration
from
(select * from sessions) as first_action
left outer join (select * from sessions) as next_action
on first_action.user_id = next_action.user_id
and first_action.action + 1 = next_action.action
and date(first_action.datetime) = date(next_action.datetime);
E.g. something like...
SELECT x.user_id
, x.action
, x.datetime start
, y.datetime stop
, TIMEDIFF(y.datetime,x.datetime) duration
FROM my_table x
LEFT
JOIN my_table y
ON y.user_id = x.user_id
AND DATE(y.datetime) = DATE(x.datetime)
AND y.action = 2
WHERE x.action = 1
ORDER
BY user_id
, start;

Joining tables but needs 0 for empty rows

I don't know how to explain the scenario using words. So am writing the examples:
I have a table named tblType:
type_id | type_name
---------------------
1 | abb
2 | cda
3 | edg
4 | hij
5 | klm
And I have another table named tblRequest:
req_id | type_id | user_id | duration
-------------------------------------------
1 | 4 | 1002 | 20
2 | 1 | 1002 | 60
3 | 5 | 1008 | 60
....
So what am trying to do is, fetch the SUM() of duration for each type, for a particular user.
This is what I tried:
SELECT
SUM(r.`duration`) AS `duration`,
t.`type_id`,
t.`type_name`
FROM `tblRequest` AS r
LEFT JOIN `tblType` AS t ON r.`type_id` = t.`type_id`
WHERE r.`user_id` = '1002'
GROUP BY r.`type_id`
It might return something like this:
type_id | type_name | duration
-------------------------------
1 | abb | 60
4 | hij | 20
It works. But the issue is, I want to get 0 as value for other types that doesn't have a row in tblRequest. I mean I want the output to be like this:
type_id | type_name | duration
-------------------------------
1 | abb | 60
2 | cda | 0
3 | edg | 0
4 | hij | 20
5 | klm | 0
I mean it should get the rows of all types, but 0 as value for those type that doesn't have a row in tblRequest
You could perform the aggregation on tblRequest and only then join it, using a left join to handle missing rows and coalesce to convert the nulls to 0s:
SELECT t.type_id, type_name, COALESCE(sum_duration, 0) AS duration
FROM tblType t
LEFT JOIN (SELECT type_id, SUM(duration) AS sum_duration
FROM tblRequest
WHERE user_id = '1002'
GROUP BY type_id) r ON t.type_id = r.type_id
Select a.type_id, isnull(sum(b.duration), 0)
From tblType a Left Outer Join tblRequest b
ON a.type_id = b.type_id and b.user_id = 1002
Group by a.type_id