Convert varchar of HH:MM:SS to minutes value - sql

Using SQL Server, I have a column with varchar data coming from another source that is formatted in pseudo HH:MM:SS string representing a duration time like:
Duration
HH:MM:SS
00:43:46
01:30:06
43:56:38
89:24:00
5890:01:00
I wanted to convert it to a simple minutes (int) value for each
Duration MinuteDuration
HH:MM:SS mm
00:43:46 43
01:30:06 90
43:56:38 2636
89:24:00 5364
5890:01:00 353401
I looked around stackoverflow and found several people suggesting CONVERT with the TIME param
USE [MyDB]
GO
SELECT [User],
[Duration],
(SELECT CONVERT(TIME, Duration, 8)) as DurationMinutes,
FROM [dbo].[MyTable]
GO
but I cannot use that since my hours values may be larger than 24/12 (my hours value could be in the thousands). Since the original data is in a varchar, I need to interpret the string first, then multiple the hours by 60* and add it to the minutes value (and just drop the seconds value).

Although parsing the components of the time is definitely a possibility, the string manipulation is pretty simple too:
select duration,
(convert(int, left(duration, charindex(':', duration) - 1)) * 60 +
convert(int, left(right(duration, 5), 2))
)
from t;
Here is a db<>fiddle.

Another option is ParseName() in concert with a CROSS APPLY
Example
Declare #YourTable Table ([Duration] varchar(50))
Insert Into #YourTable Values
('00:43:46')
,('01:30:06')
,('43:56:38')
,('89:24:00')
,('5890:01:00')
Select A.Duration
,Minutes = parsename(NewValue,2) + (parsename(NewValue,3)*60)
from #YourTable A
Cross Apply ( values (replace(Duration,':','.') ) ) B(NewValue)
Returns
Duration Minutes
00:43:46 43
01:30:06 90
43:56:38 2636
89:24:00 5364
5890:01:00 353401

Related

I have column x in hh:mm format of datatype varchar in SQL Server and I want to perform sum on that 'x' column

I have column x in hh:mm format of datatype varchar in SQL Server and I want to perform sum on that x column.
I created a user-defined function to convert total min into hh:mm format.
Then I tried to perform sum to calculate total duration:
sum(cast(new_totalmin AS Int))
also i want total of HH:mm exactly as example
4:20
+1:10
5:30
5 hour: 30 minute
or i can do one thing here insted hh:mm i keep column as it is which is totalmin as int once sum cal insted of hh:mm (hh.mm which is in decimal also ok for me PSB it will be ok for me ':' or '.' format )
(60 min --> 1:00 --> 1.00
90 min --> 1:30 -->1.30
---------------------------------
sum --> 150 min -->2:30 --> 2.30)
but it did not work.
I got an error like
Conversion failed when converting the varchar value '01:00' to data type int
DECLARE #SampleData AS TABLE (HourMinutes VARCHAR(10));
INSERT INTO #SampleData VALUES ('4:32');
INSERT INTO #SampleData VALUES ('5:28');
INSERT INTO #SampleData VALUES ('6:00');
INSERT INTO #SampleData VALUES ('7:10');
SELECT * FROM #SampleData
SELECT SUM(datediff(minute, 0, HourMinutes)) TotalMinute
FROM #SampleData
You will get following output
hh:mm is a varchar data and applying SUM will not work on it.
As you are telling that you are already having a function, I would suggest you to perform sum of the minutes and then later convert them to hh:mm
SELECT ... , YourUserDefinedFunction(sum(minuteData)) as minutesInHHMM_Format
FROM ...
WHERE ...
GROUP BY ...
I would recommend that you store numeric values -- such as the number of minutes -- as a number rather than a string.
The challenge is converting the value back to an HH:MM format. SQL Server does not support time values of 24 hours or greater, so you need to use string manipulations.
Assuming that your values are all less than 24 hours, you can use:
select sum(datediff(minute, 0, hhmm)) as num_minutes,
concat(sum(datediff(minute, 0, hhmm)) / 60, ':',
format(sum(datediff(minute, 0, hhmm)) % 60, '00')
)
from t;
The result here is a string, so this can exceed 24 hours.
A more general solution eschews date/times altogether:
select sum(v.minutes) as num_minutes,
concat(sum(v.minutes) / 60, ':',
format(sum(v.minutes) % 60, '00')
)
from t cross apply
(values (left(t.hhmm, charindex(':', t.hhmm) - 1) * 60 + right(t.hhmm, 2))
) v(minutes);
Here is a db<>fiddle.

How to add two time values together in SQL

I have a table with 'accumulate_low_risk' and 'accumulate_high_risk'. Both are timedata (time(7)) in the format 00:00:00.0000000.
I want to add them together and place them into another timedata called 'combined_shift_total'
I have tried to convert them to seconds, add the results together and convert them back to timedata but i'm getting lost.
The table is as follows:
CREATE TABLE accumulate_time ('id INT IDENTITY(1,1) PRIMARY KEY,time DATETIME NOT NULL,worker_id INTEGER NOT NULL REFERENCES worker_table(worker_id),shift_start DATETIME NOT NULL,accumulate_low_risk TIME NOT NULL,accumulate_high_risk TIME NOT NULL,combined_shift_total TIME NOT NULL)
I was trying something along the lines of....
select Sum(Left (CONVERT(nvarchar(30),accumulate_low_risk, 126),2) * 3600 + substring(accumulate_low_risk, 4,2) * 60 + substring(accumulate_low_risk, 7,2))
from accumulate_time WHERE id IN (SELECT max(id) FROM accumulate_time WHERE worker_id = 6)
But this is way too messy.
My knowledge of SQL is limited so I'm sure there's a simple way to achieve this.
I would be very grateful for any and all help.
Thanks!
To me it looks like SQL Server. You can achieve it by counting how many seconds you have in your columns using datediff() and then add it to 00:00:00.0000000 using dateadd(). Please note that I don't know how precise is your data, so if you want it to me more precise you need to i.e. change second to milisecond. It's shift data so it shouldn't excess 24 hours, but please note what in this case it wouldn't work.
select cast(dateadd(second, datediff(second, '00:00:00.0000000', accumulate_low_risk) + datediff(second, '00:00:00.0000000', accumulate_high_risk), '2019-01-01') as time(7))
from accumulate_time
An examply how it works on a temp table:
create table #time (t1 time(7), t2 time(7))
insert into #time values ('01:12:04.0000000','00:12:04.0000000')
select cast(dateadd(second, datediff(second, '00:00:00.0000000', t1) + datediff(second, '00:00:00.0000000', t2), '2019-01-01') as time(7))
from #time

How to Handle DATEDIFF(MINUTE, '00:00', '24:20') Like scenario?

There is a column in my Table. In which we are storing string value in format 'HH:MM'.During fetching records with this table every things works ok with
DATEDIFF(MINUTE, '00:00', ColumnName)
Problem is when we have Value greater than 23:59.
Its showing error like
Conversion failed when converting date and/or time from character string.
Can anybody suggest me the right approach for achieving this scenario.
If you are storing the value as something other than a time, why not just store the number of minutes and convert to whatever format you want on output?
Otherwise, I would suggest that you simply convert the value to minutes:
select (cast(left(ColumnName, 2) as int) * 60 +
cast(right(ColumnName, 2) as int)
) as Minutes
If you are not using date/time values, there is no requirement for using the functions specifically designed for them.
EDIT:
To handle hours longer than 99, use charindex():
select (cast(left(ColumnName, charindex(':', ColumnName) - 1) as int) * 60 +
cast(right(ColumnName, 2) as int)
) as Minutes
So it sounds like your saving the length of a time period. Try storing it in minutes. My query can handle numbers of different lengths since it's based on the colon.
DECLARE #yourTable TABLE (ColumnName VARCHAR(10));
INSERT INTO #yourTable
VALUES ('100:00'),
('24:20');
SELECT ColumnName,
(hr * 60) + minut AS time_period_in_minutes
FROM #yourTable
CROSS APPLY (SELECT CAST(SUBSTRING(ColumnName,0,CHARINDEX(':',ColumnName)) AS INT),
CAST(SUBSTRING(ColumnName,CHARINDEX(':',ColumnName) + 1,LEN(ColumnName)) AS INT)) CA(hr,minut)
Results:
ColumnName time_period_in_minutes
---------- ----------------------
100:00 6000
24:20 1460
Try to this
select DATEDIFF(MINUTE, '00:00', case when ISDATE(ColumnName)=0 then '00:00' else ColumnName end )

convert Excel Date Serial Number to Regular Date

I got a column called DateOfBirth in my csv file with Excel Date Serial Number Date
Example:
36464
37104
35412
When i formatted cells in excel these are converted as
36464 => 1/11/1999
37104 => 1/08/2001
35412 => 13/12/1996
I need to do this transformation in SSIS or in SQL. How can this be achieved?
In SQL:
select dateadd(d,36464,'1899-12-30')
-- or thanks to rcdmk
select CAST(36464 - 2 as SmallDateTime)
In SSIS, see here
http://msdn.microsoft.com/en-us/library/ms141719.aspx
The marked answer is not working fine, please change the date to "1899-12-30" instead of "1899-12-31".
select dateadd(d,36464,'1899-12-30')
You can cast it to a SQL SMALLDATETIME:
CAST(36464 - 2 as SMALLDATETIME)
MS SQL Server counts its dates from 01/01/1900 and Excel from 12/30/1899 = 2 days less.
tldr:
select cast(#Input - 2e as datetime)
Explanation:
Excel stores datetimes as a floating point number that represents elapsed time since the beginning of the 20th century, and SQL Server can readily cast between floats and datetimes in the same manner. The difference between Excel and SQL server's conversion of this number to datetimes is 2 days (as of 1900-03-01, that is). Using a literal of 2e for this difference informs SQL Server to implicitly convert other datatypes to floats for very input-friendly and simple queries:
select
cast('43861.875433912' - 2e as datetime) as ExcelToSql, -- even varchar works!
cast(cast('2020-01-31 21:00:37.490' as datetime) + 2e as float) as SqlToExcel
-- Results:
-- ExcelToSql SqlToExcel
-- 2020-01-31 21:00:37.490 43861.875433912
this actually worked for me
dateadd(mi,CONVERT(numeric(17,5),41869.166666666664)*1440,'1899-12-30')
(minus 1 more day in the date)
referring to the negative commented post
SSIS Solution
The DT_DATE data type is implemented using an 8-byte floating-point number. Days are represented by whole number increments, starting with 30 December 1899, and midnight as time zero. Hour values are expressed as the absolute value of the fractional part of the number. However, a floating point value cannot represent all real values; therefore, there are limits on the range of dates that can be presented in DT_DATE. Read more
From the description above you can see that you can convert these values implicitly when mapping them to a DT_DATE Column after converting it to a 8-byte floating-point number DT_R8.
Use a derived column transformation to convert this column to 8-byte floating-point number:
(DT_R8)[dateColumn]
Then map it to a DT_DATE column
Or cast it twice:
(DT_DATE)(DT_R8)[dateColumn]
You can check my full answer here:
Is there a better way to parse [Integer].[Integer] style dates in SSIS?
Found this topic helpful so much so created a quick SQL UDF for it.
CREATE FUNCTION dbo.ConvertExcelSerialDateToSQL
(
#serial INT
)
RETURNS DATETIME
AS
BEGIN
DECLARE #dt AS DATETIME
SELECT #dt =
CASE
WHEN #serial is not null THEN CAST(#serial - 2 AS DATETIME)
ELSE NULL
END
RETURN #dt
END
GO
I had to take this to the next level because my Excel dates also had times, so I had values like this:
42039.46406 --> 02/04/2015 11:08 AM
42002.37709 --> 12/29/2014 09:03 AM
42032.61869 --> 01/28/2015 02:50 PM
(also, to complicate it a little more, my numeric value with decimal was saved as an NVARCHAR)
The SQL I used to make this conversion is:
SELECT DATEADD(SECOND, (
CONVERT(FLOAT, t.ColumnName) -
FLOOR(CONVERT(FLOAT, t.ColumnName))
) * 86400,
DATEADD(DAY, CONVERT(FLOAT, t.ColumnName), '1899-12-30')
)
In postgresql, you can use the following syntax:
SELECT ((DATE('1899-12-30') + INTERVAL '1 day' * FLOOR(38242.7711805556)) + (INTERVAL '1 sec' * (38242.7711805556 - FLOOR(38242.7711805556)) * 3600 * 24)) as date
In this case, 38242.7711805556 represents 2004-09-12 18:30:30 in excel format
In addition of #Nick.McDermaid answer I would like to post this solution, which convert not only the day but also the hours, minutes and seconds:
SELECT DATEADD(s, (42948.123 - FLOOR(42948.123))*3600*24, dateadd(d, FLOOR(42948.123),'1899-12-30'))
For example
42948.123 to 2017-08-01 02:57:07.000
42818.7166666667 to 2017-03-24 17:12:00.000
You can do this if you just need to display the date in a view:
CAST will be faster than CONVERT if you have a large amount of data, also remember to subtract (2) from the excel date:
CAST(CAST(CAST([Column_With_Date]-2 AS INT)AS smalldatetime) AS DATE)
If you need to update the column to show a date you can either update through a join (self join if necessary) or simply try the following:
You may not need to cast the excel date as INT but since the table I was working with was a varchar I had to do that manipulation first. I also did not want the "time" element so I needed to remove that element with the final cast as "date."
UPDATE [Table_with_Date]
SET [Column_With_Excel_Date] = CAST(CAST(CAST([Column_With_Excel_Date]-2 AS INT)AS smalldatetime) AS DATE)
If you are unsure of what you would like to do with this test and re-test! Make a copy of your table if you need. You can always create a view!
Google BigQuery solution
Standard SQL
Select Date, DATETIME_ADD(DATETIME(xy, xm, xd, 0, 0, 0), INTERVAL xonlyseconds SECOND) xaxsa
from (
Select Date, EXTRACT(YEAR FROM xonlydate) xy, EXTRACT(MONTH FROM xonlydate) xm, EXTRACT(DAY FROM xonlydate) xd, xonlyseconds
From (
Select Date
, DATE_ADD(DATE '1899-12-30', INTERVAL cast(FLOOR(cast(Date as FLOAT64)) as INT64) DAY ) xonlydate
, cast(FLOOR( ( cast(Date as FLOAT64) - cast(FLOOR( cast(Date as FLOAT64)) as INT64) ) * 86400 ) as INT64) xonlyseconds
FROM (Select '43168.682974537034' Date) -- 09.03.2018 16:23:28
) xx1
)
For those looking how to do this in excel (outside of formatting to a date field) you can do this by using the Text function https://exceljet.net/excel-functions/excel-text-function
i.e.
A1 = 132134
=Text(A1,"MM-DD-YYYY") will result in a date
This worked for me because sometimes the field was a numeric to get the time portion.
Command:
dateadd(mi,CONVERT(numeric(17,5),41869.166666666664)*1440,'1899-12-31')

casting odd smallint time to to datetime format

I'm working with a db (SQL server 2008), and have an interesting issue with times stored in the db.
The DBA who originally set it up was crafty and stored scheduled times as smallints in 12-hour form-- 6:00AM would be represented as 600. I've figured out how to split them into hours and minutes like thus:
select floor(time/100) as hr, right(time, 2) as min from table;
What I want to do is compare these scheduled times to actual times, which are stored in the proper datetime format. Ideally, I would do this with two datetime fields and use datediff() between them, but this would require converting the smallint time into a datetime, which I can't figure out.
Does anyone have suggestions on how to do this?
Thanks in advance.
Can think of two ways to do that. The first is to build a string in the HH:MM format, and cast that to datetime. The second is to convert the smallint format to float with the number of days as unit. The number of days is the internal time representation, so you can cast that to datetime too.
Example code:
declare #i smallint
set #i = 621
-- Cast to a string '6:21', then to a datetime
select cast(CAST(#i / 100 as varchar) + ':' + CAST(#i % 100 as varchar)
as datetime)
-- Convert to number of days, which is the interal datetime format
select cast((#i/100)/24.0 + (#i%100)/(24*60.0) as datetime)
P.S. If you divide an integer by another integer. the result is a third integer: 100 / 24 = 4. If you divide an integer by a float, the result is a float: 100 / 24.0 = 4.16666.
Since you are using SQL Server 2008, you can take advantage of the new Time data type. In order to convert the integer to a time value, we need to assume that the last two digits are minutes. To get the minute portion, divide by 100, take the integer portion and subtract it from the initial value. So in the case of 621 we get:
621 - Floor(621/100)* 100
621 - Floor(6.21)*100
621 - 6*100
621 - 600 = 21 minutes
For the hour portion, we can simply take the integer value after dividing by 100.
Create Table #Test( IntVal smallint not null )
Insert #Test Values( 621 )
Insert #Test Values( 2359 )
Insert #Test Values( 1200 )
Insert #Test Values( 1201 )
Insert #Test Values( 1159 )
Select Z.TimeVal, GetDate(), DateDiff(hh, Z.TimeVal, Cast(GetDate() As Time(0)))
From (
Select Cast(DateAdd(mi
, IntVal - Floor(IntVal/100)*100
, DateAdd(hh, Floor(IntVal/100), 0)
) As Time(0)) As TimeVal
From #Test
) As Z
Part of the trick here is to use DateAdd(hh, Floor(IntVal/100), 0) which does a DateAdd against the zero value for datetime.