T-SQL: Check temperature over 24 hour period - sql

I have a SQL Server database with two values I'm interested in:
dtime - datetime
temperature - varchar
The table is fed by an external process that takes a building's temperature every 30 minutes. I'm interested in triggering an alert if the temperature exceeds 80 degrees for 48 periods (24 hours).
I think this needs to be an external process that scans the table and sends an alert when this condition is met. I'm struggling with writing the SQL to do this.
EDIT
The data I'm pulling in comes in on a weekly basis. During this week I need to see if at any time in a 24-hour period the temperature has exceeded 80 degrees. The air conditioning could fail at any time and span two days or more, so I need to potentially check this across multiple days. The temperature is taken every half hour, so during the week I need to check if there are 48+ instances where the temperature exceeded 80 degrees.
Sample data:
10/1/2012 12:00:00 AM | 70 | {ok}
10/1/2012 12:30:00 AM | 70 | {ok}
10/1/2012 1:00:00 AM | 70 | {ok}
10/1/2012 1:30:00 AM | 75 | {ok}
10/1/2012 2:00:00 AM | 75 | {ok}
10/1/2012 2:30:00 AM | 80 | {ok}

You can use ALL:
IF 80 < ALL(
SELECT temperature
FROM (
SELECT temperature, dtime,
RN = ROW_NUMBER() OVER (ORDER BY dtime DESC)
FROM dbo.Temps
) X
WHERE RN <= 48
)
SELECT 'ALERT, the last 48 measurements exceeded 80 degrees!'
ELSE
SELECT 'everything is okay';
Fiddle: http://sqlfiddle.com/#!6/4e2c8/7/0
Edit: As Blam has mentioned this can be simplified by using TOP
IF 80 < ALL(
SELECT TOP 48 temperature
FROM dbo.Temps
ORDER BY dtime DESC
)
SELECT 'ALERT, the last 48 measurements exceeded 80 degrees!'
ELSE
SELECT 'everything is okay';

select 'alert'
where not exists (
select 1
from MyTable
where dtime > dateadd(day, -1, getdate())
and cast(temperature as int) <= 80
)

Related

Sql query to select the records for past 60 seconds and compare the temperature of the selected records and if any record has higher value then ignore

I am trying to eliminate the data anomalies in the data I am receiving from eventhub and send only selected data to azure functions through Azure stream analytics for that I am writing a sql query where I need some help
Requirement: I need to collect the past 60 seconds data and need to group by Id and compare the records that I received in the 60 seconds and If any record value is way higher than the selected values than ignore that record (for example, I will collect the 4 records in past 60 seconds and if the data is 40 40 40 40 5. We should drop the 5. Example 2 - 20 20 20 500 drop the 500. ).
My sql table will be something like this:
id Temp date datetime
123 30 2023-01-01 2023-01-01 12:00:00
124 35 2023-01-01 2023-01-01 12:00:00
123 31 2023-01-01 2023-01-01 12:00:00
123 33 2023-01-01 2023-01-01 12:00:00
123 60 2023-01-01 2023-01-01 12:00:00
124 36 2023-01-01 2023-01-01 12:00:00
124 36 2023-01-01 2023-01-01 12:00:00
124 8 2023-01-01 2023-01-01 12:00:00
124 36 2023-01-01 2023-01-01 12:00:00
I need to eliminate the records that are not in the range with the other records
I'll leave the details of the comparison up to you, but you can use a CROSS APPLY to gather the data for comparison.
Something like:
SELECT *
FROM TemperatureData T
CROSS APPLY (
SELECT AVG(T2.Temp * 1.0) AS PriorAvgTemp, COUNT(*) As PriorCount
FROM TemperatureData T2
WHERE T2.id = T.id
AND T2.datetime >= DATEADD(second, -60, T.datetime)
AND T2.datetime < T.datetime
) P
WHERE T.Temp BETWEEN P.PriorAvgTemp - 10 AND P.PriorAvgTemp + 10
--OR P.PriorCount < 3 -- Should we allow if there is insufficient prior data
--AND P.PriorCount >= 3 -- Should we omit if there is insufficient prior data
Be sure you have an index on TemperatureData(id, datetime).
If you are willing to accept the last N values instead of a time range, windowed aggregate calculation may be more efficient.
SELECT *
FROM (
SELECT *,
AVG(T.Temp * 1.0)
OVER(PARTITION BY id ORDER BY datetime
ROWS BETWEEN 60 PRECEDING AND 1 PRECEDING)
AS PriorAvgTemp,
COUNT(*)
OVER(PARTITION BY id ORDER BY datetime
ROWS BETWEEN 60 PRECEDING AND 1 PRECEDING)
AS PriorCount
FROM TemperatureData T
) TT
WHERE TT.Temp BETWEEN TT.PriorAvgTemp - 10 AND TT.PriorAvgTemp + 10
--OR TT.PriorCount < 3 -- Should we allow if there is insufficient prior data
--AND TT.PriorCount >= 3 -- Should we omit if there is insufficient prior data
Please note: The above is untested code, which may need some syntax fixes and debugging. If you discover errors, please comment and I will correct the post.

Extract 30 minutes from timestamp and group it by 30 mins time interval -PGSQL

In PostgreSQL I am extracting hour from the timestamp using below query.
select count(*) as logged_users, EXTRACT(hour from login_time::timestamp) as Hour
from loginhistory
where login_time::date = '2021-04-21'
group by Hour order by Hour;
And the output is as follows
logged_users | hour
--------------+------
27 | 7
82 | 8
229 | 9
1620 | 10
1264 | 11
1990 | 12
1027 | 13
1273 | 14
1794 | 15
1733 | 16
878 | 17
126 | 18
21 | 19
5 | 20
3 | 21
1 | 22
I want the same output for same SQL for 30 mins. Please suggest
SELECT to_timestamp((extract(epoch FROM login_time::timestamp)::bigint / 1800) * 1800)::timestamp AS interval_30_min
, count(*) AS logged_users
FROM loginhistory
WHERE login_time::date = '2021-04-21' -- inefficient!
GROUP BY 1
ORDER BY 1;
Extracting the epoch gets the number of seconds since the epoch. Integer division truncates. Multiplying back effectively rounds down, achieving the same as date_trunc() for arbitrary time intervals.
1800 because 30 minutes contain 1800 seconds.
Detailed explanation:
Truncate timestamp to arbitrary intervals
The cast to timestamp makes me wonder about the actual data type of login_time? If it's timestamptz, the cast depends on your current time zone setting and sets you up for surprises if that setting changes. See:
How do I match an entire day to a datetime field?
Subtract hours from the now() function
Ignoring time zones altogether in Rails and PostgreSQL
Depending on the actual data type, and exact definition of your date boundaries, there is a more efficient way to phrase your WHERE clause.
You can change the column on which you're aggregating to use the minute too:
select
count(*) as logged_users,
CONCAT(EXTRACT(hour from login_time::timestamp), '-', CASE WHEN EXTRACT(minute from login_time::timestamp) < 30 THEN 0 ELSE 30 END) as HalfHour
from loginhistory
where login_time::date = '2021-04-21'
group by HalfHour
order by HalfHour;

Accessing all rows based on Max of Sum of Diff. between Start time and End time Columns

I'm new to SQL, so after trying a lot, I couldn't find appropriate solution on Stackoverflow. That's why I posted my question.[May be it's a repeating question]
I have 2 tables Driver and Ride. Structure given below:
Tables:
Person:
id Name Email reg_number
37 test1 test1#gmail.com 111111
38 test2 test2#gmail.com 222222
39 test3 test3#gmail.com 333333
40 test4 test4#gmail.com 444444
41 test5 test5#gmail.com 555555
42 test6 test6#gmail.com 666666
Rides:
id Person_id start_time end_time distance
23 38 2018-08-08T12:12:12 2018-08-08T13:12:12 1000
24 39 2018-08-08T12:12:12 2018-08-08T14:12:12 1100
25 40 2018-08-08T12:12:12 2018-08-08T13:12:12 1200
26 41 2018-08-08T12:12:12 2018-08-08T15:12:12 1300
27 42 2018-08-08T12:12:12 2018-08-08T15:12:12 600
28 42 2018-08-08T12:12:12 2018-08-08T13:12:12 700
29 41 2018-08-08T12:12:12 2018-08-08T16:12:12 800
My Query is : Person is a driver who rides cab.
start_time is the start time of his ride. end_time is the end time of his ride. Distance is distance in KM.
I want to pass start and end time to the query.
Result should include TOP 5 Persons with their email,name, total minutes of Ride, maximum ride duration in minutes.
Only rides that starts and ends within the mentioned durations should be counted.
Note: Total minutes of all rides by a person is the criteria for TOP.
Date/time functions are notoriously dependent on the database. You need to tag the database you are using.
I am going to assume that you are using MySQL.
You can answer the "top 5" by using just the rides table:
select r.person_id,
sum(to_seconds(r.end_time) - to_seconds(r.start_time)) as duration_seconds
from rides r
where r.start_time >= ? and
r.end_time <= ?
group by r.person_id
order by duration_seconds desc
limit 5;
This is basically what you want. Now you just need to add in the person information and convert the seconds to minutes:
select p.*, top5.duration_seconds / 60 as duration_minutes
from (select r.person_id,
sum(to_seconds(r.end_time) - to_seconds(r.start_time)) as duration_seconds
from rides r
where r.start_time >= ? and
r.end_time <= ?
group by r.person_id
order by duration_seconds desc
limit 5
) top5 join
person p
on top5.person_id = p.id;
Other databases have other ways of extracting the duration between two datetime values.
The ? is the placeholder for the values defining the time range.

Calculate fixed Cost/day for multiple services on same date

Desired Output table T with Calculated Cost column:
SvcID Code ID Date Mins Units Cost
1 3000 15 4/4/2016 60 10 70
2 3000 17 4/4/2016 45 10 0
3 3000 15 5/2/2016 30 10 70
4 3000 18 5/2/2016 60 10 0
5 3000 10 5/2/2016 30 10 0
6 4200 16 2/1/2016 60 4 60
7 4200 9 2/1/2016 30 2 30
Query for calculating and displaying:
SELECT
...
,CASE
WHEN Code=4200 THEN Units*15
WHEN Code=3000 THEN ?
END AS Cost
FROM ...
WHERE Code IN ('3000','4200')
GROUP BY ....;
Cost should be a total of 70 for all services offered on same date for Code 3000, irrespective of number of services offered. No relation between Minutes and Units for this Code for calculating Cost.
One way could be to calculate cost as 70 for any one service and make the remaining services cost 0 for same date. Can this be done in the CASE statement?
Any better way to achieve this?
You need to Investigate Window functions MSDN.
Your case would become something like this:
-- New select statament
SELECT
...
,CASE
WHEN Code=4200 THEN Units*15
WHEN Code=3000 THEN ( CASE WHEN DuplicateNum = 1 THEN 70 ELSE 0 END )?
END AS Cost
FROM(
-- Your current query (with case statement removed) and ROW_NUMBER() function added
SELECT
..., ROW_NUMBER() OVER( PARTITION BY Code, Date ORDER BY ID ) AS DuplicateNum
FROM ...
WHERE Code IN ('3000','4200')
GROUP BY ....
) AS YourCurrentQuery;

GROUP BY several hours

I have a table where our product records its activity log. The product starts working at 23:00 every day and usually works one or two hours. This means that once a batch started at 23:00, it finishes about 1:00am next day.
Now, I need to take statistics on how many posts are registered per batch but cannot figure out a script that would allow me achiving this. So far I have following SQL code:
SELECT COUNT(*), DATEPART(DAY,registrationtime),DATEPART(HOUR,registrationtime)
FROM RegistrationMessageLogEntry
WHERE registrationtime > '2014-09-01 20:00'
GROUP BY DATEPART(DAY, registrationtime), DATEPART(HOUR,registrationtime)
ORDER BY DATEPART(DAY, registrationtime), DATEPART(HOUR,registrationtime)
which results in following
count day hour
....
1189 9 23
8611 10 0
2754 10 23
6462 11 0
1885 11 23
I.e. I want the number for 9th 23:00 grouped with the number for 10th 00:00, 10th 23:00 with 11th 00:00 and so on. How could I do it?
You can do it very easily. Use DATEADD to add an hour to the original registrationtime. If you do so, all the registrationtimes will be moved to the same day, and you can simply group by the day part.
You could also do it in a more complicated way using CASE WHEN, but it's overkill on the view of this easy solution.
I had to do something similar a few days ago. I had fixed timespans for work shifts to group by where one of them could start on one day at 10pm and end the next morning at 6am.
What I did was:
Define a "shift date", which was simply the day with zero timestamp when the shift started for every entry in the table. I was able to do so by checking whether the timestamp of the entry was between 0am and 6am. In that case I took only the date of this DATEADD(dd, -1, entryDate), which returned the previous day for all entries between 0am and 6am.
I also added an ID for the shift. 0 for the first one (6am to 2pm), 1 for the second one (2pm to 10pm) and 3 for the last one (10pm to 6am).
I was then able to group over the shift date and shift IDs.
Example:
Consider the following source entries:
Timestamp SomeData
=============================
2014-09-01 06:01:00 5
2014-09-01 14:01:00 6
2014-09-02 02:00:00 7
Step one extended the table as follows:
Timestamp SomeData ShiftDay
====================================================
2014-09-01 06:01:00 5 2014-09-01 00:00:00
2014-09-01 14:01:00 6 2014-09-01 00:00:00
2014-09-02 02:00:00 7 2014-09-01 00:00:00
Step two extended the table as follows:
Timestamp SomeData ShiftDay ShiftID
==============================================================
2014-09-01 06:01:00 5 2014-09-01 00:00:00 0
2014-09-01 14:01:00 6 2014-09-01 00:00:00 1
2014-09-02 02:00:00 7 2014-09-01 00:00:00 2
If you add one hour to registrationtime, you will be able to group by the date part:
GROUP BY
CAST(DATEADD(HOUR, 1, registrationtime) AS date)
If the starting hour must be reflected accurately in the output (as 9, 23, 10, 23 rather than as 10, 0, 11, 0), you could obtain it as MIN(registrationtime) in the SELECT clause:
SELECT
count = COUNT(*),
day = DATEPART(DAY, MIN(registrationtime)),
hour = DATEPART(HOUR, MIN(registrationtime))
Finally, in case you are not aware, you can reference columns by their aliases in ORDER BY:
ORDER BY
day,
hour
just so that you do not have to repeat the expressions.
The below query will give you what you are expecting..
;WITH CTE AS
(
SELECT COUNT(*) Count, DATEPART(DAY,registrationtime) Day,DATEPART(HOUR,registrationtime) Hour,
RANK() over (partition by DATEPART(HOUR,registrationtime) order by DATEPART(DAY,registrationtime),DATEPART(HOUR,registrationtime)) Batch_ID
FROM RegistrationMessageLogEntry
WHERE registrationtime > '2014-09-01 20:00'
GROUP BY DATEPART(DAY, registrationtime), DATEPART(HOUR,registrationtime)
)
SELECT SUM(COUNT) Count,Batch_ID
FROM CTE
GROUP BY Batch_ID
ORDER BY Batch_ID
You can write a CASE statement as below
CASE WHEN DATEPART(HOUR,registrationtime) = 23
THEN DATEPART(DAY,registrationtime)+1
END,
CASE WHEN DATEPART(HOUR,registrationtime) = 23
THEN 0
END