2

I have a column with time records that represent the difference between two dates. Some of these records exceed 24 hours.

As time syntax doesn't account for time records > 24 hours, I've had to convert these records to varchar hh:mm:ss as outlined in the solution in the link below:

SQL date format [h]:mm:ss like Excel does, beyond 24 hr

This works fine, however my problem is I now need to convert these to float (e.g. 69:00:00.0000 as 2.875).

Unfortunately I can't use the commonly suggested datediff to do this as it throws an 'out of range' error for the records exceeding 24 hours.

Any ideas on how I can overcome this?

Community
  • 1
  • 1
jamjam1000
  • 41
  • 5
  • 2
    `I've had to convert these records to varchar` ... I don't think it's a good idea _ever_ to store numerical data as `varchar`. Is there any chance you could store the diff in seconds? – Tim Biegeleisen Oct 06 '16 at 07:22
  • 1
    Just store the duration as a number, storing the elapsed seconds/minutes/days/years, whatever unit of measurement suits your needs (i.e. I wouldn't measure geological ages in seconds, nor would I measure the 100M sprint in hours). If you ever need to display this number in hours, minutes and seconds using the format hh:mm:ss, then this is a job for the presentation layer. Storing it as string because ultimately you want to display it in a certain way is a very bad idea. – GarethD Oct 06 '16 at 07:24
  • 1
    Another alternative is to store your start date and time, and end date and time, then you can very easily work out the duration in whatever units you like using `DATEDIFF`. For related reading see [Bad habits to kick : choosing the wrong data type](https://sqlblog.org/2009/10/12/bad-habits-to-kick-choosing-the-wrong-data-type) – GarethD Oct 06 '16 at 07:30
  • As you are quite new here (Btw: Welcome!) please allow me one hint: It was very kind of you tick the acceptance check below the (best) answer's vote counter. This will 1) mark this issue as solved 2) make it easier for followers to find the best solution 3) pay points to the answerer and 4) pay points to you. Since you've crossed the 15 points border yourself, you are - additionally - asked to vote on contributions. This is the SO-way to say thank you. Happy Coding! – Shnugo Oct 12 '16 at 18:37

3 Answers3

2

Thanks for the helpful posts, I took the advice of the original comments in that it's a lot easier to calculate the difference between dates as a number and convert this later in the presentation layer (Excel).

To do this, I used DATEPART logic to sum day,hour,minute,second together to derive a 'decimal day' figure then used Excel to convert this to [h]:mm:ss

(e.g. difference between '2016-04-30 23:23:00' and '2016-04-30 23:25:00' is 2 minutes. This is 0.0013888833 as a decimal day, which converts to 0:02:00 in Excel when formatted as [h]:mm:ss)

Unfortunately I don't have time to test whether the alternative solutions provided work.

jamjam1000
  • 41
  • 5
  • 1
    I wouldn't even bother testing the other solutions - while they may work they remind me of answers to the question - [Pounding A Nail: Old Shoe or Glass Bottle?](https://weblogs.asp.net/alex_papadimoulis/408925), the truth is while both may work, but both are treating the symptoms, and not the actual problem. Your approach has corrected the problem (which is actually quite rare on SO and rather refreshing - such suggestions are usually mean with weak excuses as to why the system can't be changed) – GarethD Oct 06 '16 at 10:17
  • @GarethD the big amount of question on SO is like question from the article you posted, and I always see comments that advice to build a good normal (NF) solution (not to store comma separated values in single rows etc). The answer as always - yes we know it is bad, no we can not rebuild it. Basically we have the same situation in our company too. That is why, most of the times, I answer such question just for fun, and to make something, I haven't yet made. The answers like OP gave are very rare (at list I have found few) and that deserve to be mentioned! It is just my point of view. :) – gofr1 Oct 06 '16 at 10:53
1

Something like this?

DECLARE @YourTime VARCHAR(100)='69:00:00.0000';

WITH Splitted AS
(
    SELECT @YourTime AS t
          ,CHARINDEX(':',@YourTime)-1 AS HourLength
          ,LEFT(@YourTime,CHARINDEX(':',@YourTime)-1) AS HourPart
)
SELECT HourPart/24 + CAST(CAST(CAST(STUFF(@YourTime,1,HourLength,HourPart-(HourPart/24)*24) AS TIME) AS DATETIME) AS FLOAT)
FROM Splitted

UPDATE the same as function

CREATE FUNCTION dbo.ConvertExceedingTimeToFloat(@TimeString VARCHAR(100))
RETURNS FLOAT
AS
BEGIN
    DECLARE @RetVal FLOAT;
    WITH Splitted AS
    (
        SELECT @TimeString AS t
              ,CHARINDEX(':',@TimeString)-1 AS HourLength
              ,LEFT(@TimeString,CHARINDEX(':',@TimeString)-1) AS HourPart
    )
    SELECT @RetVal = HourPart/24 + CAST(CAST(CAST(STUFF(@TimeString,1,HourLength,HourPart-(HourPart/24)*24) AS TIME) AS DATETIME) AS FLOAT)
    FROM Splitted;

    RETURN @RetVal;
END
GO

SELECT dbo.ConvertExceedingTimeToFloat('69:00:00.0000')
GO

DROP FUNCTION dbo.ConvertExceedingTimeToFloat;
Shnugo
  • 66,100
  • 9
  • 53
  • 114
1

Maybe you could REPLACE : to .. This will give you a string that could be parsed with PARSENAME (starting with SQL Server 2012)), then CAST to float:

DECLARE @SomeTime VARCHAR(100)='69:00:00.0000'

SELECT CAST(PARSENAME(REPLACE(@SomeTime,':','.'),4)/24.00 as float)

Or SUBSTRING with CHARINDEX:

SELECT CAST(SUBSTRING(@SomeTime,1,CHARINDEX(':',@SomeTime)-1)/24.00 as float)

Output:

2,875

Hope, I get your question right.

EDIT

The above method used only for hours. If you need to go deeper:

DECLARE @SomeTime VARCHAR(100)='69:55:10.999',
        @x xml
--Convert to XML
SELECT @x = CAST('<p>'+REPLACE(REPLACE(@SomeTime,':','.'),'.','</p><p>') +'</p>' as xml)
--Working with XML, each part need convertion
SELECT  CAST(
        t.c.value('/p[1]','int')/24.00+
        (t.c.value('/p[2]','int')/60.00)/24.00+
        ((t.c.value('/p[3]','int')/60.00)/60.00)/24.00+
        (((t.c.value('/p[4]','int')/1000.00)/60.00)/60.00)/24.00
        as float) result
FROM @x.nodes('/') as t(c)

Output:

2,9133217194375

NOTE:

I am know not much about MATH in SQL Server, please, report/suggest if you see any flows in current solution.

gofr1
  • 15,741
  • 11
  • 42
  • 52
  • Btw: Worth to mention, that `PARSENAME` need SQL Server 2012+ – Shnugo Oct 06 '16 at 08:26
  • @Shnugo fixed about PARSENAME and add SUBSTRING solution. What other parts you have mentioned? – gofr1 Oct 06 '16 at 08:29
  • If the given value has got minutes, this is ignored... Your solution works for the hours only... – Shnugo Oct 06 '16 at 08:30
  • @Shnugo I don't see any other parts in the question - no mention of minutes and second to use, so I take only hours. If OP need that stuff to - I guess, he will add a comment here and I will delete/rewrite my solution. – gofr1 Oct 06 '16 at 08:35
  • Well, we don't know... But why should OP *had to convert these records to varchar hh:mm:ss*, if the values are naked hours? – Shnugo Oct 06 '16 at 08:37
  • Because he somehow need it I guess :) Add XML solution. – gofr1 Oct 06 '16 at 08:48
  • @Shnugo Thanks! Once again: XML is great! – gofr1 Oct 06 '16 at 08:59
  • Thanks guys, I did actually need minutes and seconds, my example was just easier for people to understand – jamjam1000 Oct 06 '16 at 09:42
  • My pleasure! :) It is great that you found solution by yourself! – gofr1 Oct 06 '16 at 11:11