SQL update column3 after group by column1 and compare value in column2 - mysql

If I have a table T that look like this: where id is the unique auto-increment primary key. Difference column is default to 0. I want to UPDATE only the difference of largestId - secondLargestId in each id_str group while the rest remains unchanged.
id_str id Value Difference
2380 1 21.01 0
2380 3 22.04 0
2380 5 22.65 0
2380 8 23.11 0
2380 10 35.21 0
20100 2 37.07 0
20100 4 38.17 0
20100 6 38.97 0
20103 7 57.98 0
20103 9 60.83 0
The result I want is:
id_str id Value Difference
2380 1 21.01 0
2380 3 22.04 0
2380 5 22.65 0
2380 8 23.11 0
2380 10 35.21 12.1
20100 2 37.07 0
20100 4 38.17 0
20100 6 38.97 0.8
20103 7 57.98 0
20103 9 60.83 2.85
How can I write the query?

This should do the trick in MySQL.
CREATE TABLE SomeTable
( id_str VARCHAR(10),
id INTEGER,
value_ DECIMAL(7,5),
difference DECIMAL(7,5)
);
INSERT INTO SomeTable VALUES(2380,1,21.01,0);
INSERT INTO SomeTable VALUES(2380,3,22.04,0);
INSERT INTO SomeTable VALUES(2380,5,22.65,0);
INSERT INTO SomeTable VALUES(2380,8,23.11,0);
INSERT INTO SomeTable VALUES(2380,10,35.21,0);
INSERT INTO SomeTable VALUES(20100,2,37.07,0);
INSERT INTO SomeTable VALUES(20100,4,38.17,0);
INSERT INTO SomeTable VALUES(20100,6,38.97,0);
INSERT INTO SomeTable VALUES(20103,7,57.98,0);
INSERT INTO SomeTable VALUES(20103,9,60.83,0);
UPDATE SomeTable,
(SELECT T1.id AS id_updt,
T1.value_ - T2.value_ AS diff_updt
FROM (SELECT id_str,
id,
value_,
(
CASE id_str
WHEN #curStr THEN #curRow := #curRow + 1
ELSE #curRow := 1
AND #curStr := id_str
END
) AS rnk
FROM SomeTable,
(SELECT #curRow := 0, #curStr := '') r
ORDER
BY id_str DESC,
id DESC
) AS T1
INNER
JOIN (SELECT id_str,
id,
value_,
(
CASE id_str
WHEN #curStr THEN #curRow := #curRow + 1
ELSE #curRow := 1
AND #curStr := id_str
END
) AS rnk
FROM SomeTable,
(SELECT #curRow := 0, #curStr := '') r
ORDER
BY id_str DESC,
id DESC
) AS T2
ON T1.id_str = T2.id_str
AND T1.rnk = 1
AND T2.rnk = 2
) AS UPDT
SET SomeTable.difference = UPDT.diff_updt
WHERE SomeTable.id = UPDT.id_updt;
Deprecated solution - This will work for a DBMS that supports the rank function.
UPDATE SomeTable
FROM ( SELECT RNK1.id AS id_updt,
RNK1.value_ - RNK2.value_ AS diff_updt
FROM (SELECT id_str,
RANK() OVER
( PARTITION BY id_str
ORDER BY id DESC
) AS id_rnk
FROM SomeTable
) AS RNK1
INNER
JOIN (SELECT id_str,
RANK() OVER
( PARTITION BY id_str
ORDER BY id DESC
) - 1 AS id_rnk_decrement
FROM SomeTable
) AS RNK2
ON RNK1.id_str = RNK2.id_str
AND RNK1.id_rnk = RNK2.id_rnk_decrement
WHERE RNK1.id_rnk = 1
) AS UPDT
SET SomeTable.difference_ = UPDT.diff_updt
WHERE SomeTable.id = UPDT.id_updt;

You can find the two greatest ids per group with the following query:
select t1.id_str, max(t1.id) as id1, (
select max(t2.id)
from mytable t2
where t2.id_str = t1.id_str
and t2.id < max(t1.id)
) as id2
from mytable t1
group by t1.id_str;
Result:
| id_str | id1 | id2 |
|--------|-----|-----|
| 2380 | 10 | 8 |
| 20100 | 6 | 4 |
| 20103 | 9 | 7 |
Use it as subquery in your update statement:
update mytable u
join (
select t1.id_str, max(t1.id) as id1, (
select max(t2.id)
from mytable t2
where t2.id_str = t1.id_str
and t2.id < max(t1.id)
) as id2
from mytable t1
group by t1.id_str
) t on t.id1 = u.id
join mytable t1 on t1.id = t.id1
join mytable t2 on t2.id = t.id2
set u.Difference = t1.Value - t2.Value;
The table will now contain:
| id_str | id | Value | Difference |
|--------|----|-------|------------|
| 2380 | 1 | 21.01 | 0 |
| 2380 | 3 | 22.04 | 0 |
| 2380 | 5 | 22.65 | 0 |
| 2380 | 8 | 23.11 | 0 |
| 2380 | 10 | 35.21 | 12.1 |
| 20100 | 2 | 37.07 | 0 |
| 20100 | 4 | 38.17 | 0 |
| 20100 | 6 | 38.97 | 0.8 |
| 20103 | 7 | 57.98 | 0 |
| 20103 | 9 | 60.83 | 2.85 |
http://rextester.com/CCO40873

Related

SQL query to get latest non null value of columns grouping by a column

I have a table in MySQL table, which looks like
+--------------------------+------------+-----------------+---------+------+-------+-------------+-------------------------+----------------------------+-------+-------+
| deviceID | date | timestamp | counter | rssi | vavId | nvo_airflow | nvo_air_damper_position | nvo_temperature_sensor_pps | block | floor |
+--------------------------+------------+-----------------+---------+------+-------+-------------+-------------------------+----------------------------+-------+-------+
| fd00::212:4b00:1957:d616 | 2020-02-29 | 12:40:01.513066 | 805 | 91 | 7 | NULL | NULL | 26.49 | NULL | ABCD |
| fd00::212:4b00:1957:d616 | 2020-02-29 | 12:41:01.542272 | 807 | 94 | 5 | 50 | 64 | 26.37 | NULL | ABCD |
| fd00::212:4b00:1957:d616 | 2020-02-29 | 12:43:01.699023 | 811 | 90 | 7 | 50 | NULL | NULL | NULL | ABCD |
| fd00::212:4b00:1957:d616 | 2020-02-29 | 12:46:01.412259 | 817 | 64 | 26 | NULL | NULL | 25.85 | NULL | ABCD |
| fd00::212:4b00:1957:d616 | 2020-02-29 | 12:48:01.576133 | 821 | 91 | 26 | 55 | 42 | NULL | NULL | ABCD |
| fd00::212:4b00:1957:d616 | 2020-02-29 | 12:49:01.529593 | 823 | 91 | 7 | 45 | 72 | NULL | NULL | ABCD |
I want to get the latest non null data of 3 columns(nvo_airflow, nvo_air_damper_position, nvo_temperature_sensor_pps) for each vavId.
My result should look something like
vavId,nvo_airflow,nvo_air_damper_position,nvo_temperature_sensor_pps
5,50,64,26.37
7,45,72,26.49
26,55,42,25.85
I have written a sql query for the same,
SELECT airflow_table.nvo_airflow,damper_position_table.nvo_air_damper_position,temperature_sensor_table.nvo_temperature_sensor_pps,temperature_sensor_table.vavId
FROM(
((SELECT t1.date,t1.timestamp,t1.nvo_airflow,t1.vavId
FROM
(SELECT * FROM vavDataOptimized where date='2020-02-29')t1
INNER JOIN
(SELECT max(timestamp) as recent_timestamp,vavId FROM vavDataOptimized where date='2020-02-29' and `nvo_airflow` is not null GROUP BY vavId)t2
ON (t1.timestamp = t2.recent_timestamp and t1.vavId = t2.vavId)
ORDER BY vavId) airflow_table
inner join
(SELECT t1.date,t1.timestamp,t1.nvo_air_damper_position,t1.vavId
FROM
(SELECT * FROM vavDataOptimized where date='2020-02-29')t1
INNER JOIN
(SELECT max(timestamp) as recent_timestamp,vavId FROM vavDataOptimized where date='2020-02-29' and `nvo_air_damper_position` is not null GROUP BY vavId)t2
ON (t1.timestamp = t2.recent_timestamp and t1.vavId = t2.vavId)
ORDER BY vavId) damper_position_table ON airflow_table.vavId = damper_position_table.vavId)
inner join
(SELECT t1.date,t1.timestamp,t1.nvo_temperature_sensor_pps,t1.vavId
FROM
(SELECT * FROM vavDataOptimized where date='2020-02-29')t1
INNER JOIN
(SELECT max(timestamp) as recent_timestamp,vavId FROM vavDataOptimized where date='2020-02-29' and `nvo_temperature_sensor_pps` is not null GROUP BY vavId)t2
ON (t1.timestamp = t2.recent_timestamp and t1.vavId = t2.vavId)
ORDER BY vavId) temperature_sensor_table on airflow_table.vavId = temperature_sensor_table.vavId);
What I am trying to do is getting the latest value for each of nvo_airflow, nvo_air_damper_position, nvo_temperature_sensor_pps for each vav as three intermediate tables and then trying to do a inner join on the tables.
This query is taking a lot of time to time and not getting executed. I am not sure if I am doing in it an optimized way. Am I doing something wrong, or is there a better way of doing this?
Here is one option of doing it. What i have done is to first rank the records on the basis of latest value of the attribute columns which are not null(eg: airflow_flg=1 implies ranks by not null values only)
After that a union of all the three would get the values you are looking for.
with data
as (
select *
,case when nvo_airflow is null then 0 else 1 end as airflow_flg
,case when nvo_air_damper_position is null then 0 else 1 end as damper_flg
,case when nvo_temperature_sensor_pps is null then 0 else 1 end as sensor_flg
,row_number() over(partition by case when nvo_airflow is null then 0 else 1 end,deviceid,vavid order by timestamp1 desc) as rnk_airflow
,row_number() over(partition by case when nvo_air_damper_position is null then 0 else 1 end,deviceid,vavid order by timestamp1 desc) as rnk_damper
,row_number() over(partition by case when nvo_temperature_sensor_pps is null then 0 else 1 end,deviceid,vavid order by timestamp1 desc) as rnk_sensor
from t
)
,concat_data
as (
select deviceid
,vavid
,nvo_airflow as val
,'nvo_airflow' as txt
from data
where airflow_flg=1
and rnk_airflow=1
union all
select deviceid
,vavid
,nvo_air_damper_position as val
,'nvo_air_damper_position' as txt
from data
where damper_flg=1
and rnk_damper=1
union all
select deviceid
,vavid
,nvo_temperature_sensor_pps as val
,'nvo_temperature_sensor_pps' as txt
from data
where sensor_flg=1
and rnk_sensor=1
)
select deviceid
,vavid
,max(case when txt='nvo_airflow' then val end) as nvo_airflow
,max(case when txt='nvo_air_damper_position' then val end) as nvo_air_damper_position
,max(case when txt='nvo_temperature_sensor_pps' then val end) as nvo_temperature_sensor_pps
from concat_data
group by deviceid
,vavid
https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=5440a6e55e7b9a82596f57840dc38083
Does something like this fits your needs?
select vavId,nvo_airflow,nvo_air_damper_position,nvo_temperature_sensor_pps
from
(select vavId, #rownum1 := #rownum1 + 1 as rownum1 from
(select vavId
from vavDataOptimized
where vavId is not NULL
ORDER BY date1,timestamp1 DESC LIMIT 3) a
CROSS JOIN (SELECT #rownum1 := 0) v) a,
(select nvo_airflow, #rownum2 := #rownum2 + 1 as rownum2 from
(select nvo_airflow
from vavDataOptimized
where nvo_airflow is not NULL
ORDER BY date1,timestamp1 DESC LIMIT 3) b
CROSS JOIN (SELECT #rownum2 := 0) v) b,
(select nvo_air_damper_position, #rownum3 := #rownum3 + 1 as rownum3 from
(select nvo_air_damper_position
from vavDataOptimized
where nvo_air_damper_position is not NULL
ORDER BY date1,timestamp1 DESC LIMIT 3) c
CROSS JOIN (SELECT #rownum3 := 0) v) c,
(select nvo_temperature_sensor_pps, #rownum4 := #rownum4 + 1 as rownum4 from
(select nvo_temperature_sensor_pps
from vavDataOptimized
where nvo_temperature_sensor_pps is not NULL
ORDER BY date1,timestamp1 DESC LIMIT 3) d
CROSS JOIN (SELECT #rownum4 := 0) v) d
where rownum1 = rownum2
and rownum1 = rownum3
and rownum1 = rownum4
Here is the fiddle : https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=57539e363b668038547df037b15f0dee

Sum of repeating values in successive rows

I have a table like this:
id col1 col2 col3
10 1 3
9 1 2 3
8 2 3
7 2 3
6 1 2
5 3
Each column has one value only or null. Eg. Col1 has 1 or empty. Col2 has 2 or empty.
I'd like to get the sum of repeating values only between two successive rows.
so the result would look like this:
I need to get the sum of total repeating values in each row.
id col1 col2 col3 Count
10 1 3 2 (shows the repeating values between id10 & id9 rows)
9 1 2 3 2 (shows the repeating values between id9 & id8 rows)
8 2 3 1
7 2 1
6 1 2 0
5 3
I googled and tried some queries I found on the web but couldn't get the right result. Thanks in advance for your help.
To further clarify, for example:
id10 row has (1,,3) and id9 row has (1,2,3). so there is two values repeating. so count is 2.
If the ids are consecutive and there are no gaps, you can do it with a self join:
select
t.*,
coalesce((t.col1 = tt.col1), 0) +
coalesce((t.col2 = tt.col2), 0) +
coalesce((t.col3 = tt.col3), 0) count
from tablename t left join tablename tt
on tt.id = t.id - 1
See the demo.
Results:
| id | col1 | col2 | col3 | count |
| --- | ---- | ---- | ---- | ----- |
| 10 | 1 | | 3 | 2 |
| 9 | 1 | 2 | 3 | 2 |
| 8 | | 2 | 3 | 1 |
| 7 | | 2 | | 1 |
| 6 | 1 | 2 | | 0 |
| 5 | | | 3 | 0 |
And if there are gaps...
SELECT a.id
, a.col1
, a.col2
, a.col3
, COALESCE(a.col1 = b.col1,0) + COALESCE(a.col2 = b.col2,0) + COALESCE(a.col3 = b.col3,0) n
FROM
( SELECT x.*
, MIN(y.id) y_id
FROM my_table x
JOIN my_table y
ON y.id > x.id
GROUP
BY x.id
) a
LEFT
JOIN my_table b
ON b.id = a.y_id;
Were you to restructure your schema, then you could do something like this instead...
DROP TABLE IF EXISTS my_table;
CREATE TABLE my_table
(id INT NOT NULL
,val INT NOT NULL
,PRIMARY KEY(id,val)
);
INSERT INTO my_table VALUES
(10,1),
(10,3),
( 9,1),
( 9,2),
( 9,3),
( 8,2),
( 8,3),
( 7,2),
( 7,3),
( 6,1),
( 6,2),
( 5,3);
SELECT a.id
, COUNT(b.id) total
FROM
( SELECT x.*
, MIN(y.id) next
FROM my_table x
JOIN my_table y
ON y.id > x.id
GROUP
BY x.id
, x.val
) a
LEFT
JOIN my_table b
ON b.id = a.next
AND b.val = a.val
GROUP
BY a.id;
+----+-------+
| id | total |
+----+-------+
| 5 | 0 |
| 6 | 1 |
| 7 | 2 |
| 8 | 2 |
| 9 | 2 |
+----+-------+
You can use :
select t1_ID, t1_col1,t1_col2,t1_col3, count
from
(
select t1.id as t1_ID, t1.col1 as t1_col1,t1.col2 as t1_col2,t1.col3 as t1_col3, t2.*,
case when t1.col1 = t2.col1 then 1 else 0 end +
case when t1.col2 = t2.col2 then 1 else 0 end +
case when t1.col3 = t2.col3 then 1 else 0 end as count
from tab t1
left join tab t2
on t1.id = t2.id + 1
order by t1.id
) t3
order by t1_ID desc;
Demo
If there are gaps between id values for the next row, you could have user defined variables to explicitly assign values to rows in their natural ordering in the table. Rest logic remains the same as already answered. You would do an inner join between current row number and next row number to get the col1,col2 and col3 values and use coalesce for computation of count.
select derived_1.*,
coalesce((derived_1.col1 = derived_2.col1), 0) +
coalesce((derived_1.col2 = derived_2.col2), 0) +
coalesce((derived_1.col3 = derived_2.col3), 0) count
from (
select #row := #row + 1 as row_number,t1.*
from tablename t1,(select #row := 0) d1
) derived_1
left join (
select *
from (
select #row2 := #row2 + 1 as row_number,t2.*
from tablename t2,(select #row2 := 0) d2
) d3
) derived_2
on derived_1.row_number + 1 = derived_2.row_number;
Demo: https://www.db-fiddle.com/f/wAzb67zSEfbZKg5RywQvC8/1

Subtract Next to the current and make that output as the data of that current row

Computing Kilometer Run is based on this
current value = next value - current value
I have a table that looks like this.
My question is how can I compute the kmr based on the odometer value? I will replace the value of kmr column of kmr value
You can use variables to store last values.
create table tbl (code varchar(10), vdate date, kmr int);
✓
insert into tbl values
('Person1', '20180101', 71883),
('Person1', '20180102', 71893),
('Person1', '20180103', 71903),
('Person2', '20180101', 71800),
('Person2', '20180102', 71815),
('Person2', '20180103', 71820);
✓
select code, vdate, kmr, current_kmr
from
(
select t1.code, t1.vdate, t1.kmr,
t1.kmr - if(coalesce(#last_code, t1.code) = t1.code, coalesce(#last_kmr, t1.kmr), t1.kmr) as current_kmr,
#last_kmr := t1.kmr,
#last_code := t1.code
from tbl t1,
(select #last_kmr := null, #last_code := null) t2
order by t1.code, t1.vdate
) t
code | vdate | kmr | current_kmr
:------ | :--------- | ----: | ----------:
Person1 | 2018-01-01 | 71883 | 0
Person1 | 2018-01-02 | 71893 | 10
Person1 | 2018-01-03 | 71903 | 10
Person2 | 2018-01-01 | 71800 | 0
Person2 | 2018-01-02 | 71815 | 15
Person2 | 2018-01-03 | 71820 | 5
dbfiddle here
This will work using rank and for MORE THAN one person
http://sqlfiddle.com/#!9/4c054e/1
Select m.`code`,m.vdate, ( n.kmr - m.kmr) as new_kmr
From
(Select t1.*, #rnk := #rnk + 1 as rnk
From tbl t1, (select #rnk := 0) t
Order by t1.`code`,t1.vdate) m left join
(Select t2.*, #rnk1 := #rnk1 + 1 as rnk
From tbl t2, (select #rnk1:= 0) t
Order by t2.`code`,t2.vdate) n
On m.`code` = n.`code`
And m.rnk + 1 = n.rnk
Order by m.`code`, m.vdate
Output:
code vdate new_kmr
person 1 2018-03-01 10
person 1 2018-03-02 10
person 1 2018-03-03 (null)
person 2 2018-03-01 5
person 2 2018-03-02 (null)

Sum SQL timestamp

How can do a sum search by 'id=1' and add up their hours.
The first line will be there in time, the next will be their out time.
Below should add up to 5:43:29
id, ts
1,2016-06-20 04:25:32
3,2016-06-20 07:40:09
1,2016-06-20 09:37:46
3,2016-06-20 14:40:57
1,2016-06-20 15:12:14
1,2016-06-20 15:43:29
2,2016-06-20 15:47:01
2,2016-06-20 17:47:03
You can add row number with user defined variable. Here odd rows represents starting time, multiply -1 when it is starting time with if(r%2=0,1,-1), and add them up:
select sec_to_time(sum(time_to_sec(ts)*if(r%2=0,1,-1)))
from (
select #row:=#row+1 r, a.*
from Table1 a
join (select #row:=0) b
where a.id = 1) a;
Oh boy, this was a fun one:
SELECT SEC_TO_TIME(
SUM(UNIX_TIMESTAMP(
(SELECT ts FROM example_table e2 WHERE id = ranked.id AND ts > ranked.ts ORDER BY id, ts LIMIT 1)
)-UNIX_TIMESTAMP(ts))
)
FROM (
SELECT
#row := #row +1 AS rownum, id, ts
FROM (
SELECT #row :=0) r, example_table
WHERE id = 1
ORDER BY id ASC, ts ASC
) ranked
WHERE ranked.rownum % 2 = 1
First we fetch all the odd rows: 1, 3, 5, 7 and soforth. Next, we use a subquery to fetch the next timestamp matching that row (The punch out time), and get the differences (In seconds). Sum will total up the seconds, and sec_to_time will convert that into a friendly readable format.
Here my answer with a sample
Use sql like this:
SELECT
t_from.id,
SEC_TO_TIME(SUM(TIME_TO_SEC( TIMEDIFF(
( SELECT ts
FROM t t_to
WHERE
t_to.ts > t_from.ts_from
AND
t_to.id = t_from.id
ORDER BY t_to.id,t_to.ts
LIMIT 1
)
, t_from.ts_from
)))) AS ts_diff
FROM (
SELECT #nr:=((#nr+1) %2) AS nr,t.id,t.ts AS ts_from
FROM t
CROSS JOIN ( SELECT #nr:=0) AS parameter
ORDER BY id
) AS t_from
WHERE nr =1
GROUP BY t_from.id;
my Table
MariaDB [yourSchema]> select * from t;
+----+---------------------+
| id | ts |
+----+---------------------+
| 1 | 2016-06-20 04:25:32 |
| 3 | 2016-06-20 07:40:09 |
| 1 | 2016-06-20 09:37:46 |
| 3 | 2016-06-20 14:40:57 |
| 1 | 2016-06-20 15:12:14 |
| 1 | 2016-06-20 15:43:29 |
| 2 | 2016-06-20 15:47:01 |
| 2 | 2016-06-20 17:47:03 |
+----+---------------------+
8 rows in set (0.00 sec)
convert to start end times
MariaDB [yourSchema]> SELECT
-> t_from.id
-> , t_from.ts_from,
-> ( SELECT ts
-> FROM t t_to
-> WHERE
-> t_to.ts > t_from.ts_from
-> AND
-> t_to.id = t_from.id
-> ORDER BY t_to.id,t_to.ts
-> LIMIT 1
-> ) AS ts_to
-> FROM (
-> SELECT #nr:=((#nr+1) %2) AS nr,t.id,t.ts AS ts_from
-> FROM t
-> CROSS JOIN ( SELECT #nr:=0) AS parameter
-> ORDER BY id
-> ) AS t_from
-> WHERE nr =1;
+----+---------------------+---------------------+
| id | ts_from | ts_to |
+----+---------------------+---------------------+
| 1 | 2016-06-20 04:25:32 | 2016-06-20 09:37:46 |
| 1 | 2016-06-20 15:12:14 | 2016-06-20 15:43:29 |
| 2 | 2016-06-20 15:47:01 | 2016-06-20 17:47:03 |
| 3 | 2016-06-20 07:40:09 | 2016-06-20 14:40:57 |
+----+---------------------+---------------------+
4 rows in set (0.00 sec)
MariaDB [yourSchema]>
Sum the diffs and group by id
MariaDB [yourSchema]> SELECT
-> t_from.id,
-> SEC_TO_TIME(SUM(TIME_TO_SEC( TIMEDIFF(
-> ( SELECT ts
-> FROM t t_to
-> WHERE
-> t_to.ts > t_from.ts_from
-> AND
-> t_to.id = t_from.id
-> ORDER BY t_to.id,t_to.ts
-> LIMIT 1
-> )
-> , t_from.ts_from
-> )))) AS ts_diff
-> FROM (
-> SELECT #nr:=((#nr+1) %2) AS nr,t.id,t.ts AS ts_from
-> FROM t
-> CROSS JOIN ( SELECT #nr:=0) AS parameter
-> ORDER BY id
-> ) AS t_from
-> WHERE nr =1
-> GROUP BY t_from.id;
+----+----------+
| id | ts_diff |
+----+----------+
| 1 | 05:43:29 |
| 2 | 02:00:02 |
| 3 | 07:00:48 |
+----+----------+
3 rows in set (0.00 sec)
MariaDB [yourSchema]>
Join the table with itself and search for the next timestamp:
select t1.id,
t1.ts as ts_from,
min(t2.ts) as ts_to,
timediff(min(t2.ts), t1.ts) as uptime
from uptime t1
join uptime t2
on t2.id = t1.id
and t2.ts > t1.ts
where t1.id = 1
group by t1.id, t1.ts
Result:
| id | ts_from | ts_to | uptime |
|----|---------------------|---------------------|----------|
| 1 | 2016-06-20 04:25:32 | 2016-06-20 09:37:46 | 05:12:14 |
| 1 | 2016-06-20 09:37:46 | 2016-06-20 15:12:14 | 05:34:28 |
| 1 | 2016-06-20 15:12:14 | 2016-06-20 15:43:29 | 00:31:15 |
Demo
You will need an index on id, ts.
ALTER TABLE `uptime` ADD INDEX `id_ts` (`id`, `ts`);
Update:
After reading you question again i think i got what you are after. You can use the obove query in a subquery, a session variable to switch betwen even and odd rows and a conditional SUM:
select sec_to_time(sum(
case when #switch := 1 - #switch then time_to_sec(sub.uptime) end
)) as uptime
from (
select timediff(min(t2.ts), t1.ts) as uptime
from uptime t1
join uptime t2
on t2.id = t1.id
and t2.ts > t1.ts
where t1.id = 1
group by t1.id, t1.ts
order by t1.ts
) sub
join (select #switch := 0) init_switch
Demo

MySql counting the number of groups of rows containing a certain value

How can I get the number of "groups" of a status, where status == 0, excluding groups which start the table and groups that span <= hour? (If the time constraint is too difficult, we can alternatively exclude groups with counts <= 40 instead of groups spanning <= hour, since a row is logged about every 1:30 minutes.)
For example, the following SAMPLE table WITHOUT the time constraint would produce 3 if grouping by status == 0.
+------+----------+----------+
| id | status |time |
+------+----------+----------+
| 0001 | 1 |11:32:48 |
+------+----------+----------+
| 0002 | 0 |11:30:26 |
+------+----------+----------+
| 0003 | 0 |11:28:54 |
+------+----------+----------+
| 0004 | 1 |11:27:23 |
+------+----------+----------+
| 0005 | 0 |11:25:52 |
+------+----------+----------+
| 0006 | 1 |11:24:20 |
+------+----------+----------+
| 0007 | 1 |11:22:48 |
+------+----------+----------+
| 0008 | 0 |11:21:17 |
+------+----------+----------+
| 0009 | 0 |11:19:45 |
+------+----------+----------+
| 0010 | 0 |11:18:14 |
+------+----------+----------+
| 0011 | 0 |11:16:43 |
+------+----------+----------+
| 0012 | 0 |11:15:11 |
+------+----------+----------+
| 0013 | 0 |11:13:39 |
+------+----------+----------+
| 0002 | 0 |11:12:08 |
+------+----------+----------+
| 0014 | 1 |11:10:37 |
+------+----------+----------+
| 0015 | 1 |11:09:05 |
+------+----------+----------+
| 0016 | 1 |11:07:33 |
+------+----------+----------+
| 0017 | 0 |11:06:02 |
+------+----------+----------+
One solution I can think of would be to grab the entire table and produce the result with Java, but I am afraid this would be too inefficient given that the table can have millions of entries.
select sum(is_different_from_previous) , status
from (
select status,
(#prevStatus <> status and #prevStatus <> -1) is_different_from_previous,
#prevStatus := status
from myTable t1
cross join (select #prevStatus := -1) t2
order by t1.time
) t1 group by status
for a specific status
select * from (
select sum(is_different_from_previous) , status
from (
select status,
(#prevStatus <> status and #prevStatus <> -1) is_different_from_previous,
#prevStatus := status
from myTable t1
cross join (select #prevStatus := -1) t2
order by t1.time
) t1 group by status
) t1 where status = 0
Edit
To only count groups with a certain # of 0s
select count(*) from (
select * from (
select status,
(#prevStatus <> status and #prevStatus <> -1) is_different_from_previous,
if(#prevStatus <> status and #prevStatus <> -1,#groupNumber := #groupNumber + 1, #groupNumber) groupNumber,
#prevStatus := status
from myTable t1
cross join (select #prevStatus := -1, #groupNumber := 0) t2
order by t1.id
) t1
where status = 0
group by groupNumber
having count(*) > 4
) t1
http://sqlfiddle.com/#!9/e4a49/23
Try the following modified query, which is more efficient than the earlier one, because another table scan is eliminated and we restrict the data to only the last one hour. Also, the first group is not counted.
EDIT: I changed the JOIN condition back to st2.id = st1.id+1 to satisfy the requirements.
select
st1.status,
count(st1.id)
from sampletable st1
inner join sampletable st2
on (st2.id = st1.id+1 and st2.status <> st1.status)
where st1.status = 0 AND st1.time >= DATE_SUB(NOW(), INTERVAL 1 hour)
group by st1.status;
Updated SQL Fiddle demo with same id, status data:
SQL Fiddle demo