SQL Parse Duration String to Seconds - sql-server

I am using SQL Server 12.0.4213.0 and am new to advanced SQL queries.
I have a nvarchar column named duration containing timespans in one of two formats: 39 Seconds if the duration is under 1 minute, else the format is like 1 Day 18 Hours 2 Minutes. Note that each Day/Hour/Minute/Second can be singular or plural. Also in the first format, if the day is 0, the Day will be omitted (same with Hour).
I need to convert the duration to an integer representing total seconds. 1 Minute 30 Seconds should return 90 and 39 Seconds should return 39.
I think this could be completed with a nested CASE statement. The dataset is very large so I want to be sure I am approaching this efficiently. Any help would be appreciated.
One method I have working in PowerShell is to split the duration column on the space character, loop through the resultant array incrementing counter by 2, take array[counter] and multiply it by the corresponding seconds value determined by array[counter+1], add the product to the total seconds sum.

This is a little complicated, but not terrible. The way you can solve this is by removing the Day(s), Hours(s), Minute(s), Second(s) labels from the string, and replacing these with some delimiting string/character.
Then you can split the resulting values using a CTE.
Then, you'll create the timespan in seconds from each part based on it's location in the delimited string.
Finally, sum over the resulting time-span-part-seconds.
EDIT: Now defined as a function
Like so:
CREATE FUNCTION fn_getDurationSeconds(#duration varchar(100)) returns int
as
BEGIN
DECLARE #durationSeconds INT = 0;
with split (duration, part, remainder) as
(
SELECT
duration,
left(duration, CHARINDEX(':', duration)-1),
right(duration, LEN(duration) - CHARINDEX(':', duration))
FROM
(
SELECT
REPLACE(
REPLACE(
REPLACE(
REPLACE(
REPLACE(
REPLACE(
CASE WHEN #duration LIKE '% Day' or #duration like '% Days' THEN #Duration + ' 0 Hours 0 Minutes 0 Seconds'
WHEN #duration LIKE '% Hour' or #duration like '% Hours' THEN #Duration + ' 0 Minutes 0 Seconds'
WHEN #duration LIKE '% Minute'or #duration like '% Minutes' THEN #Duration + ' 0 Seconds'
ELSE #Duration
END, ' Day', ':'),
' Hour', ':'),
' Minute', ':'),
' Second', ':00'),
's', ''),
' ', '') duration
) t
union all
select
duration,
left(remainder, CHARINDEX(':', remainder) - 1),
right(remainder, LEN(remainder) - CHARINDEX(':', remainder))
from split
where CHARINDEX(':', remainder) > 0
)
SELECT #durationSeconds = sum(seconds)
FROM
(
select
duration,
CASE row_number() over(partition by duration order by LEN(remainder) )
WHEN 4 THEN 24 * 60 * 60
WHEN 3 THEN 60 * 60
WHEN 2 THEN 60
WHEN 1 THEN 1
END * CAST(part AS INT) Seconds
from split
) t
group by duration
order by duration
RETURN #durationSeconds
END

Another option is to use substring and patindex and a couple of common table expressions to get the desired result:
First, create and populate a sample table: (Please save us this step in your future questions)
DECLARE #T AS TABLE
(
Duration varchar(40),
NumberOfSeconds int
)
INSERT INTO #T (Duration) VALUES
('39 Seconds'),
('2 Minutes 4 Seconds'),
('1 Hours 7 Minutes 17 Seconds'),
('3 Days 9 Hour 8 Minutes 25 Seconds')
Then, use one cte to hold the patindex results for each word.
This cte is only there to save us the need to write the patindex over and over again.
Add another cte to this one, that will use substring to extract the values for each time part, using the first cte:
;WITH cte1 as
(
SELECT *,
PATINDEX('%Day%', Duration) As DaysIndex,
PATINDEX('%Hour%', Duration) As HoursIndex,
PATINDEX('%Minute%', Duration) As MinutesIndex,
PATINDEX('%Second%', Duration) As SecondIndex
FROM #T
), cte2 as
(
SELECT *,
CASE WHEN DaysIndex > 0 THEN
CAST(SUBSTRING(Duration, 0, DaysIndex) As int) * 60 * 60 * 24
ELSE
0
END As D,
CASE WHEN HoursIndex > 0 THEN
CAST(
SUBSTRING(Duration,
CASE WHEN DaysIndex > 0 THEN DaysIndex + 4
ELSE 0
END,
CASE WHEN DaysIndex > 0 THEN HoursIndex - DaysIndex - 4
ELSE HoursIndex
END
) As int) * 60 * 60
ELSE
0
END As H,
CASE WHEN MinutesIndex > 0 THEN
CAST(
SUBSTRING(Duration,
CASE WHEN HoursIndex > 0 THEN HoursIndex + 5
ELSE 0
END,
CASE WHEN HoursIndex > 0 THEN MinutesIndex - HoursIndex - 5
ELSE MinutesIndex
END
) As int) * 60
ELSE
0
END As M,
CASE WHEN SecondIndex > 0 THEN
CAST(
SUBSTRING(Duration,
CASE WHEN MinutesIndex > 0 THEN MinutesIndex + 7
ELSE 0
END,
CASE WHEN MinutesIndex > 0 THEN SecondIndex - MinutesIndex - 7
ELSE SecondIndex
END
) As int)
ELSE
0
END As S
FROM cte1
)
Now, update the NumberOfSeconds column:
UPDATE cte2
SET NumberOfSeconds = D + H + M + S
Verify the update results:
SELECT *
FROM #T
Results:
Duration NumberOfSeconds
---------------------------------------- ---------------
39 Seconds 39
2 Minutes 4 Seconds 124
1 Hours 7 Minutes 17 Seconds 4037
3 Days 9 Hour 8 Minutes 25 Seconds 292105

Related

Calculating Early and Late Dates for Performance Measurement

I am trying to calculate shipping performance in SSMS- Im getting stuck in several areas and I hope i can get some help!
I have an Estimated Ship Date, an Appt Date, and an Actual Ship date.
Im measuring warehouse performance, so in most cases shipments leave same day. But in others, they may leave a few days early or late.
The problem that i am having is the correct output. I want to show the Values in DD:HH:MM, but the syntax for DateDiff doesn't give me an accurate day to use:
For Example, a shipment was supposed to leave on 6/3/2019 # at 8 am, but didnt leave the warehouse until 7/22/2019 # 6:30 AM. In this case, DateDiff calcs 49 days, when really its 48 days 22 hours and 30 minutes late. Here is an example of some of the syntax i am using:
EstimatedShipDate datetime,
AppointmentShipDate datetime,
ActualShipDate datetime
);
insert into #test values ('2019-07-01 11:00', '2019-07-01 11:00','2019-06-30 10:30');
insert into #test values ('2019-07-08 13:45', null,'2019-07-01 22:00');
insert into #test values ('2019-07-09 15:00', null,'2019-07-10 15:00');
insert into #test values ('2019-07-03 15:00', null,'2019-07-04 15:00');
insert into #test values ('2019-07-08 15:00', null,'2019-07-08 15:00');
insert into #test values ('2019-07-08 15:00', null,'2019-07-08 22:00');
insert into #test values ('2019-07-03 08:00', null,'2019-07-04 15:00');
insert into #test values ('2019-07-03 08:00', null,'2019-07-03 06:30');
insert into #test values ('2019-06-03 08:00', null,'2019-07-22 06:30');
insert into #test values ('2019-07-01 11:00', null,'2019-06-29 10:30');
Select
EstimatedShipDate,
AppointmentShipDate,
ActualShipDate,
DATEDIFF(DAY,ISNULL(CAST(AppointmentShipDate as DateTime),CAST(EstimatedShipDate as DateTime)), CAST(ActualShipDate as DateTime)) as Days,
DATEPART(DAY, ISNULL(CAST(AppointmentShipDate as DateTime),CAST(EstimatedShipDate as DateTime))-CAST(ActualShipDate as DateTime) ) as days2,
DATEDIFF(Hour,ISNULL(CAST(AppointmentShipDate as DateTime),CAST(EstimatedShipDate as DateTime)), CAST(ActualShipDate as DateTime)) as Hours,
convert(varchar, CAST(ActualShipDate as DateTime)-ISNULL(CAST(AppointmentShipDate as DateTime),CAST(EstimatedShipDate as DateTime)),108) as DateSubtract_Convert,
convert(varchar,ISNULL(CAST(AppointmentShipDate as DateTime),CAST(EstimatedShipDate as DateTime)-CAST(ActualShipDate as DateTime) ),108) as DateSubtract_ConvertEarly
from #TEST
In this case, DateDiff calcs 49 days, when really its 48 days 22 hours and 30 minutes late. Or vice versa, I have a date range that shows 2 days 23:20 but it should be 1 day 23:20
One way is to increase granularity of DATEDIFF to MINUTE. That will require calculation for the days, hours, and minutes using the minutes (division and mod).
SELECT ABS(dT.diff_Minutes / 1440) AS [Days] --days, there are 1440 minutes in a day
,ABS((dT.diff_Minutes % 1440) / 60) AS [Hours] --hours remaining in the day
,ABS((dT.diff_Minutes % 1440) % 60) AS [Minutes] --minutes remaining in the day
FROM (
SELECT DATEDIFF(MINUTE, ISNULL(AppointmentShipDate, EstimatedShipDate), ActualShipDate) [diff_Minutes]
FROM #test
) AS dT
Your temp table produces output:
Days Hours Minutes
1 0 30
6 15 45
1 0 0
1 0 0
0 0 0
0 7 0
1 7 0
0 1 30
48 22 30
2 0 30
To put these in DD:HH:MM format is more complicated, but you can cast the numbers to varchar and concatenate as strings. RIGHT is used to add any leading zeroes.
SELECT CASE WHEN dT.diff_Minutes < 0 THEN '- ' ELSE '+ ' END --positive or negative
+ RIGHT('00' + CAST(ABS(dT.diff_Minutes / 1440) as varchar(1000)), 2)
+ ':'
+RIGHT('00' + CAST((ABS(dT.diff_Minutes % 1440) / 60) as varchar(2)), 2)
+ ':'
+RIGHT('00' + CAST((ABS(dT.diff_Minutes % 1440) % 60) as varchar(2)), 2)
AS [DD:HH:MM]
FROM (
SELECT DATEDIFF(MINUTE, ISNULL(AppointmentShipDate, EstimatedShipDate), ActualShipDate) [diff_Minutes]
FROM #test
) AS dT
Produces output:
DD:HH:MM
- 01:00:30
- 06:15:45
+ 01:00:00
+ 01:00:00
+ 00:00:00
+ 00:07:00
+ 01:07:00
- 00:01:30
+ 48:22:30
- 02:00:30
Use seconds instead of days and then do a little math with seconds, minutes and hours.
Here's a simple SSMS example:
DECLARE
#ScheduledDate DATETIME = '6/3/2019 08:00:00',
#ShippedDate DATETIME = '07/22/2019 06:30:00';
SELECT
CONVERT( VARCHAR, DATEDIFF( s, #ScheduledDate, #ShippedDate ) /60/60/24 ) + ' Days and '
+ CONVERT( VARCHAR, ( #ShippedDate - #ScheduledDate ), 108 ) + ' Hours.';
Returns
48 Days and 22:30:00 Hours.

SQL Server 2014 - Sum Working Hour group by Day/Night Time, Week/Weekend, Regular/Overtime

I have a list of tasks,
for each I have a TaskID, startTime and StopTime as milliseconds from 1-1-1970 and a list of users (#Tasks).
I need to calculate the time spent by each user on the task splitted by day/night time, week or weekend, regular/overtime considering nighttime hours from 10:00 PM to 06:00 AM.
Surely there's a better solution but so far I got this:
IF OBJECT_ID('tempdb..#Tasks') IS NULL
BEGIN
create table #Tasks
(
TaskID nvarchar(50),
DateStart bigint,
DateStop bigint,
Staff nvarchar(100)
)
insert into #Tasks values
('C001',1554181200000,1554190200000,'john,jack'),
('C002',1554202800000,1554212700000,'tom,john'),
('C003',1554228000000,1554246900000,'john,franck'),
('C004',1554613200000,1554626700000,'john')
END
GO
declare
#UserName nvarchar(50)='john',
#DateFrom datetime='2019-04-01',
#DateTo datetime='2019-04-30',
#nStart time='06:00:00',
#nStop time='22:00:00'
select
startday as [Day],
sum([WeekDay]) as [WeekDay],
sum([WeekNight]) as [WeekNight],
sum([WeekendDay]) as [WeekendDay],
sum([WeekendNight]) as [WeekendNight],
sum([TotalMinutes]) as [TotalMinutes],
0 WeekDayOverTime,
0 WeekNightOvertime,
0 WeekendDayOvertime,
0 WeekendNightOvertime,
[UserName]
,timeframe
from
(
select
iif(isWeekend=1,NightMinutes,0) WeekendNight,
iif(isWeekend=0,NightMinutes,0) WeekNight,
iif(isWeekend=1,DayMinutes,0) WeekendDay,
iif(isWeekend=0,DayMinutes,0) [WeekDay],
TotalMinutes,
username,
startday,
timeframe
from
(
select
iif(Before6>0,Before6,0)+ iif(After22>0,After22,0) NightMinutes,
TotalMinutes-iif(Before6>0,Before6,0)- iif(After22>0,After22,0) DayMinutes,
TotalMinutes,
startday,
isWeekend,
UserName,
timeframe
from
(
Select
(t.datestop-t.datestart)/60000 TotalMinutes,
datediff(n,convert(time,DATEADD(SECOND,t.DateStart/1000,'1970-01-01')),#nStart) Before6,
datediff(n,#nStop,convert(time,DATEADD(SECOND,t.DateStop/1000,'1970-01-01'))) After22,
iif((((DATEPART(DW, convert(datetime,DATEADD(SECOND,t.DateStart/1000,'1970-01-01'))) - 1 ) + ##DATEFIRST ) % 7) IN (0,6),1,0) isWeekend,
convert(varchar(10),DATEADD(SECOND,t.DateStart/1000,'1970-01-01'),126) startday,
STUFF(( SELECT distinct ' ' + convert(varchar(5),DATEADD(SECOND,t.DateStart/1000,'1970-01-01'),108)+'-'+convert(varchar(5),DATEADD(SECOND,t.DateStop/1000,'1970-01-01'),108) AS [text()]
FROM #Tasks tt
--WHERE tt.taskID=t.TaskID
FOR XML PATH('') ), 1, 1, '' ) AS [timeframe],
#UserName UserName
FROM #Tasks t
WHERE t.Staff like '%'+#UserName+'%'
and DATEADD(SECOND,t.DateStart/1000,'1970-01-01') between #DateFrom and #DateTo
) z
) zz
) zzz
group by startday,username,timeframe
order by startday
I need now to :
1) group result by day, summing up WeekDay,WeekNight,WeekendDay,WeekendNight and TotalMinutes, and concatenating timeframe so to have for example on 2nd April "05:00-07:30|11:00-13:45|18:00-23:00"
2) Not sum up time between 12:00 and 12:30 (if applicable) since it is lunch time
3) considering that after 8 hours daily it has to be calculated as overtime, I have to split the total minutes between in time and overtime, but depending if overtime is by daytime or nighttime or on the weekend
4) eventually using a holiday table
in other words we should have this:
Day TotalMinutes WeekDay WeekNight WeekendDay WeekendNight WeekDayOverTime WeekNightOvertime WeekendDayOvertime WeekendNightOvertime UserName timeframe
02/04/2019 630 420 60 0 0 45 75 0 0 john 05:00-07:30|11:00-13:45|18:00-23:00
07/04/2019 225 0 0 165 60 0 0 0 0 john 05:00-08:45
because (on 2nd April) we have:
First Task:
60 minutes of Regular NightTime
90 minutes of Regular DayTime
Second Task:
165 minutes of Regular DayTime, but have to count only 135 due to lunch time
Third Task:
240 DayTime
75 NightTime
but since with Task 1 and 2 we sum up 285 minutes, only the first 185 Minutes of Third task are Regular DayTime: the remaining 45 are Overtime DayTime, and the following 75 of NightTime are actually OvertimeNightTime
In this approach the first CTE (properDates) get the Start and Stop Datetimes, then you don't need to repeat that formula over the query.
The second CTE(splittedMinutes) is to get the same data you get in your current approach, except for the first CROSS APPLY, which is splitting the timeframes crossing with lunch time. The second CROSS APPLY gets the number of minutes and isWeekend value.
In the third CTE(qualifiedMinutes) I am using a window partition to get the accumulated minutes and generate the overtimes when applies.
At the end I used a selective SUM to separate weekdays and weekends in the aggregates
;with properDates AS (
SELECT TaskID, DATEADD(SECOND,t.DateStart/1000,'1970-01-01') as DateStart,DATEADD(SECOND,t.DateStop/1000,'1970-01-01') as DateStop, Staff
FROM #Tasks t
WHERE Staff LIKE '%' + #UserName + '%'
), splittedMinutes AS (
select
CAST(p.DateStart AS DATE) as [Day],
TotalMinutes,
SUM(TotalMinutes) OVER (PARTITION BY CAST(p.DateStart AS DATE) ORDER BY b.start) AS cumulate,
TotalMinutes - EarlyMinutes - LateMinutes as DayTime,
EarlyMinutes + LateMinutes as NightTime,
isWeekend,
CONVERT(VARCHAR(5),b.Start,108) + '-' + CONVERT(VARCHAR(5),b.Stop,108) as [timeframe]
from properdates p
cross apply (
select CAST(p.DateStart As TIME) AS Start, #bStart as Stop WHERE CAST(p.DateStart AS TIME) < #bStart and CAST(p.DateStop AS TIME) > #bStart
union
select #bStop as Start, CAST(DateStop AS TIME) AS Stop WHERE CAST(p.DateStop AS TIME) > #bStop and CAST(p.DateStart AS TIME) < #bStop
union
select CAST(p.DateStart AS TIME) AS Start, CAST(p.DateStop AS TIME) AS Stop WHERE NOT (CAST(p.DateStart AS TIME) < #bStart and CAST(p.DateStop AS TIME) > #bStart) AND NOT (CAST(p.DateStop AS TIME) > #bStop and CAST(p.DateStart AS TIME) < #bStop)
) b
cross apply (
select
DATEDIFF(Minute, b.Start, b.Stop) as TotalMinutes,
(DATEDIFF(Minute, CAST(b.Start AS TIME), #nStart) + ABS(DATEDIFF(Minute, CAST(b.Start AS TIME), #nStart))) / 2 as EarlyMinutes,
(DATEDIFF(Minute, #nStop, CAST(b.Stop AS TIME)) + ABS(DATEDIFF(Minute, #nStop, CAST(b.Stop AS TIME)))) / 2 as LateMinutes,
CASE WHEN DATEPART(DW, p.DateStart) IN (1,7) THEN 1 ELSE 0 END AS isWeekend
) c
), qualifiedMinutes As (
SELECT Day, TotalMinutes, RegularDay, RegularNight, OvertimeDay, OvertimeNight, isWeekend, timeframe
FROM splittedMinutes
OUTER APPLY (
SELECT RegularDay = CASE WHEN cumulate <= #maxTime THEN DayTime WHEN DayTime - (cumulate - TotalMinutes - #maxTime) > 0 THEN ABS(cumulate - TotalMinutes - #maxTime) ELSE 0 END
) RD
OUTER APPLY (
SELECT OvertimeDay = DayTime - RegularDay
) OWD
OUTER APPLY (
SELECT RegularNight = CASE WHEN cumulate <= #maxTime THEN NightTime WHEN (cumulate - TotalMinutes - #maxTime + RegularDay) < 0 THEN NightTime + (cumulate - TotalMinutes - #maxTime + RegularDay) ELSE 0 END
) RWN
OUTER APPLY (
SELECT OvertimeNight = NightTime - RegularNight
) OWN
)
SELECT
Day,
#UserName and UserName,
SUM(TotalMinutes) AS TotalMinutes,
SUM(CASE WHEN isWeekend = 0 THEN RegularDay ELSE 0 END) AS WeekDay,
SUM(CASE WHEN isWeekend = 0 THEN RegularNight ELSE 0 END) AS WeekNight,
SUM(CASE WHEN isWeekend = 1 THEN RegularDay ELSE 0 END) AS WeekendDay,
SUM(CASE WHEN isWeekend = 1 THEN RegularNight ELSE 0 END) AS WeekendNight,
SUM(CASE WHEN isWeekend = 0 THEN OvertimeDay ELSE 0 END) AS WeekDayOverTime,
SUM(CASE WHEN isWeekend = 0 THEN OvertimeNight ELSE 0 END) AS WeekNightOvertime,
SUM(CASE WHEN isWeekend = 1 THEN OvertimeDay ELSE 0 END) AS WeekendDayOverTime,
SUM(CASE WHEN isWeekend = 1 THEN OvertimeNight ELSE 0 END) AS WeekendNightOvertime,
STUFF((SELECT '|' + timeframe FROM qualifiedMinutes tt WHERE tt.Day = q.Day ORDER BY timeframe FOR XML PATH('') ), 1, 1, '' ) AS [timeframe]
FROM qualifiedMinutes q
GROUP BY Day

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

Count the number of specific days of the month that have passed since a given date

I'm writing a function in SQL Server 2012 that will need to know the number of 3 specific days of the month that have passed since a given date. I can do this with a while loop, but its slow and I was looking for a better way.
Here is what I have so far:
Let's assume that GETDATE() = '11/14/2016' and #productDate = '10/1/2016'
--Get the number of "units" that have passed since the date on the label
DECLARE #unitCount INT = 0;
DECLARE #countingDate DATE
SET #countingDate = DATEADD(DAY,1,#productDate);--add 1 to prevent counting the date on the label as the first unit
WHILE (#countingDate < CAST(GETDATE() As date ))
BEGIN
SELECT #unitCount = #unitCount +
CASE
WHEN DAY(#countingDate) = 1 OR DAY(#countingDate) = 10 OR DAY(#countingDate) = 20 THEN 1
ELSE 0
END
SET #countingDate = DATEADD(DAY,1,#countingDate);
END
This will result in #unitCount = 4
GETDATE() of '11/20/2016' would result in #unitCount = 5
Without using a numbers table
create function dbo.fn_DateCounter
(
#datefrom date,
#dateto date
)
returns int
as
begin
return
-- number of complete months
3 *
(
(DATEPART(YYYY, #dateto) * 12 + DATEPART(MM, #dateto))
-(DATEPART(YYYY, #datefrom) * 12 + DATEPART(MM, #datefrom))
- 1
)
-- add on the extras from the first month
+ case when DATEPART(DD, #datefrom) < 10 then 2
when DATEPART(DD, #datefrom) < 20 then 1
else 0
end
-- add on the extras from the last month
+ case when DATEPART(DD, #dateto) > 20 then 3
when DATEPART(DD, #dateto) > 10 then 2
else 1
end
end
go
select dbo.fn_DateCounter('01-jan-2000','01-jan-2000') -- 0
select dbo.fn_DateCounter('01-jan-2000','10-jan-2000') -- 0
select dbo.fn_DateCounter('01-jan-2000','11-jan-2000') -- 1
select dbo.fn_DateCounter('01-jan-2000','20-jan-2000') -- 1
select dbo.fn_DateCounter('01-jan-2000','21-jan-2000') -- 2
select dbo.fn_DateCounter('11-jan-2000','21-jan-2000') -- 1
select dbo.fn_DateCounter('11-jan-2000','21-feb-2000') -- 4
select dbo.fn_DateCounter('01-jan-2000','01-jan-2001') -- 36
select dbo.fn_DateCounter('01-jan-2000','11-jan-2001') -- 37
You can use a combination of sum, case, and the dbo.spt_values table:
declare #productDate datetime = '11/01/2016',
#unitCount int
;with nums as ( -- use a CTE to build a number list
select top 1000 number from master..spt_values
)
select #unitCount = sum(
case when day(dateadd(day, n, #productDate)) in (1, 10, 20)
then 1 else 0 end
) -- add 1 for each 1,10,20 we find
from (
select n = row_number() over (order by nums.number)
from nums cross join nums as num -- 1000*1000 = 1 million rows
) n
where dateadd(day, n, #productDate) < getdate()
select #unitCount
This will grab each date between #productDate and getdate(). The case statement will select 1 for each 1/10/20, and 0 for every other date. Finally, we take the sum of the result.
For 11/1 - 11/11, it returns 1.
For 1/1 - 11/11, the result is 31.
EDIT: In the CTE (with nums as...), we select 1-1000, and then we do a cross join which gives us a million records to work with. The answer is still limited, but now you can go ~2700 years with this.

Add missing ZERO on HH:MM:SS in SQL column

This is the format I need from a T-SQL Column HH:MM:SS
Example:
SELECT CONVERT(varchar,GETDATE(),108) -- 11:06:03
I have these values: (Select duration from MyTable)
2:13:00
11:02
0:43
That needs to be this:
02:13:00
00:11:02
00:00:43
How can this be done in T-SQL?
Use Convert, then cast
transformation. The peculiarity here is hidden in omitted hours string, while sql server assumes seconds omitted.
Select
CONVERT(time(0),
case when len(duration) > 5 then duration else '00:' + duration end, 108)
from MyTable
Examples:
SELECT CONVERT(time(0),
case when len('2:13:00') > 5 then '2:13:00' else '00:' + '2:13:00' end ,108)
SELECT CONVERT(time(0),
case when len('11:02') > 5 then '11:02' else '00:' + '11:02' end ,108)
SELECT CONVERT(time(0),
case when len('0:43') > 5 then '0:43' else '00:' + '0:43' end ,108)
--------------
02:13:00
00:11:02
00:00:43
Update1: optimized the answer to time(0) due #Mikael Eriksson tip
Update2: modified solution concerning example2 requirement
This did it for me at the end.....
SELECT RIGHT('000' + CASE WHEN LEN(Duration) > 5
THEN LEFT(duration, CHARINDEX(':', duration) - 1)
ELSE 0 -- Hours
END, 3) + ':' +
RIGHT('00' + CASE WHEN LEN(Duration) > 5
THEN SUBSTRING(duration
,CHARINDEX(':',duration) + 1
,CHARINDEX(':',duration
,CHARINDEX(':',duration) + 1) - 1- CHARINDEX(':',duration))
ELSE LEFT(duration,CHARINDEX(':',duration) - 1)END, 2) + ':' + -- Minutes
RIGHT('00'+ RIGHT(duration, 2), 2) -- Seconds
NewDuration
FROM MyTable

Resources