How to convert varchar to int to get average? - sql-server

I would like to get the average of each user_id for a specific time period.
Since RIGHT works on strings , I have to convert it to int and get the average.
For that reason I get error:
Conversion failed when converting the varchar value '08:36' to data
type int.
SELECT
user_id,
avg(convert (int , right('0' + convert(VARCHAR(2),datediff (second, QueueEndDt,ConnClearDt)/ 60 % 60), 2) + ':' +
right('0' + convert(VARCHAR(2),datediff (second, QueueEndDt,ConnClearDt)% 60),2))) as average_minutes
FROM Detail
WHERE QueueEndDt between '2015-02-09 08:00:00.000' AND '2015-02-09 23:02:33.043'
group by user_id
Without using the average and convert I get the correct datediff.
I made a http://sqlfiddle.com/#!6/298eb with some data.
What I would like to output is average of each user_id.

This will return you the average difference in hh:mm:ss format. Note that hh may exceed to 24. Modify the query to add years, months and days part.
WITH CTE AS(
SELECT
user_id,
Average_Seconds = AVG(DATEDIFF(SECOND, QueueEndDt, ConnClearDt))
FROM details
WHERE
QueueEndDt BETWEEN '2015-02-09 08:00:00.000' AND '2015-02-09 23:02:33.043'
GROUP BY user_id
)
SELECT
user_id,
[Average hh:mm:ss] =
CONVERT(VARCHAR(12), Average_Seconds / 60 / 60) + ':'
+ RIGHT('00' + CONVERT(VARCHAR(2), Average_Seconds / 60 % 60), 2) + ':'
+ RIGHT('00' + CONVERT(VARCHAR(2), Average_Seconds % 60), 2)
FROM CTE
RESULT
user_id Average hh:mm:ss
---------- ------------------
number1 13:44:16
number2 13:50:36
number3 13:46:33

Related

MSSQL: Cannot perform an aggregate function on an expression containing an aggregate or a subquery

I want to calculation MTTR.This is formula Sum(Finish_Date-Start_Date)/n I need to calculate in SQL Server.
SELECT dbo.BOYS.Machine_ID,
SUM(DATEDIFF(SECOND, Start_Date, Finish_Date)) as Total_TTR
, SUM(1) AS n
, SUM(DATEDIFF(SECOND, Start_Date, Finish_Date))/SUM(1) AS MTTR
FROM [BT].[dbo].[BOYS]
GROUP BY Machine_ID
I calculate it second type.
MTTR
3600
165600
...
in table.
but I want it in hh:mm:ss format.
MTTR
01:25:13 etc.
...
I use
SELECT dbo.BOYS.Machine_ID,
SUM(DATEDIFF(SECOND, First_Date, Second_Date)) as Total_DT
,SUM(1) AS n, SUM(DATEDIFF(SECOND, First_Date, Second_Date))/SUM(1) AS MDT
, Convert(varchar(6), ((SUM(DATEDIFF(SECOND, First_Date, Second_Date))/SUM(1))/3600)
+ ':'
+ RIGHT('0' + CONVERT(varchar(2),(SUM(DATEDIFF(SECOND, First_Date, Second_Date)/SUM(1)) % 3600) / 60), 2)
+ ':'
+ RIGHT('0' + CONVERT(varchar(2),(SUM(DATEDIFF(SECOND, First_Date, Second_Date))/SUM(1)) %60),2)) AS 'MTTR'
FROM [BT].[dbo].[BOYS]
GROUP BY Machine_ID
but it throws an error:
Cannot perform an aggregate function on an expression containing an aggregate or a subquery.
You can put the query with the group by in a subquery, and then select on that resultset, then you don't have to worry about aggregates in the upper select.
Look at this example
first I need to create some sample data
declare #boys table (machine_id int, start_Date datetime, finish_Date datetime)
insert into #boys values
(1, '20220728 08:00:00', '20220728 09:15:30'),
(1, '20220728 10:30:00', '20220728 10:45:00'),
(2, '20220728 08:30:00', '20220728 14:12:31')
now for the query, it was not clear to me if you want to build the time for the average or for the total, so I put both in the query, just pick the one you need
select t.Machine_ID,
t.n,
t.Total_DT,
t.MDT,
-- time base on the average
Convert( varchar(6), (MDT / 3600))
+ ':'
+ RIGHT('0' + CONVERT(varchar(2), (MDT % 3600) / 60), 2)
+ ':'
+ RIGHT('0' + CONVERT(varchar(2), (MDT % 60)), 2)
as MTTR_avg,
-- time base on the total
Convert( varchar(6), (Total_DT / 3600))
+ ':'
+ RIGHT('0' + CONVERT(varchar(2), (Total_DT % 3600) / 60), 2)
+ ':'
+ RIGHT('0' + CONVERT(varchar(2), (Total_DT % 60)), 2)
as MTTR_tot
from ( SELECT b.Machine_ID,
SUM(DATEDIFF(SECOND, b.Start_Date, b.Finish_Date)) as Total_DT,
SUM(1) AS n,
SUM(DATEDIFF(SECOND, b.Start_Date, b.Finish_Date)) / SUM(1) AS MDT
FROM #boys b
group by Machine_ID
) t
It also makes the convert much more easy to read.
It results in this
Machine_ID
n
Total_DT
MDT
MTTR_avg
MTTR_tot
1
2
5430
2715
0:45:15
1:30:30
2
1
20551
20551
5:42:31
5:42:31
Your primary issue is this line, with spacing you can see the issue is a bracket in the wrong place, so you have SUM(1) inside the other SUM.
RIGHT(
'0' +
CONVERT(
varchar(2),
(
SUM(
DATEDIFF(
SECOND,
First_Date,
Second_Date
)
/ SUM(1)
)
% 3600
)
/ 60
),
2
)
You can clean this up considerably.
Firstly, put the DATEDIFF in a CROSS APPLY(VALUES so you don't have to repeat it.
SUM(1) is the same as COUNT(*), and SUM(x) / COUNT(*) is the same as AVG(x).
Use TIMEFROMPARTS to create a time value.
Don't use three-part naming. Your connection should define which database you are using.
Don't quote table and column names unless you have to. And if you do, do it with [].
SELECT
b.Machine_ID
, SUM(v.diff) AS Total_DT
, COUNT(*) AS n
, AVG(v.diff) AS MDT
, TIMEFROMPARTS(
AVG(v.diff) / 3600,
(AVG(v.diff) % 3600) / 60,
AVG(v.diff) % 60,
0, 0) AS MTTR
FROM dbo.BOYS b
CROSS APPLY (VALUES (
DATEDIFF(SECOND, b.First_Date, b.Second_Date)
) ) v(diff)
GROUP BY
b.Machine_ID;
db<>fiddle

Convert number of minutes to hh:mm

I have a column in a table that stores the number of minutes as a numeric(18,4) field named [course_access_minutes].
The stored values come from a blackboard database and look like this:
0.0500
0.0667
0.3667
up to
314.0833
625.8167
How do I convert these to time hh:mm, I've had a good look at the database documentation and all I can find is
course_access_minutes numeric(18,4) This is the number of minutes that the user accesses this course in total during this login session.
Can I assume that I can make a direct conversion from minutes into hours? I think I will take any values below 1 as 0 minutes. What is the best way to do this in SQL? Thanks in advance for your help.
Try this
SELECT CONVERT(varchar, DATEADD(s, 625.8167 * 60, 0), 108)
If the duration is longer than 24 hours you can use this
SELECT CONVERT(varchar, CAST(1877.4501 * 60 AS int) / 3600)
+ RIGHT(CONVERT(varchar, DATEADD(s, 1877.4501 * 60, 0), 108), 6)
You could use FLOOR like this
DECLARE #SampleData AS TABLE
(
Minutes numeric(18,4)
)
INSERT INTO #SampleData
VALUES
( 0.0500),
( 1.0500),
( 30.0500),
( 80.0500),
( 314.0833),
( 625.8167)
SELECT CONCAT(floor(sd.Minutes/60),':', CASE WHEN sd.Minutes - floor(sd.Minutes/60)*60 < 1 THEN '0'
ELSE FLOOR(sd.Minutes - floor(sd.Minutes/60)*60 )
END) AS hours
FROM #SampleData sd
Returns
hours
0:0
0:1
0:30
1:20
5:14
10:25
WITH _Samples AS (
SELECT CONVERT(numeric(18, 4), 0.0500) [course_access_minutes]
UNION ALL SELECT 0.0667
UNION ALL SELECT 0.3667
UNION ALL SELECT 314.0833
UNION ALL SELECT 625.8167
)
SELECT
S.course_access_minutes,
-- split out the number
FLOOR(S.course_access_minutes / 60) [hours],
FLOOR(S.course_access_minutes % 60) [minutes],
FLOOR((S.course_access_minutes - FLOOR(S.course_access_minutes)) * 60) [seconds],
-- to a string
CONVERT(varchar(10), FLOOR(S.course_access_minutes / 60))
+ ':' + RIGHT('00' + CONVERT(varchar(10), FLOOR(S.course_access_minutes % 60)), 2)
+ ':' + RIGHT('00' + CONVERT(varchar(10), FLOOR((S.course_access_minutes - FLOOR(S.course_access_minutes)) * 60)), 2) [time_string],
-- You could consider converting to the time data type if the values will never exceed the limit
-- time supports 00:00:00.0000000 through 23:59:59.9999999
-- 0 through 1439.9833333 ... 23 * 60 = 1380 + 59 = 1439 + (59 / 60) = 1439.9833333
-- (see: https://learn.microsoft.com/en-us/sql/t-sql/data-types/time-transact-sql)
CONVERT(time,
CONVERT(varchar(10), FLOOR(S.course_access_minutes / 60))
+ ':' + RIGHT('00' + CONVERT(varchar(10), FLOOR(S.course_access_minutes % 60)), 2)
+ ':' + RIGHT('00' + CONVERT(varchar(10), FLOOR((S.course_access_minutes - FLOOR(S.course_access_minutes)) * 60)), 2)
) [time]
FROM
_Samples S
(It wouldn't be difficult to further this idea and split out the fractional seconds as well.)
Which yields:
course_access_minutes hours minutes seconds time_string time
---------------------- ------ -------- -------- ------------ ----------------
0.0500 0 0 3 0:00:03 00:00:03.0000000
0.0667 0 0 4 0:00:04 00:00:04.0000000
0.3667 0 0 22 0:00:22 00:00:22.0000000
314.0833 5 14 4 5:14:04 05:14:04.0000000
625.8167 10 25 49 10:25:49 10:25:49.0000000
Note that this is going to be like Greg's answer, but I wanted to explain and simplify it.
You have minutes, so dividing them by 60 and flooring it (removing the decimal) gives the hours (without the minutes).
If you take the total minutes again, and remove (mod it by) the floored hours - which requires conversion to minutes by multiplying by 60 - you are left with the remaining minutes by essentially just finding out what is left after taking away that many groups of sixties:
SELECT FLOOR(course_access_minutes / 60) as Hours,
(FLOOR(course_access_minutes) % 60) as Minutes
FROM MyTable
If you want the decimal to appear for the amount of minute fractions (you want the seconds to appear, in decimal form), remove FLOOR.
If you want seconds in real numbers, keep FLOOR and use what Greg had: FLOOR((S.course_access_minutes - FLOOR(S.course_access_minutes)) * 60) for seconds. Be careful with the parenthesis, though, because you can end up accidentally flooring your decimaled minutes and get 0, and then 0*60 is zero:
FLOOR(
(
course_access_minutes -
FLOOR(course_access_minutes)
) * 60
) as Seconds

SQL Adding integer strings with zero values

I am trying to add strings which are integers. I have 201404 as input and I need it to be converted to 201503 so the only way to do this is to increase the year (2014) by 1 and decrease the month 02 by 1.
I have tried the below but the leading zero in the month does not seem to preserve:
DECLARE #YearMonth INT = 201404
, #left INT = 0
, #right INT = 0
SET #YearMonth = CAST(#YearMonth AS VARCHAR(6))
SET #left = CAST(LEFT(#YearMonth, 4) + 1 AS VARCHAR(MAX))
SET #right = RIGHT(#YearMonth, 2) - 1
SET #right = CAST(#right AS VARCHAR(2))
SET #right = RIGHT(('0' + CAST(#right AS VARCHAR(2))), 2)
PRINT #left
PRINT RIGHT('0' + LTRIM(RTRIM(#right)), 6)
Dealing with integer YYYYMM format can be difficult when adding and subtracting months. One method is to convert to a number of months, and then convert back to the format. So, this converts the value to a number of months
select (#YearMonth / 100) * 12 + (#YearMonth % 100)
Then we can add a number, such as 11 and convert back to the integer format:
select (( (#YearMonth / 100) * 12 + (#YearMonth % 100) + 11) / 12) * 100 +
( (#YearMonth / 100) * 12 + (#YearMonth % 100) + 11) % 12)
) as yyyymm
Another method that might be simpler is to use date arithmetic:
select dateadd(11, month, cast(#YearMonth as varchar(255)) + '01')
This returns a date. You can convert it back to the number as:
select (year(dateadd(11, month, cast(#YearMonth as varchar(255)) + '01')) * 100 +
month(dateadd(11, month, cast(#YearMonth as varchar(255)) + '01'))
) as yyyymm
Use REPLICATE
replicate('0', 2 - len(#right)) + #right
Just ran this:
DECLARE #YearMonth INT = 201404;
SELECT CONVERT(VARCHAR(6), DATEPART(YEAR, T.Data) + 1) + RIGHT(100 + DATEPART(MONTH, T.Data) -1, 2)
FROM (VALUES (CONVERT(VARCHAR(8), #YearMonth) + '01')) AS T(Data);
Result:
201503
It's going to pick month number and add 100 to it and then pick 2 right chars from it, so for instance you got 4, it becomes 104 and then RIGHT function picks last 2 characters, which are 04.
Checked with other params, seems fine:
DECLARE #YearMonth INT = 201411;
SELECT CONVERT(VARCHAR(6), DATEPART(YEAR, T.Data) + 1) + RIGHT(100 + DATEPART(MONTH, T.Data) -1, 2)
FROM (VALUES (CONVERT(VARCHAR(8), #YearMonth) + '01')) AS T(Data);
Result:
201510
I would convert implicitly to date, add 11 months and then format back as a string. The integer conversion would be implicit as well.
select format(dateadd(month, 11, str(#YearMonth) + '01'), 'yyyyMM')

How to calculate difference between two datetime fields (yyyy-mm-dd hh:mm:ss) in time format (hh:mm:ss) in sql server?

I have 2 columns in my table in sql server – [Occurrence Time (NT)] and [Clearance Time(NT)] having the format yyyy-mm-dd hh:mm:ss and a third column [Outage Duration] with the format hh:mm:ss
[Identifier] | [Occurrence Time(NT)] | [Clearance Time(NT)] | [Outage Duration]
4 | 2014-12-28 15:06:33.000 | 2014-12-28 15:18:18.000 | 00:11:45.000
Outage duration is calculated as the difference of [Occurrence Time (NT)] and [Clearance Time(NT)]
Currently my code to do this is as follows :
select distinct a.[Identifier]
,a.[Occurrence Time(NT)]
,a.[Clearance Time(NT)]
,cast((cast(a.[Clearance Time(NT)] as datetime) - cast(a.[Occurrence Time(NT)]as datetime)) as time )
as [Outage Duration]
from final_report_2 a
The cast is just a failsafe as both the columns are already datetime. This code works for all cases where [Occurrence Time (NT)] and [Clearance Time(NT)] fall on the same day i.e. the outage is within 24 hours
For e.g in above row no. 4 both have 28th as the day and thus the outage is calculated correctly.
But the outage duration is wrongly calculated in case of different days.
2678 | 2014-12-28 12:50:04.000 | 2014-12-31 23:59:59.000 | 11:09:55.000
In row 2678 the time should be 83:09:55 instead of 11:09:55.
So I need to factor in the difference of the days as well and then calculate the [Outage duration] in hh:mm:ss format.
One of the possible ways to do this is as follows :
(23:59:59 – 12:50:04) + 00:00:01 + ((31-28-1)*24):00:00 + 23:59:59
where the first part calculates the time of the first day , then the second part calculates the no. of whole days in between the [Occurrence Time (NT)] and [Clearance Time(NT)] and multiplies it by 24 and the final part represents the time of the final day.
How can I implement the above in sql server ? Can it be done using DATEPART or DATEADD functions?
you need to use datediff function.
First find the difference in seconds between the two dates.
select Datediff(SECOND, [Clearance Time(NT)], [Occurrence Time(NT)])
Now convert the seconds to hh:mm:ss using this code.
DECLARE #TimeinSecond INT
SET #TimeinSecond = 86399 -- Change the seconds
SELECT RIGHT('0' + CAST(#TimeinSecond / 3600 AS VARCHAR),2) + ':' +
RIGHT('0' + CAST((#TimeinSecond / 60) % 60 AS VARCHAR),2) + ':' +
RIGHT('0' + CAST(#TimeinSecond % 60 AS VARCHAR),2)
Change your query something like this.
SELECT DISTINCT a.[Identifier],
a.[Occurrence Time(NT)],
a.[Clearance Time(NT)],
RIGHT('0' + Cast(Datediff(SECOND, [Clearance Time(NT)],[Occurrence Time(NT)]) / 3600 AS VARCHAR), 2)
+ ':'
+ RIGHT('0' + Cast((Datediff(SECOND, [Clearance Time(NT)],[Occurrence Time(NT)]) / 60) % 60 AS VARCHAR), 2)
+ ':'
+ RIGHT('0' + Cast(Datediff(SECOND, [Clearance Time(NT)],[Occurrence Time(NT)]) % 60 AS VARCHAR), 2) [Outage Duration]
FROM final_report_2 a
Reference: conversion of seconds to hh:mm:ss referred from this link
Good old SQL DATEDIFF.
Here's the duration function I use. All wrapped up in a UDF.
CREATE FUNCTION fDuration(#DtStart DATETIME, #DtEnd DATETIME)
RETURNS VARCHAR(10)
AS
BEGIN
RETURN (
SELECT
CONVERT(VARCHAR(10), t.Hours) + ':' +
RIGHT('00' + CONVERT(VARCHAR(10), t.Minutes), 2) + ':' +
RIGHT('00' + CONVERT(VARCHAR(10), t.Seconds - (t.Hours * 60 * 60) - (t.Minutes) * 60), 2) as Value
FROM (
SELECT
t.Hours,
ABS(t.Seconds / 60 - (t.Hours * 60)) as Minutes,
t.Seconds
FROM (
SELECT
DATEDIFF(SECOND, #DtStart, #DtEnd) as Seconds,
DATEDIFF(SECOND, #DtStart, #DtEnd)/60/60 AS Hours) t
) t)
END
USAGE:
SELECT dbo.fDuration('2014-12-28 12:50:04.000', '2014-12-31 23:59:59.000')
RETURNS :
83:09:55
In your case, you can modify your query like this
SELECT DISTINCT a.[Identifier],
a.[Occurrence Time(NT)],
a.[Clearance Time(NT)],
dbo.fDuration(a.[Occurrence Time(NT)], a.[Clearance Time(NT)]) as [Outage Duration]
FROM final_report_2 a
Or even add [Outage Duration] as a Computed field in final_report_2 with formula
dbo.fDuration([Occurrence Time(NT)],[Clearance Time(NT)])
Then it becomes a simple select...
SELECT DISTINCT a.[Identifier],
a.[Occurrence Time(NT)],
a.[Clearance Time(NT)],
a.[Outage Duration]
FROM final_report_2 a
Note
Hours are not prefixed with a '0'. This allows for negative durations.
Hope that helps

Cannot get hours to total over 24, eg 48 hours in hh:mm:ss output

I am running a query to find out the total amount of time a user has been browsing for. Each browsing session is stored in the DB as seconds and I then sum the total seconds and convert it into hh:mm:ss. The problem is when I'm converting the seconds into hh:mm:ss. I want it to display for example '78:20:00' but I dont know how to get the code to total it like this. When it gets past 24 hrs the hrs column goes back to 00 because its into a day.
The query I run to convert the time can be seen below:
SELECT Username,
CONVERT(VARCHAR(12),DATEADD(SECOND,TotalTimeInSeconds,0),108) AS TotalHours
FROM #TotalSessionTime
SELECT USERNAME,
CAST(TotalTimeInSeconds / (60 * 60) AS Varchar) + ':' +
CAST(TotalTimeInSeconds % (60 * 60) / 60 AS Varchar) + ':' +
CAST(TotalTimeInSeconds % (60) AS Varchar) AS TotalHours
FROM #TotalSessionTime
If you want minutes and seconds to always be two digits, you'll have to left pad them, which would make for an ugly example, but would work fine.
Try this...
DECLARE #TimeInSeconds int = 123400;
DECLARE #MyDate datetime;
SELECT #MyDate = CONVERT( DateTime, DATEADD( SECOND, #TimeInSeconds, 0))
SELECT CONVERT(CHAR(2), (DAY( #MyDate )-1) * 24 + DATEPART(hour,#MyDate)) + ':' + CONVERT(CHAR(2), DATEPART(minute, #MyDate)) + ':' + CONVERT(CHAR(2), DATEPART(SECOND, #MyDate))
You'll want to left-pad the minutes and seconds with a zero to be sure it's 2-digits, though.

Resources