How convert floats into time duration - sql

In a SQL Server table I have two number columns (float to be exact):
Numberfield_01 has value 3,50
Numberfield_02 has value 1,25
These number fields represent hours (European timescale).
Numberfield_02 stands for 1 hours and a quarter of an hour.
With SUM the outcome of these two fields is 4,75 (4 hours and 3 quarters of an hour)
Now I want to convert this outcome of 4,75 into a new field (type float): 04:45 via a SELECT statement.
How do I accomplish that?

DECLARE #TABLE TABLE (Numberfield_01 FLOAT, Numberfield_02 FLOAT)
INSERT INTO #TABLE VALUES (3.50 , 1.25)
SELECT CONVERT(TIME(0), DATEADD(MINUTE, 60*(Numberfield_01 + Numberfield_02), 0))
FROM #TABLE
RESULT : 04:45:00

Related

Subtracting Time Intervals in SQL

I have a table with 2 rows of time intervals. The data is stored as a varchar containing the hour and minutes, like the sample data below. I need to find a way to subtract the first column of data from the second and keep it as a time interval format. As seen below in the following table, I want to subtract colB from colA, to get the numbers in desiredOutcome displayed in the same format.
colA
colB
desiredOutcome
10:30
1:30
9:00
1:50
1:47
0:03
20:10
15:15
4:55
250:10
50:05
200:05
Any and all help is greatly appreciated
EDIT:
To clarify, these are time intervals, not times of the day. I have added more data to the table to show this, it is the amount of time taken in hours and minutes to perform a task. Also, the data is stored in varchar, but I can just use CAST() to change that.
Will say would have recommended storing this as simple minutes using a data type like int, then format for the user to your HH:MM format. It would make calculations like this a lot easier.
So for example instead of storing 250:10, would have stored it as 15010 minutes
Below I converted your HH:MM time lengths to minutes, did the calculation, and then formatted it back again.
Calculating Difference Between HH:MM Time Lengths
DROP TABLE IF EXISTS #Interval
CREATE TABLE #Interval (ID INT IDENTITY(1,1) PRIMARY KEY, colA Varchar(10),colB Varchar(10))
INSERT INTO #Interval
VALUES
('10:30','1:30')
,('1:50','1:47')
,('20:10','15:15')
,('250:10','50:05')
SELECT *
,DesiredOutcome = CONCAT(FLOOR(DeltaInMinutes/60.0),':',FORMAT(DeltaInMinutes % 60,'0#'))
FROM #Interval AS A
/*Find the colons*/
CROSS APPLY (
SELECT idxA = NULLIF(CHARINDEX(':',A.ColA),0)
,idxB = NULLIF(CHARINDEX(':',A.ColB),0)
) AS B
/*Break up into hours and minutes*/
CROSS APPLY(
SELECT HrsA = ISNULL(SUBSTRING(A.ColA,0,idxA),0)
,HrsB = ISNULL(SUBSTRING(A.ColB,0,idxB),0)
,MinutesA = ISNULL(SUBSTRING(A.ColA,idxA+1,100),0)
,MinutesB = ISNULL(SUBSTRING(A.ColB,idxB+1,100),0)
) AS C
/*Find total duration in minutes*/
CROSS APPLY (
SELECT TotalMinutesA = (HrsA * 60) + MinutesA
,TotalMinutesB = (HrsB * 60) + MinutesB
) AS D
/*Calculate delta*/
CROSS APPLY (
SELECT DeltaInMinutes = TotalMinutesA - TotalMinutesB
) AS E

Decimal to time SQL Server

I have two decimals that are represented as time in SQL Server:
[Driver_Hours_QT]
[Driver_Minutes_QT]
Result:
Driver_Hours_QT = 202.00000
Driver_Minutes_QT = 41.00000
I would like to concat these to get one time represented as 202.41 in order to divide by # of bills (394) to get a bill per hour.
How would I do this?
Based on your examples, you want arithmetic:
Driver_Hours_QT + Driver_Minutes_QT / 100.0
For bills per hour - you want to divide by the time
declare #hours as decimal
set #hours = 202
declare #mins as decimal
set #mins = 41
declare #bills as integer
set #bills =394
select #Bills/(#hours+#mins/60)
Obviously if you want Time per bill, then it's
select (#hours+#mins/60)/#Bills

SQL Server : average count of alerts per day, not including days with no alerts

I have a table that acts as a message log, with the two key tables being TIMESTAMP and TEXT. I'm working on a query that grabs all alerts (from TEXT) for the past 30 days (based on TIMESTAMP) and gives a daily average for those alerts.
Here is the query so far:
--goback 30 days start at midnight
declare #olderdate as datetime
set #olderdate = DATEADD(Day, -30, DATEDIFF(Day, 0, GetDate()))
--today at 11:59pm
declare #today as datetime
set #today = dateadd(ms, -3, (dateadd(day, +1, convert(varchar, GETDATE(), 101))))
print #today
--Grab average alerts per day over 30 days
select
avg(x.Alerts * 1.0 / 30)
from
(select count(*) as Alerts
from MESSAGE_LOG
where text like 'The process%'
and text like '%has alerted%'
and TIMESTAMP between #olderdate and #today) X
However, I want to add something that checks whether there were any alerts for a day and, if there are no alerts for that day, doesn't include it in the average. For example, if there are 90 alerts for a month but they're all in one day, I wouldn't want the average to be 3 alerts per day since that's clearly misleading.
Is there a way I can incorporate this into my query? I've searched for other solutions to this but haven't been able to get any to work.
This isn't written for your query, as I don't have any DDL or sample data, thus I'm going to provide a very simple example instead of how you would do this.
USE Sandbox;
GO
CREATE TABLE dbo.AlertMessage (ID int IDENTITY(1,1),
AlertDate date);
INSERT INTO dbo.AlertMessage (AlertDate)
VALUES('20190101'),('20190101'),('20190105'),('20190110'),('20190115'),('20190115'),('20190115');
GO
--Use a CTE to count per day:
WITH Tots AS (
SELECT AlertDate,
COUNT(ID) AS Alerts
FROM dbo.AlertMessage
GROUP BY AlertDate)
--Now the average
SELECT AVG(Alerts*1.0) AS DayAverage
FROM Tots;
GO
--Clean up
DROP TABLE dbo.AlertMessage;
You're trying to compute a double-aggregate: The average of daily totals.
Without using a CTE, you can try this as well, which is generalized a bit more to work for multiple months.
--get a list of events per day
DECLARE #Event TABLE
(
ID INT NOT NULL IDENTITY(1, 1)
,DateLocalTz DATE NOT NULL--make sure to handle time zones
,YearLocalTz AS DATEPART(YEAR, DateLocalTz) PERSISTED
,MonthLocalTz AS DATEPART(MONTH, DateLocalTz) PERSISTED
)
/*
INSERT INTO #Event(EntryDateLocalTz)
SELECT DISTINCT CONVERT(DATE, TIMESTAMP)--presumed to be in your local time zone because you did not specify
FROM dbo.MESSAGE_LOG
WHERE UPPER([TEXT]) LIKE 'THE PROCESS%' AND UPPER([TEXT]) LIKE '%HAS ALERTED%'--case insenitive
*/
INSERT INTO #Event(DateLocalTz)
VALUES ('2018-12-31'), ('2019-01-01'), ('2019-01-01'), ('2019-01-01'), ('2019-01-12'), ('2019-01-13')
--get average number of alerts per alerting day each month
-- (this will not return months with no alerts,
-- use a LEFT OUTER JOIN against a month list table if you need to include uneventful months)
SELECT
YearLocalTz
,MonthLocalTz
,AvgAlertsOfAlertingDays = AVG(CONVERT(REAL, NumDailyAlerts))
FROM
(
SELECT
YearLocalTz
,MonthLocalTz
,DateLocalTz
,NumDailyAlerts = COUNT(*)
FROM #Event
GROUP BY YearLocalTz, MonthLocalTz, DateLocalTz
) AS X
GROUP BY YearLocalTz, MonthLocalTz
ORDER BY YearLocalTz ASC, MonthLocalTz ASC
Some things to note in my code:
I use PERSISTED columns to get the month and year date parts (because I'm lazy when populating tables)
Use explicit CONVERT to escape integer math that rounds down decimals. Multiplying by 1.0 is a less-readable hack.
Use CONVERT(DATE, ...) to round down to midnight instead of converting back and forth between strings
Do case-insensitive string searching by making everything uppercase (or lowercase, your preference)
Don't subtract 3 milliseconds to get the very last moment before midnight. Change your semantics to interpret the end of a time range as exclusive, instead of dealing with the precision of your datatypes. The only difference is using explicit comparators (i.e. use < instead of <=). Also, DATETIME resolution is 1/300th of a second, not 3 milliseconds.
Avoid using built-in keywords as column names (i.e. "TEXT"). If you do, wrap them in square brackets to avoid ambiguity.
Instead of dividing by 30 to get the average, divide by the count of distinct days in your results.
select
avg(x.Alerts * 1.0 / x.dd)
from
(select count(*) as Alerts, count(distinct CAST([TIMESTAMP] AS date)) AS dd
...

Join Table variable with a Table - SQL

This question might looks simple and repeated, Since I am beginner in SQL, I have stuck up with this problem.
I have created a table variable to store hour range in a 24 hr format. Here is the code
DECLARE #TIMERANGE TABLE ([TIME] NVARCHAR(MAX))
;with hrs (time)
AS
(
SELECT 0
UNION ALL
SELECT time+1
FROM hrs WHERE time<23
)
INSERT INTO #TIMERANGE select
RIGHT ('0000' + CONVERT(VARCHAR, time), 4) + '-' + RIGHT('0000' + CONVERT(VARCHAR, time + 1), 4) AS [TIME]
from hrs
output for this table is:
TIME
0000-0001
0001-0002
0002-0003
0003-0004
0004-0005
0005-0006
0006-0007
0007-0008
0008-0009
0009-0010
0010-0011
0011-0012
0012-0013
0013-0014
0014-0015
0015-0016
0016-0017
0017-0018
0018-0019
0019-0020
0020-0021
0021-0022
0022-0023
0023-0024
Condition is, I want to join this with my real table with a specific condition
Id Date Time Score
1 2008-01-01 00:05 15
2 2008-01-01 00:15 20
3 2008-01-02 10:15 05
4 2008-01-02 11.00 55
I want to find the sum of score in specific time range, Eg, 00.15 will falls in Time range 0000-0001.
Desired output is:
Time Range Score
0000-0001 25
........ ..
........ ..
Please Help
I am hoping I understood the requirements. I see why you did the CTE. I've done that to support graphs so every hour in the day is represented, with or without resulting data.
I re factored the query to produce the following:
declare #tmp TABLE (MyDate DATE, MyTime TIME,Score INT)
INSERT INTO #tmp VALUES('2008-01-01','00:05',15),
('2008-01-01','00:15',20),
('2008-01-02','10:15',05),
('2008-01-02','11:00',55)
SELECT SUM(Score) Score,datepart(hour,GETDATE()) TimeRange FROM #tmp Group By datepart(hour,MyTime)
The result will show the SUM (or average or whatever you need) by Hour. If you still need to graph the result THEN join back into your CTE on the Hour component of the time.
Hope this helps.

Group DateTime into 5,15,30 and 60 minute intervals

I am trying to group some records into 5-, 15-, 30- and 60-minute intervals:
SELECT AVG(value) as "AvgValue",
sample_date/(5*60) as "TimeFive"
FROM DATA
WHERE id = 123 AND sample_date >= 3/21/2012
i want to run several queries, each would group my average values into the desired time increments. So the 5-min query would return results like this:
AvgValue TimeFive
6.90 1995-01-01 00:05:00
7.15 1995-01-01 00:10:00
8.25 1995-01-01 00:15:00
The 30-min query would result in this:
AvgValue TimeThirty
6.95 1995-01-01 00:30:00
7.40 1995-01-01 01:00:00
The datetime column is in yyyy-mm-dd hh:mm:ss format
I am getting implicit conversion errors of my datetime column. Any help is much appreciated!
Using
datediff(minute, '1990-01-01T00:00:00', yourDatetime)
will give you the number of minutes since 1990-1-1 (you can use the desired base date).
Then you can divide by 5, 15, 30 or 60, and group by the result of this division.
I've cheked it will be evaluated as an integer division, so you'll get an integer number you can use to group by.
i.e.
group by datediff(minute, '1990-01-01T00:00:00', yourDatetime) /5
UPDATE As the original question was edited to require the data to be shown in date-time format after the grouping, I've added this simple query that will do what the OP wants:
-- This convert the period to date-time format
SELECT
-- note the 5, the "minute", and the starting point to convert the
-- period back to original time
DATEADD(minute, AP.FiveMinutesPeriod * 5, '2010-01-01T00:00:00') AS Period,
AP.AvgValue
FROM
-- this groups by the period and gets the average
(SELECT
P.FiveMinutesPeriod,
AVG(P.Value) AS AvgValue
FROM
-- This calculates the period (five minutes in this instance)
(SELECT
-- note the division by 5 and the "minute" to build the 5 minute periods
-- the '2010-01-01T00:00:00' is the starting point for the periods
datediff(minute, '2010-01-01T00:00:00', T.Time)/5 AS FiveMinutesPeriod,
T.Value
FROM Test T) AS P
GROUP BY P.FiveMinutesPeriod) AP
NOTE: I've divided this in 3 subqueries for clarity. You should read it from inside out. It could, of course, be written as a single, compact query
NOTE: if you change the period and the starting date-time you can get any interval you need, like weeks starting from a given day, or whatever you can need
If you want to generate test data for this query use this:
CREATE TABLE Test
( Id INT IDENTITY PRIMARY KEY,
Time DATETIME,
Value FLOAT)
INSERT INTO Test(Time, Value) VALUES('2012-03-22T00:00:22', 10)
INSERT INTO Test(Time, Value) VALUES('2012-03-22T00:03:22', 10)
INSERT INTO Test(Time, Value) VALUES('2012-03-22T00:04:45', 10)
INSERT INTO Test(Time, Value) VALUES('2012-03-22T00:07:21', 20)
INSERT INTO Test(Time, Value) VALUES('2012-03-22T00:10:25', 30)
INSERT INTO Test(Time, Value) VALUES('2012-03-22T00:11:22', 30)
INSERT INTO Test(Time, Value) VALUES('2012-03-22T00:14:47', 30)
The result of executing the query is this:
Period AvgValue
2012-03-22 00:00:00.000 10
2012-03-22 00:05:00.000 20
2012-03-22 00:10:00.000 30
Building on #JotaBe's answer (to which I cannot comment on--otherwise I would), you could also try something like this which would not require a subquery.
SELECT
AVG(value) AS 'AvgValue',
-- Add the rounded seconds back onto epoch to get rounded time
DATEADD(
MINUTE,
(DATEDIFF(MINUTE, '1990-01-01T00:00:00', your_date) / 30) * 30,
'1990-01-01T00:00:00'
) AS 'TimeThirty'
FROM YourTable
-- WHERE your_date > some max lookback period
GROUP BY
(DATEDIFF(MINUTE, '1990-01-01T00:00:00', your_date) / 30)
This change removes temp tables and subqueries. It uses the same core logic for grouping by 30 minute intervals but, when presenting the data back as part of the result I'm just reversing the interval calculation to get the rounded date & time.
So, in case you googled this, but you need to do it in mysql, which was my case:
In MySQL you can do
GROUP BY
CONCAT(
DATE_FORMAT(`timestamp`,'%m-%d-%Y %H:'),
FLOOR(DATE_FORMAT(`timestamp`,'%i')/5)*5
)
In the new SQL Server 2022, you can use DATE_BUCKET, this rounds it down to the nearest interval specified.
SELECT
DATE_BUCKET(minute, 5, d.sample_date) AS TimeFive,
AVG(d.value) AS AvgValue
FROM DATA d
WHERE d.id = 123
AND d.sample_date >= '20121203'
GROUP BY
DATE_BUCKET(minute, 5, d.sample_date);
You can use the following statement, this removed the second component and calculates the number of minutes away from the five minute mark and uses this to round down to the time block. This is ideal if you want to change your window, you can simply change the mod value.
select dateadd(minute, - datepart(minute, [YOURDATE]) % 5, dateadd(minute, datediff(minute, 0, [YOURDATE]), 0)) as [TimeBlock]
This will help exactly what you want
replace dt - your datetime c - call field astro_transit1 - your table 300 refer 5 min so add 300 each time for time gap increase
SELECT FROM_UNIXTIME( 300 * ROUND( UNIX_TIMESTAMP( r.dt ) /300 ) ) AS 5datetime, ( SELECT r.c FROM astro_transit1 ra WHERE ra.dt = r.dt ORDER BY ra.dt DESC LIMIT 1 ) AS first_val FROM astro_transit1 r GROUP BY UNIX_TIMESTAMP( r.dt ) DIV 300 LIMIT 0 , 30