Tricky SQL query - need to get time frames [closed] - mysql

It's difficult to tell what is being asked here. This question is ambiguous, vague, incomplete, overly broad, or rhetorical and cannot be reasonably answered in its current form. For help clarifying this question so that it can be reopened, visit the help center.
Closed 10 years ago.
I am stumbled upon a problem, when I need a query which will produce a list of speeding time frames.
Here is the data example
[idgps_unit_location] [dt] [idgps_unit] [lat] [long] [speed_kmh]
26 10/18/2012 18:53 2 47 56 30
27 10/18/2012 18:53 2 49 58 31
28 10/18/2012 18:53 2 28 37 15
29 10/18/2012 18:54 2 56 65 33
30 10/18/2012 18:54 2 152 161 73
31 10/18/2012 18:55 2 134 143 64
32 10/18/2012 18:56 2 22 31 12
36 10/18/2012 18:59 2 98 107 47
37 10/18/2012 18:59 2 122 131 58
38 10/18/2012 18:59 2 91 100 44
39 10/18/2012 19:00 2 190 199 98
40 10/18/2012 19:01 2 194 203 101
41 10/18/2012 19:02 2 182 191 91
42 10/18/2012 19:03 2 162 171 78
43 10/18/2012 19:03 2 174 183 83
44 10/18/2012 19:04 2 170 179 81
45 10/18/2012 19:05 2 189 198 97
46 10/18/2012 19:06 2 20 29 10
47 10/18/2012 19:07 2 158 167 76
48 10/18/2012 19:08 2 135 144 64
49 10/18/2012 19:08 2 166 175 79
50 10/18/2012 19:09 2 9 18 5
51 10/18/2012 19:09 2 101 110 48
52 10/18/2012 19:09 2 10 19 7
53 10/18/2012 19:10 2 32 41 20
54 10/18/2012 19:10 1 54 63 85
55 10/19/2012 19:11 2 55 64 50
I need a query that would convert this table into the following report that shows frames of time when speed was >80:
[idgps_unit] [dt_start] [lat_start] [long_start] [speed_start] [dt_end] [lat_end] [long_end] [speed_end] [speed_average]
2 10/18/2012 19:00 190 199 98 10/18/2012 19:02 182 191 91 96.66666667
2 10/18/2012 19:03 174 183 83 10/18/2012 19:05 189 198 97 87
1 10/18/2012 19:10 54 63 85 10/18/2012 19:10 54 63 85 85
Now, what have I tried? I tried putting this into separate tables, queries and do some joins... Nothing works and I am very frustrated... I am not even sure if this could be done via the query. Asking for the expert help!

You were right, it is fairly tricky, but I think I've managed it:
SELECT s.idgps_unit,
MIN(s.dt) AS DT_Start,
MIN(CASE WHEN s.RowNumber = 1 THEN s.Lat END) AS Lat_Start,
MIN(CASE WHEN s.RowNumber = 1 THEN s.Long END) AS Long_Start,
MIN(CASE WHEN s.RowNumber = 1 THEN s.Speed_kmh END) AS Speed_Start,
MAX(s.dt) AS dt_end,
MIN(CASE WHEN s.RowNumber = MaxRowNumber THEN s.Lat END) AS Lat_End,
MIN(CASE WHEN s.RowNumber = MaxRowNumber THEN s.Long END) AS Long_End,
MIN(CASE WHEN s.RowNumber = MaxRowNumber THEN s.Speed_kmh END) AS Speed_End,
AVG(Speed_kmh) AS Speed_Average
FROM ( SELECT T.*,
#i:= CASE WHEN Speed_Kmh > 80 AND #b = 0 THEN #i + 1 ELSE #i END AS IntervalID,
#r:= CASE WHEN Speed_Kmh > 80 AND #b = 0 THEN 1 ELSE #r + 1 END AS RowNumber,
#b:= CASE WHEN Speed_Kmh> 80 THEN 1 ELSE 0 END AS IntervalCheck
FROM T,
(SELECT #i:= 0) i,
(SELECT #r:= 0) r,
(SELECT #b:= 0) b
ORDER BY dt, idgps_unit_location
) s
INNER JOIN
( SELECT IntervalID, MAX(RowNumber) AS MaxRowNumber
FROM ( SELECT T.*,
#i:= CASE WHEN Speed_Kmh > 80 AND #b = 0 THEN #i + 1 ELSE #i END AS IntervalID,
#r:= CASE WHEN Speed_Kmh > 80 AND #b = 0 THEN 1 ELSE #r + 1 END AS RowNumber,
#b:= CASE WHEN Speed_Kmh> 80 THEN 1 ELSE 0 END AS IntervalCheck
FROM T,
(SELECT #i:= 0) i,
(SELECT #r:= 0) r,
(SELECT #b:= 0) b
ORDER BY dt, idgps_unit_location
) d
WHERE IntervalCheck = 1
GROUP BY IntervalID
) MaxInt
ON MaxInt.IntervalID = s.IntervalID
WHERE s.IntervalCheck = 1
GROUP BY s.IntervalID, s.idgps_unit;
The key is in this part:
SELECT T.*,
#i:= CASE WHEN Speed_Kmh > 80 AND #b = 0 THEN #i + 1 ELSE #i END AS IntervalID,
#r:= CASE WHEN Speed_Kmh > 80 AND #b = 0 THEN 1 ELSE #r + 1 END AS RowNumber,
#b:= CASE WHEN Speed_Kmh> 80 THEN 1 ELSE 0 END AS IntervalCheck
FROM T,
(SELECT #i:= 0) i,
(SELECT #r:= 0) r,
(SELECT #b:= 0) b
ORDER BY dt, idgps_unit_location
Each time a row is encountered where the speed is over it sets the variable #b to 1, if this variable was 0 before it assigns the row a new intervalID, if it does this it begins numbering the row at 1 again, so you end up with something like this:
[idgps_unit_location] [dt] [idgps_unit] [lat] [long] [speed_kmh] [IntervalID] RowNumber IntervalCheck
37 10/18/2012 18:59 2 122 131 58 1 1 0
38 10/18/2012 18:59 2 91 100 44 1 2 0
39 10/18/2012 19:00 2 190 199 98 2 1 1
40 10/18/2012 19:01 2 194 203 101 2 2 1
41 10/18/2012 19:02 2 182 191 91 2 3 1
42 10/18/2012 19:03 2 162 171 78 2 4 0
43 10/18/2012 19:03 2 174 183 83 3 1 1
You then need to elimate all rows where the speed is under 80 (WHERE IntervalCheck = 1), and finally you can use aggregate functions along with CASE to find the rows where RowNumber is 1 (the first row of speeding), or the highest rownumber for that interval (the last row of speeding). The join at the end simply repeats the process to find what the maximum rownumber is for each intervalID.
Example on SQL Fiddle

Have you tried something like this (omitting the average speed calculation):
SELECT * FROM (
SELECT
start.idgps_unit,
start.dt dt_start,
...
end.dt dt_end,
...
(...) average_speed
FROM
your_table start,
your_table end
WHERE
start.dt < end.dt
)
WHERE average_speed > 80
This will get you a lot of overlapping timeframes, not sure whether this is desired or not. If not, you could filter with NOT EXISTS:
SELECT *
FROM (query_above) timeframes
WHERE NOT EXISTS (
SELECT *
FROM (query_above) longer_timeframes
WHERE
longer_timeframes.dt_start < timeframes.dt_end OR
longer_timeframes.dt_end > timeframes.dt_end
)
This might still get you some overlap, e.g. if you go 60 from 19:00 to 19:03, 100 from 19:03 to 19:07, and again 60 from 19:07 to 19:10. Then you have two maximum-length time intervals in which average speed was greater than 80, one from 19:00 to 19:07, the other from 19:03 to 19:10.

Related

Filter rows in mysql

I'm trying to solve a MySQL problem without going crazy. Not sure if it is feasible or not.
Data come from a door/light sensor to detect if toilet is occupied. When door is closed or opened, I get the info + light info. If I have info of closed door and light<10, I say that toilet is not occupied, if light>10, toilet is occupied, and if door is open, toilet is not occupied.
Here is an example of my data :
id wc_id door_open light time
138 0 1 64 2018-10-10 12:28:51
139 0 0 58 2018-10-10 12:34:00
140 0 0 54 2018-10-10 12:34:38
141 0 1 68 2018-10-10 12:35:11
142 0 1 3 2018-10-10 12:35:36
143 0 0 60 2018-10-10 12:37:56
144 0 0 60 2018-10-10 12:37:57
145 0 0 57 2018-10-10 12:38:30
146 0 1 65 2018-10-10 12:43:53
147 0 1 3 2018-10-10 12:44:17
148 0 0 63 2018-10-10 13:10:55
149 0 0 59 2018-10-10 13:11:16
150 0 1 71 2018-10-10 13:12:09
151 0 1 4 2018-10-10 13:12:14
152 0 1 1 2018-10-10 13:15:07
153 0 0 62 2018-10-10 13:17:18
154 0 0 58 2018-10-10 13:18:01
155 0 1 68 2018-10-10 13:19:20
156 0 1 3 2018-10-10 13:19:56
157 0 1 42 2018-10-10 13:26:41
158 0 0 63 2018-10-10 13:26:44
159 0 0 58 2018-10-10 13:27:39
160 0 1 71 2018-10-10 13:27:40
161 0 1 3 2018-10-10 13:28:37
The idea is at the end to have only a series of door_open to 0 to 1, it's not possible to have two 0 or two 1 consecutively.
So I need to keep first door_open=0 with light>10 following a door_open=1, and first door_open=1 after door_open=0, whatever light value.
Is it possible with MySQL? I use MariaDB 10.3.9.
Thanks for your ideas.
The output should be like that :
id wc_id door_open light time
139 0 0 58 12:34:00
141 0 1 68 12:35:11
143 0 0 60 12:37:56
146 0 1 65 12:43:53
148 0 0 63 13:10:55
150 0 1 71 13:12:09
153 0 0 62 13:17:18
155 0 1 68 13:19:20
158 0 0 63 13:26:44
160 0 1 71 13:27:40
(I simplified the time, it's not really important here)
Here is a fiddle
This query should do what you want. It uses a MySQL variable to delay the value of door_open by 1 row, and then returns rows where door_open=0 with light>10 following a door_open=1, and first door_open=1 after door_open=0, whatever light value:
SELECT events.*, #door_open := door_open
FROM events
JOIN (SELECT #door_open := 1) do
WHERE #door_open = 0 AND door_open = 1 OR
#door_open = 1 AND door_open = 0 AND light > 10
Output (from your fiddle data):
id toilet_id door_open light time #door_open := door_open
101 0 false 62 2018-10-10T11:39:31Z 0
103 0 true 69 2018-10-10T11:39:34Z 1
104 0 false 62 2018-10-10T11:42:16Z 0
106 0 true 68 2018-10-10T11:45:50Z 1
109 0 false 56 2018-10-10T12:13:11Z 0
Updated SQLFiddle
Here is the potential answer to my problem, after working on Nick solution. I had to reorder my table (after deleting rows) to avoid an order mess.
select es.id,
es.idNext,
es.toilet_id,
es.time,
es.nextTime,
timediff(es.nextTime, es.time) AS duration
from (
SELECT id, toilet_id, time,
#door_open := door_open as door_open,
lead(id, 1) OVER(ORDER BY id) idNext,
lead(time, 1) OVER(ORDER BY id) nextTime
FROM events e
JOIN (SELECT #door_open := 1) do
WHERE #door_open = 0 AND door_open = 1 OR
#door_open = 1 AND door_open = 0 AND light > 20
) es
where
es.door_open=0 and
timediff(es.nextTime, es.time)>5
Next thing is to update the query to use a partition over toilet_id to separate data from each id.

Percentage by Row Group

I have a matrix with rows grouped by Dept (Department). I am trying to get the actual hours / required hours percentage in a column for each row group, but I can only get the total %, not the % by group. Ex:
I should get this:
Total Employee Req Hrs Rep Hrs % Billable hrs % NonBill Hrs % Time Off %
Dept A Total 672 680 101 575 85 140 21 8 1
Emp1 168 170 101 150 89 50 29 0 0
Emp2 168 165 98 120 71 20 12 8 4
Emp3 168 175 104 155 92 20 12 0 0
Emp4 168 170 101 150 89 50 29 0 0
Dept B Total 420 428 102 365 87 80 19 4 .1
Emp5 168 170 101 150 89 50 29 0 0
Emp6 84 84 98 60 71 10 12 4 4
Emp7 168 175 104 155 92 20 12 0 0
G Total 1092 1108 101 940 86 190 17 12 1
But I get this:
Total Employee Req Hrs Rep Hrs % Billable hrs % NonBill Hrs % Time Off %
Dept A Total 1684 1675 101 1250 86 225 17 12 1
Emp1 168 170 101 150 89 50 29 0 0
Emp2 168 165 98 120 71 20 12 8 4
Emp3 168 175 104 155 92 20 12 0 0
Emp4 168 170 101 150 89 50 29 0 0
Dept B Total 1092 1108 101 1250 86 225 17 12 1
Emp5 168 170 101 150 89 50 29 0 0
Emp6 84 84 98 60 71 10 12 4 4
Emp7 168 175 104 155 92 20 12 0 0
G Total 1092 1108 101 940 86 190 17 12 1
The totals are correct but the % is wrong.
I have several Datasets because the report only runs the department you are in, except for the VPs who can see all departments.
I Insert the percentage columns into the matrix and have tried several expressions with no results including:
=Fields!ActHrs.Value/Fields!ReqHrs.Value
=Sum(Fields!ActHrs.Value, "Ut_Query")/Sum(Fields!ReqHrs.Value, "Ut_Query")
=Sum(Fields!ActHrs.Value, "Ut_Query","Dept")/Sum(Fields!ReqHrs.Value,
"Ut_Query","Dept")
=Sum(Fields!ActHrs.Value,"Dept", "Ut_Query")/Sum(Fields!ReqHrs.Value,
"Dept","Ut_Query")
Plus more I can't even remember.
I tried creating new groups, and even a new matrix.
There must be a simple way to get the percentage by group but I have not found an answer on any of the interned boards.
OK, I figured this out, but it doesn't make much sense. If I try:
=Textbox29/TextBox28 I get error messages about undefined variables.
If I go the the textbox properties and rename the textboxes to Act and Req and use:
=Act/Req I get the right answer.

select query to get value from a table of an interval

I have a table with these values...
id colA colB colC
55 00:00:00 NULL NULL
56 01:01:02 0.00007 0.00007
57 02:01:02 0 0.00007
58 03:01:02 0.00006 0
59 15:01:02 0.00012 0.00006
60 16:01:02 -0.00008 0.00012
61 20:59:02 -0.00006 -0.00008
62 21:05:01 0.00005 -0.00006
63 22:01:02 -0.00013 0.00005
64 23:01:02 0.00004 -0.00013
65 00:00:01 -0.00012 0.00004
66 01:01:02 -0.00004 -0.00012
67 02:01:02 0.00006 -0.00004
68 03:01:02 0.00004 0.00006
69 15:01:02 -0.00004 0.00004
70 16:01:02 0 -0.00004
71 23:01:02 -0.00014 0
72 00:00:01 -0.00011 -0.00014
73 01:01:02 -0.00004 -0.00011
74 02:01:02 0.00011 -0.00004
75 03:01:02 0.00005 0.00011
76 15:01:02 0 0.00005
77 16:01:02 -0.00008 0
78 23:01:02 0 -0.00008
79 00:00:01 0.00003 0
80 01:01:02 -0.00009 0.00003
81 02:01:02 -0.00007 0
82 03:01:02 -0.00007 -0.00016
83 15:01:02 0.00044 -0.00007
84 16:01:02 0 0.00044
Now I want to get the SUM of colb, colc on each time colA value changes to 15:01...
The output should be like the following
colA sum(colB) SUM(colC)
15:01:01.582 0.0002500 0.0002000
15:01:01.582 -0.0002800 -0.0001200
15:01:01.582 -0.0001300 -0.0001700
15:01:01.582 0.0001600 -0.0002800
Can anyone please help me with this query.
You can characterize each row by the number of cola values that are 18:01 that appear after it. Then you can group by this number.
In MySQL, the easiest way to get this number is by using variables:
select grp, max(cola), sum(colb), sum(colc)
from (select t.*,
(#grp := #grp + (cola = '18:01')) as grp
from table t cross join (select #grp := 0) vars
order by id desc
) t
group by grp;

how do i join two different tables in mysql

This is table1:
id name m1 m2 m3 total itemno
1 raj 10 10 10 30 1
2 ram 60 60 60 180 1
3 kumar 70 70 70 210 1
4 kanna 50 50 50 150 1
5 vivek 64 64 91 200 1
5 vivek 90 90 90 270 2
This is table2:
id name mark1 mark2 mark3 itemno
101 vivek 78 78 78 1
102 vivekkanna 89 88 78 1
103 rajagopalan 97 90 98 1
104 kumar 69 54 56 1
101 vivek 90 90 90 2
I want to join these two tables like this into a result set that looks like this:
id name m1 m2 m3 total mark1 mark2 mark3 item no
1 raj 10 10 10 30 0 0 0 1
2 ram 60 60 60 180 0 0 0 1
3 kumar 70 70 70 210 69 54 56 1
4 kanna 50 50 50 150 0 0 0 1
5 vivek 64 64 91 200 78 78 78 1
5 vivek 90 90 90 270 90 90 90 2
Seems you want a regular LEFT JOIN, returning a default value if the row in table2 does not exist;
SELECT t1.id, t1.name, t1.m1, t1.m2, t1.m3,
COALESCE(t2.mark1, 0) mark1, COALESCE(t2.mark2, 0) mark2, t1.itemno
FROM table1 t1
LEFT JOIN table2 t2 ON t1.name = t2.name AND t1.itemno = t2.itemno
ORDER BY t1.id, t1.itemno
An SQLfiddle to test with
We use a LEFT JOIN to get the default NULL value for all table2 fields that don't match with a row in table1, then COALESCE to turn them into 0 instead.

Finding a running Difference in Columns with mySQL

I am trying to get the following result:
HOUR Throughput Difference
0 298 0
1 209 -89
6 1 -208
7 8 -7
8 9 1
9 175 166
10 223 and so on
11 260
12 246
13 195
14 170
15 230
16 200
17 301
18 316
19 282
20 293
21 281
22 175
23 244
The sql statement i currently have is
SELECT DISTINCT
HOUR(p_datetime) as HOUR,
Count(p_type) as Throughput
FROM
product_log
WHERE
DATE(p_datetime) = '2013-11-30' AND
p_type='STACK'
GROUP BY HOUR;
I have been trying for a while now and cannot find a solution This is done in mysql.
Thanks!
One way to do it
SELECT hour, throughput, difference
FROM
(
SELECT hour, throughput,
throughput - #p difference, #p := throughput
FROM
(
SELECT HOUR(p_datetime) hour,
COUNT(*) throughput
FROM product_log l
WHERE p_datetime >= '2013-11-30'
AND p_datetime < '2013-11-31'
AND p_type = 'stack'
GROUP BY HOUR(p_datetime)
) a CROSS JOIN (SELECT #p := 0) i
) b
Here is SQLFiddle demo