Calculate time difference for panel data in mysql - 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;

Related

Query to lookup reference tables on sum the result

I am new to SQL, would like to have your suggestions on how to solve this problem,
I have the sales information by type
I want to sum the Prices of certain references by Type and based on the resulting sum, fetch the values from another table and populate in the Output Column.
Group Type 100000 200000 300000
1 A 1 2 3
1 B 0 1 1
2 T 2 2 4
2 U 0 2 2
3 V 2 2 3
4 N 1 1 1
From the above table 2 we find the TYPE A and B belong to same group - Group 1. So in the first table, the query should sum Prices of the references belonging to the Group 1. If the sum is >100000 and <=200000 then based on the type the corresponding value must be chosen.
Incase the sum of Prices based on group is less than 100000 or the type not found in Table 2 then it should take the values from the below table
[+------+----+---+
| Type | 1 | 2 |
+------+----+---+
| A | 50 | 2 |
| B | 60 | 5 |
| C | 65 | 2 |
| D | 65 | 3 |
| E | 65 | 4 |
+------+----+---+][3]
Thus the final output for the above datasheet would be like below,
Order ID Reference Type Price Output
101 AAA A 500000 3
101 AAB B 100000 1
101 ABC C 20000 67
101 DCE B 50000 1
101 BOD D 200000 68
101 ZYZ E 200000 69
102 AAA A 20000 52
So for the first line, its TYPE A and Type A is present under Group 1 and in Group1 we also have Type 2. So for the same order ID 101 , the overall Sales of Type A and B is 650000 > 300000, therefore for Type A we chose the value 3 from the table 2. Since Type C is not present in Table 2, I went to Table 3 and added the two values and so on
Sorry for the long post. Hope my question is clear? Would like to have your expert opinion.
Thanks,
SS
Join all tables and make sure you do LEFT JOIN as we want to keep records from the first table even we don't have corresponding data in the second or third table.
For total count, give priority to the second table, use case when to verify in which range this mrp field is falling. If lies within a range pick count from the second table otherwise pick count from the third table.
SELECT
s.order_id,
s.reference,
s.`type`,
s.mrp,
#a:= IFNULL(g_total.Total, s.mrp) AS MRP_Total, -- #a variable to use it in CASE WHEN clause
CASE
WHEN #a > 100000 AND #a <= 200000 AND sg.`type` IS NOT NULL THEN sg.price_100000
WHEN #a > 200000 AND #a <= 300000 AND sg.`type` IS NOT NULL THEN sg.price_200000
WHEN #a > 300000 AND sg.`type` IS NOT NULL THEN sg.price_300000
ELSE tp.price_1 + tp.price_2
END Total
FROM sales s
LEFT JOIN sales_group sg ON s.`type` = sg.`type`
LEFT JOIN type_prices tp ON s.`type` = tp.`type`
LEFT JOIN (
SELECT
s.order_id, sgg.`group`, SUM(mrp) as Total
FROM sales s
INNER JOIN sales_group sgg ON s.`type` = sgg.`type`
GROUP BY s.order_id, sgg.`group`
) AS g_total -- Temp table to find total MRP, order and group wise
ON s.order_id = g_total.order_id AND sg.`group` = g_total.`group`
ORDER BY s.order_id, s.`type`;
Output:
sales
---
| order_id | reference | type | mrp | MRP_Total | Total |
---------------------------------------------------------
| 101 | AAA | A | 500000 | 650000 | 3 |
| 101 | DCE | B | 50000 | 650000 | 1 |
| 101 | AAB | B | 100000 | 650000 | 1 |
| 101 | ABC | C | 200000 | 200000 | 67 |
| 101 | BOD | D | 200000 | 200000 | 68 |
| 101 | ZYZ | E | 200000 | 200000 | 69 |
| 102 | AAA | A | 20000 | 20000 | 52 |
Note: sg.type IS NOT NULL is added in CASE WHEN clause because if we don't have any mapping in the second table, we should move to ELSE part which refers to the third table.

Calculate total spent time exclude pauses

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

Latest datetime from unique mysql index

I have a table. It has a pk of id and an index of [service, check, datetime].
id service check datetime score
---|-------|-------|----------|-----
1 | 1 | 4 |4/03/2009 | 399
2 | 2 | 4 |4/03/2009 | 522
3 | 1 | 5 |4/03/2009 | 244
4 | 2 | 5 |4/03/2009 | 555
5 | 1 | 4 |4/04/2009 | 111
6 | 2 | 4 |4/04/2009 | 322
7 | 1 | 5 |4/05/2009 | 455
8 | 2 | 5 |4/05/2009 | 675
Given a service 2 I need to select the rows for each unique check where it has the max date. So my result would look like this table.
id service check datetime score
---|-------|-------|----------|-----
6 | 2 | 4 |4/04/2009 | 322
8 | 2 | 5 |4/05/2009 | 675
Is there a short query for this? The best I have is this, but it returns too many checks. I just need the unique checks at it's latest datetime.
SELECT * FROM table where service=?;
First you need find out the biggest date for each check
SELECT `check`, MAX(`datetime`)
FROM YourTable
WHERE `service` = 2
GROUP BY `check`
Then join back to get the rest of the data.
SELECT Y.*
FROM YourTable Y
JOIN ( SELECT `check`, MAX(`datetime`) as m_date
FROM YourTable
WHERE `service` = 2
GROUP BY check) as `filter`
ON Y.`service` = `filter`.service
AND Y.`datetime` = `fiter`.m_date
WHERE Y.`service` = 2

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

MySQL subtracting successive rows in same column

My question is similar to this SO post however the difference is that I do not have a sequential ID column.
I have a table in the following form
ID | length
0 | 5
0 | 7
0 | 10
1 | 3
1 | 8
1 | 12
2 | 1
2 | 2
2 | 4
2 | 5
and I want to get the difference between successive rows in the length column grouped by the ID. So it should give
ID | length | difference
0 | 5 | NULL
0 | 7 | 2
0 | 10 | 3
1 | 3 | NULL
1 | 8 | 5
1 | 12 | 4
2 | 1 | NULL
2 | 2 | 1
2 | 4 | 2
2 | 5 | 1
I'm not sure how to go about doing this. I tried giving each ID a separate ID that is sequential, but it turned out to be way to complicated and I could not get it to work. Can someone suggest a better way of doing it?
Assuming (id,length) is UNIQUE...
SELECT x.*
, x.length - MAX(y.length) diff
FROM my_table x
LEFT
JOIN my_table y
ON y.id = x.id
AND y.length < x.length
GROUP
BY x.id
, x.length;
select a.id, a.length, b.length, b.length - a.length as difference
from mytable a, mytable b
where a.id=b.id
and b.length = (select min(length) from mytable where id=a.id and length > a.length)