Checking consecutive values at a MySQL query - mysql

I have a MySQL table like this:
ID - Time - Value
And I'm getting every pair of ID, Time (grouped by ID) where Value is greater than a certain threshold. So basicaly, I'm getting every ID which has at least one time a value greater than the threshold. The query looks like this:
SELECT ID, Time FROM mydb.MYTABLE
WHERE Value>%s AND Time>=%s AND Time<=%s
GROUP BY ID
EDIT: The Time checks allow to operate in a time range of my choice between all the data which is into the table; it has nothing else to do with what I am asking.
It works perfectly, but now I want to add some filtering: I want it to avoid those times the value is greater than the threshold (let's call it alarms) if the alarm hasn't happened also the Time just before or just after. I mean: if the alarm accurs at a single, isolated instant of time instead of two consecutive instants of time, I'll consider it is a false alarm and avoid it to be returned at the query response.
Of course I can do this with a call for each Id to check for this, but I'd like to do this in a single query to make it faster. I guess I could use conditionals, but I don't have that expertise at MySQL.
Any help?
EDIT2: Example for Threshold = 10
ID - Time - Value
1 - 2004 - 9
1 - 2005 - 11
1 - 2006 - 8
2 - 2107 - 12
2 - 2109 - 13
3 - 3402 - 11
3 - 3403 - 12
In this example, only ID 3 should be a valid alarm, since 2 consecutive time values for this ID have their value > threshold. ID 1 has a single, isolated alarm, so it should be filteres. For ID 2 there are 2 alarms, but not consecutive, so it should be also filtered.

Something like this:
10 - is a threshold
0 - minimum of the time period
100000 - maximum of the time period
select ID, min(Time)
from
(
SELECT ID, Time,
(select max(time) from t
where Time<t1.Time
and Id=t1.Id
and Value>10) LAG_G,
(select max(time) from t
where Time<t1.Time
and Id=t1.Id
and Value<=10) LAG_L,
(select min(time) from t
where Time>t1.Time
and Id=t1.Id
and Value>10) LEAD_G,
(select min(time) from t
where Time>t1.Time
and Id=t1.Id
and Value<=10) LEAD_L
FROM t as t1
WHERE Value>10 AND Time>=0 AND Time<=100000
) t3
where ifnull(LAG_G,0)>ifnull(LAG_L,0)
OR
ifnull(LEAD_G,100000)<ifnull(LEAD_L,100000)
GROUP BY ID
SQLFiddle demo
This query works for searching near records.
If you need to search records by Time (+1, -1 ) as you've mentioned in the comment try this query:
select ID, min(Time) from t as t1
where Value>10
AND Time>=%s2 AND Time<=%s1
and
(
Exists(select 1 from t where Value>10
and Id=t1.Id
and Time=t1.Time-1)
OR
Exists(select 1 from t where Value>10
and Id=t1.Id
and Time=t1.Time+1)
)
group by ID
SQLFiddle demo

such alarm ?
SELECT ID, Time , count(if(value>%treshold ,1,0)) alert_active
FROM mydb.MYTABLE
WHERE Value>%s3 AND Time>=%s2 AND Time<=%s1
GROUP BY ID;
i don't understand exactly:
In this example, only ID 3 should be a valid alarm, since 2
consecutive time values for this ID have their value > threshold. ID 1
has a single, isolated alarm, so it should be filteres. For ID 2 there
are 2 alarms, but not consecutive, so it should be also filtered.
I guess that You want filter alerts:
SELECT ID, Time
FROM mydb.MYTABLE
WHERE Value>%s3 AND Time>=%s2 AND Time<=%s1
GROUP BY ID
having value<%treshold;

Related

How to check how many time a car is parked on

I need a report of which cars (license plates) are parked in and when.
This is an example of my table.
id lic_plate datetime lane
_________________________________________________
10 1234-JM 2022-10-07 12:24:33 IN
11 1234-JM 2022-10-07 12:29:57 OUT
12 5678-PC 2022-10-07 15:14:17 IN
So when I query which are those who are parked more than - for instance - 1hour, the result Now (2022-10-07 15:14:17) should be "5678-PC".
I have tried:
SELECT lic_plate, COUNT(*) AS result
FROM table
GROUP BY lic_plate
HAVING COUNT(lane='IN') AND COUNT(lane='OUT')
But I can't figure out where I insert the HOUR query. And it seems to me that this kind of solution will have some "hidden" flaws.
What will be the best approach?
Thanks in advance.
select max(id) as id
,lic_plate
,max(datetime) as datetime
,timediff(now(),max(datetime)) as time_parked
from t
group by lic_plate
having count(*)%2 != 0
id
lic_plate
datetime
time_parked
12
5678-PC
2022-10-07 15:14:17
69:26:12
Fiddle
We check the time difference between the current date and time and datetime. We count how many records we have per car and if we have even numbers of records we filter the results as we know the car is out already.
select id
,lic_plate
,datetime
,time_parked
from (
select *
,count(*) over(partition by lic_plate) as chk
,timediff(now(),datetime) as time_parked
from t
) t
where chk%2 != 0
id
lic_plate
datetime
time_parked
12
5678-PC
2022-10-07 15:14:17
00:29:58
Fiddle

MySQL - Iterate Through Table

I'm trying to figure out how I can iterate through a table to get a count of how many days an id is not in an Active status.
Say I have the following table:
id
Status
Date
1
Active
1/15/21
1
Inactive
1/13/21
1
Active
1/12/21
1
Inactive
1/9/21
1
Active
1/7/21
2
Active
1/5/21
2
Inactive
1/4/21
2
Inactive
1/2/21
2
Active
1/1/21
The desired output for this example should be:
id
Days Inactive
1
5
2
3
I would like to do a DATEDIFF() when the status changes from Active to Inactive.
I can't just do a SELECT for Status <> Active and do a DATEDIFF() on the MAX and MIN dates in the range because it could go ACTIVE in between that range which would make the count different as seen in the example table.
I think I need a LOOP and/or CURSOR with a variable that gets added to as the count grows for each id, I'm just not sure exactly how to execute that.
Any suggestions or ideas are much appreciated!
Try this:
SELECT id,count(*) as days_inactive FROM table
WHERE status like 'inactive'
GROUP BY id
You can achieve what you want using variables. Just calculate the number of days from the previous date if and only if the id is the same and the current row is an active status, as follows.
select #id :=0, #lastDate := null;
select id, sum(DaysInactive) as DaysInactive
from (
select id, (case when id = #id and status='Active' then datediff(tranDate, #lastDate) else 0 end) as DaysInactive
, #id := id, #lastDate := tranDate
from tablename
order by id, TranDate
) as calc group by id
You can run the inner select separately to confirm that your calculations are correct. It should also work if your table has consecutive 'Inactive' or 'Active' statuses.
Adjust the above for your table and column names.

Selecting rows until a column value isn't the same

SELECT product.productID
, product.Name
, product.date
, product.status
FROM product
INNER JOIN shelf ON product.sheldID=shelf.shelfID
WHERE product.weekID = $ID
AND product.date < '$day'
OR (product.date = '$day' AND shelf.expire <= '$time' )
ORDER BY concat(product.date,shelf.expire)
I am trying to stop the SQL statement at a specific value e.g. bad.
I have tried using max-date, but am finding it hard as am making the time stamp in the query. (Combining date/time)
This example table shows that 3 results should be returned and if the status "bad" was the first result than no results should be returned. (They are ordered by date and time).
ProductID Date status
1 2017-03-27 Good
2 2017-03-27 Good
3 2017-03-26 Good
4 2017-03-25 Bad
5 2017-03-25 Good
Think I may have fixed it, I added this to my while loop.
The query gives the results in order by present to past using date and time, this while loop checks if the column of that row is equal to 'bad' if it is does something (might be able to use an array to fill it up with data). If not than the loop is broken.
I know it doesn't seem ideal but it works lol
while ($row = mysqli_fetch_assoc($result)) {
if ($row['status'] == "bad") {
$counter += 1;
}
else{
break;}
I will provide an answer just with your output as if it was just one table. It will give you the main ideia in how to solve your problem.
Basically I created a column called ord that will work as a row_number (MySql doesn't support it yet AFAIK). Then I got the minimum ord value for a bad status then I get everything from the data where ord is less than that.
select y.*
from (select ProductID, dt, status, #rw:=#rw+1 ord
from product, (select #rw:=0) a
order by dt desc) y
where y.ord < (select min(ord) ord
from (select ProductID, status, #rin:=#rin+1 ord
from product, (select #rin:=0) a
order by dt desc) x
where status = 'Bad');
Result will be:
ProductID dt status ord
-------------------------------------
1 2017-03-27 Good 1
2 2017-03-27 Good 2
3 2017-03-26 Good 3
Also tested with the use case where the Bad status is the first result, no results will be returned.
See it working here: http://sqlfiddle.com/#!9/28dda/1

SQL group by, how to define which record for each group, eg. the latest, is used

I have a table that stores a reference for each product-identifier, however there are some duplicate records - ie. a product may have been submitted more than once so has more than one reference. Each record is timestamped with the updated column.
I need a query that will only give one (non-empty) reference per product-identifier but that crucially will only select the LATEST record for each product.
So if the original table is this:
id updated product-identifier reference
------------------------------------------------------------
1 2014-11-10 07:47:02 9876543210123 98043hjdww98324322
2 2014-11-10 07:53:24 9897434243242 89f7e9wew329f080re
3 2014-11-12 10:51:10 9876543210123 48308402jfjewkfwek
4 2014-11-12 12:53:24 9876543210123 89739432bkjfekwjfk
5 2014-11-12 12:55:16 9876543210321 21321hhfioefhewfoe
6 2014-11-13 01:01:10 9897434243242
7 2014-11-13 01:05:24 9897434243242 1232423jhdksffewfe
The query should return just these records:
id updated product-identifier reference
------------------------------------------------------------
4 2014-11-12 12:53:24 9876543210123 89739432bkjfekwjfk
5 2014-11-12 12:55:16 9876543210321 21321hhfioefhewfoe
7 2014-11-13 01:05:24 9897434243242 1232423jhdksffewfe
I have tried
SELECT * FROM tablename WHERE reference !='' GROUP BY product-identifier ORDER BY updated DESC
and this gives only one record for each product, but not the latest - it is grouping before sorting.
Help greatly appreciated!
I usually do this by having a subquery that selects the highest timestamp for each group (product_identfier in your case) and then use that to select the row I want. Like this
select *
from tablename a
where a.updated = (select max(updated)
from tablename b
where a.product_identifier = b.product_identifier)
There are many ways to do this. If you want the latest record, here is a method using not exists:
select t.*
from tablename t
where not exists (select 1
from tablename t2
where t2.product_identifier = t.product_identifier and
t2.updated > t.updated
);

SQL Query Help - Grouping By Sequences of Digits

I have a table, which includes the following columns and data:
id dtime instance data dtype
1 2012-10-22 10000 d 1
2 2012-10-22 10000 d 1
..
7 2012-10-22 10004 d 1
..
15 2012-10-22 10000 # 1
16 2012-10-22 10004 d 1
17 2012-10-22 10000 d 1
I want to group sequences of 'd's in the data column, with the '#' at the end of the sequence.
This could have been done by grouping via the instance column, which is an individual stream of data, however there can be multiple sequences within the stream.
I also want to end a sequence if there are no data columns in the same instance for, say, 3 seconds after the last data of that instance and no '#'s have been found within that interval.
I have managed to do exactly this using cursors and while loops, which worked reasonably well for tables with 1000s of rows, however this query will be used on far more rows eventually, and these two methods would take around a minute with a dataset of just 3-5000 rows.
Reading on this website and others, it seems that set-based logic may be the way to go, however I can think of no way to do what I need without some kind of loop on each row that compares it to every other to build the 'sequences'.
If anyone could help, or point me in the direction of something that could, it would be greatly appreciated. :)
I would ideally like the data to be output in the following format:
datacount instance lastdata dtime
20 10000 # 2012-10-22
19 10000 d 2012-10-22
22 10004 # 2012-10-22
20 10022 # 2012-10-22
Where (datacount) is a count of the number of rows in a 'sequence' (which is the data leading up to a '#' or 3 second delay), (instance) is the instance ID from the original table, (lastdata) is the last data value in the sequence, (dtime) is the datetime value of the last data value.
Let me show you how to do this for the final '#'. The time difference follows a similar idea. The key idea is to get the next '#' after the current row. For this you need a correlated subquery. After that, you can do a group by:
select groupid, count(*) as NumInSeq, max(dtime) as LastDateTime
from (select t.*,
(select min(t2.id) from t t2 where t2.id > t.id and t2.data = '#'
) as groupid
from t
) t
group by groupid
Handling the time sequence is a bit more complicated. It is something like this:
select groupid, count(*) as NumInSeq, max(dtime) as LastDateTime,
(case when sum(case when data = '#' then 1 else 0 end) > 0 then '#' else 'd' end) as FinalData
from (select t.*,
(select min(t2.id)
from t t2
where t2.id > t.id and
(t2.data = '#' or UNIX_TIMESTAMP(t2.dtime) - UNIX_TIMESTAMP(t.dtime) < 3
) as groupid
from t
) t
group by groupid