T-SQL duration in hours:minutes:seconds - tsql

I have average duration between several dates (DATETIME format) ie. 1900-01-01 01:30.00.00.
How can I convert DATETIME to format hours:minutes:seconds where hours can be more that 24 - output format can be VARCHAR.
IE.
1 days 12 hours 5 minutes convert to 36:05:00
2 days 1 hour 10 minutes 5 seconds convert to 49:10:05
etc...
DECLARE #date1 DATETIME = '2011-08-03 13:30'
DECLARE #date2 DATETIME = '2011-08-03 13:00'
DECLARE #date3 DATETIME = '2011-08-03 14:00'
DECLARE #abc DATETIME = '2011-08-03 12:00'
select CAST(AVG(CAST(data-#abc as float)) as datetime)
from
(
select 'data' as label, #date1 as data
union all
select 'data' as label, #date2 as data
union all
select 'data' as label, #date3 as data
) as a
group by label
I would like to get result as 01:30:00 which means that average time is 1 hours and 30 minutes.
I tried it:
CONVERT(VARCHAR(10), CAST(AVG(CAST(data-#abc as float)) as datetime), 108)
but then I get only time portion in HH:MM:SS. When I set #abc = 2011-08-02 then the results will be the same - this is incorrect.
King regards,
Marcin

In T-SQL a datetime is precisely that, a date and a time where the hours can never exceed 24 because that moves it to the next day. You could use datepart to piece the datetime values out and treat them as integers and then rejoin them into the string you want. Depending on your final goal, you may be better of doing this type of work in your application or presentation layers where more general purpose languages often have more robust datetime libraries to work with.

I think you need to write a scalar-valued function that takes an integer argument (time difference in seconds) and format it as needed. For example,
CREATE FUNCTION intToDateTime ( #time_in_secs BIGINT) RETURNS VARCHAR(30)
AS
BEGIN
DECLARE #retval VARCHAR(30);
SET #retval = cast(#time_in_secs/(60*60) as varchar(10))+':'+
cast( (#time_in_secs-#time_in_secs/(60*60)*3600)/60 as varchar(10))+':'+
cast( (#time_in_secs-#time_in_secs/(60)*60) as varchar(10));
return #retval;
END
This function needs some changes - you may want to display leading zero for 0-9(i.g. '00' instead of '0' as this function currently does); also you need to handle negative values in a better way.
Now you can use it with DATEDIFF(second, #val1,#val2).
Hope I pointed you to the right direction.

select cast(cast(cast(t as float) *24 as int) as varchar) + right(convert(varchar,t, 20), 6)
from(
select cast(AVG(CAST(data-#abc as float)) as datetime) t
from
(
select 'data' as label, #date1 as data
union all
select 'data' as label, #date2 as data
union all
select 'data' as label, #date3 as data
) as a
group by label
) a
Result:
1:30:00

You can't convert datetime to handle non-real dates and times.
However, you can get an output that looks like a datetime, simply by concatenating an hours string with ':' with minutes, etc.
Lookup the DATEADD() and DATEDIFF() functions...

Related

Convert Excel formula (using Date and subtraction) into T-SQL

I am trying to write this Excel formula into T-SQL (to write a function).
Expected output is 0.71944444, but currently my output (using T-SQL) is 24.0000.
I am not sure why we have to add a day to same date and subtract the same date.
Bottom is a screenshot from Excel:
This is what I have so far in T-SQL:
CREATE FUNCTION [dbo].[fn_0921] (
#Punch_Start nvarchar(max)
)
RETURNS decimal(36, 8) AS
BEGIN
DECLARE #return_value nvarchar(max);
SET #return_value =
DATEDIFF(
MINUTE, CAST(#Punch_Start AS datetime2),
(
dateadd(
day, 1, CAST(#Punch_Start AS datetime2)
)
)
)
/ (60.0)
RETURN #return_value
END;
Thanks for help.
The Excel formula is returning the difference between the datetime in cell K4 & the start of the next day (i.e. 7/26/2021 00:00) as a fraction of a whole day. The following is the equivalent in T-SQL:
DECLARE #Punch_Start datetime2 = '7/25/2021 06:44';
SELECT DATEDIFF(
MINUTE,
#Punch_Start,
CAST(
CAST(
DATEADD(DAY, 1, #Punch_Start)
AS date) -- Add 1 day to #Punch_Start & cast as date to remove the time component - this is the start of the next day
AS datetime2) -- Cast back to datetime2 to get the difference in minutes
) / 1440.; -- Divide the difference in minutes by the number of minutes in a day (60 minutes per hour, 24 hours per day) to get the difference as a fraction of a day
This can probably help you:
DECLARE #date DATETIME2 = '2021-07-25 06:44'
DECLARE #seconds INT = DATEDIFF(second, CAST(#date AS date), #date)
DECLARE #secondsFromEnd FLOAT = 86400 - #seconds
SELECT #secondsFromEnd / 86400

Create date column from year and doy column

Is there a way to create a date column combining one column having the year as string and one column containing a date-of-year (doy) as integer?
I am aware of methods like SELECT EXTRACT(DOW FROM TIMESTAMP '2001-02-16 20:38:40'); or SELECT to_char(date_trunc('year', now()) + interval '169 days', 'MM/DD') but when trying to replace the "hard coded" stings with the columns I always get some kind of an error.
SELECT s.id, s.year, s.doy,
((s.year||'-01-01')::date + (s.doy||' days')::interval )::date AS date
FROM table_name AS s
the (s.year||'-01-01') or (s.doy||' days') concats the column value with a required string and the ::date or ::interval changes the resulting string type
You can use the make_date() function and add the number of days directly because date + integer is a valid operation:
select make_date(s.year, 1, 1) + s.doy as date
from ...

Concatenate Date and Time

I m trying to concatenate Date and Time using the below line but i m getting an error. Any help?
Time column type: Time (0)
CONVERT(date, getdate()) + ' ' + CONVERT(time(0), [Time]) AS Date_Time
The data types date and varchar are incompatible in the add operator.
There are a number of ways to do that - one of them is to convert both parts to DateTime which supports the add (+) operator:
SELECT CAST(CAST(GetDate() As Date) As DateTime) + (CAST([Time] As DateTime) As Date_Time
The casting of GetDate() to Date and back to DateTime resets the time portion to midnight.
I use the below and it is working:
CAST(CONVERT(date, getdate()) AS nvarchar) + ' ' + CAST(CONVERT(time(0), [Time]) AS nvarchar) AS Date_Time
Thanks to the rules for data type precedence you are trying to convert ' ' to a data type compatible with date and time, hence the error.
The next problem is that a date does not have a time component, hence to combine the two you need to use a datetime or similar data type.
declare #Time as Time = '12:30:00';
select #Time as Time,
-- Get today's date as a date .
Cast( GetDate() as Date ) as Today,
-- Get today's date and convert it to a datetime so that the time can be added.
Cast( Cast( GetDate() as Date ) as DateTime ) + Cast( #Time as DateTime ) as DateAndTime;
Note that time values need to be converted to datetime (or another compatible type) before adding the values. Curiously, SQL Server 2008 didn't require that step.

CROSSTAB PostgreSQL - Alternative for PIVOT in Oracle

I'm migrating a query of Oracle pivot to PostgreSQL crosstab.
create table(cntry numeric,week numeric,year numeric,days text,day text);
insert into x_c values(1,15,2015,'DAY1','MON');
...
insert into x_c values(1,15,2015,'DAY7','SUN');
insert into x_c values(2,15,2015,'DAY1','MON');
...
values(4,15,2015,'DAY7','SUN');
I have 4 weeks with 28 rows like this in a table. My Oracle query looks like this:
SELECT * FROM(select * from x_c)
PIVOT (MIN(DAY) FOR (DAYS) IN
('DAY1' AS DAY1 ,'DAY2' DAY2,'DAY3' DAY3,'DAY4' DAY4,'DAY5' DAY5,'DAY6' DAY6,'DAY7' DAY7 ));
Result:
cntry|week|year|day1|day2|day3|day4|day4|day6|day7|
---------------------------------------------------
1 | 15 |2015| MON| TUE| WED| THU| FRI| SAT| SUN|
...
4 | 18 |2015| MON| ...
Now I have written a Postgres crosstab query like this:
select *
from crosstab('select cntry,week,year,days,min(day) as day
from x_c
group by cntry,week,year,days'
,'select distinct days from x_c order by 1'
) as (cntry numeric,week numeric,year numeric
,day1 text,day2 text,day3 text,day4 text, day5 text,day6 text,day7 text);
I'm getting only one row as output:
1|17|2015|MON|TUE| ... -- only this row is coming
Where am I doing wrong?
ORDER BY was missing in your original query. The manual:
In practice the SQL query should always specify ORDER BY 1,2 to ensure that the input rows are properly ordered, that is, values with the same row_name are brought together and correctly ordered within the row.
More importantly (and more tricky), crosstab() requires exactly one row_name column. Detailed explanation in this closely related answer:
Crosstab splitting results due to presence of unrelated field
The solution you found is to nest multiple columns in an array and later unnest again. That's needlessly expensive, error prone and limited (only works for columns with identical data types or you need to cast and possibly lose proper sort order).
Instead, generate a surrogate row_name column with rank() or dense_rank() (rnk in my example):
SELECT cntry, week, year, day1, day2, day3, day4, day5, day6, day7
FROM crosstab (
'SELECT dense_rank() OVER (ORDER BY cntry, week, year)::int AS rnk
, cntry, week, year, days, day
FROM x_c
ORDER BY rnk, days'
, $$SELECT unnest('{DAY1,DAY2,DAY3,DAY4,DAY5,DAY6,DAY7}'::text[])$$
) AS ct (rnk int, cntry int, week int, year int
, day1 text, day2 text, day3 text, day4 text, day5 text, day6 text, day7 text)
ORDER BY rnk;
I use the data type integer for out columns cntry, week, year because that seems to be the (cheaper) appropriate type. You can also use numeric like you had it.
Basics for crosstab queries here:
PostgreSQL Crosstab Query
I got this figured out from http://www.postgresonline.com/journal/categories/24-tablefunc
select year_wk_cntry.t[1],year_wk_cntry.t[2],year_wk_cntry.t[3],day1,day2,day3,day4,day5,day6,day7
from crosstab('select ARRAY[country :: numeric,week,year] as t,days,min(day) as day
from x_c group by country,week,year,days order by 1,2
','select distinct days from x_c order by 1')
as year_wk_cntry (t numeric[],day1 text,day2 text,day3 text,
day4 text, day5 text,day6 text,day7 text);
thanks!!

Most Performant Way to Convert DateTime to Int Format

I need to convert Datetime fields to a specifically formatted INT type. For example, I want
2000-01-01 00:00:00.000 to convert to 20010101.
What is the most performant way to make that conversion for comparison in a query?
Something like:
DATEPART(year, orderdate) * 10000 + DATEPART(month, orderdate) * 100 +
DATEPART(day, orderdate)
or
cast(convert(char(8), orderdate, 112) as int)
What's the most performant way to do this?
Your example of cast(convert(char(8), orderdate, 112) as int) seems fine to me. It quickly gets the date down to the format you need and converted to an int.
From an execution plan standpoint, there seems to be no difference between the two.
You can try with TSQL builtin functions.
It's not .NET tick compatible but it's still FAST sortable and you can pick your GRANULARITY on demand:
SELECT setup.DateToINT(GETDATE(), 4) -- will output 2019 for 2019-06-06 12:00.456
SELECT setup.DateToINT(GETDATE(), 6) -- will output 201906 for 2019-06-06 12:00.456
SELECT setup.DateToINT(GETDATE(), 20) -- will output 20190606120045660 for 2019-05-05 12:00.456
CREATE FUNCTION setup.DateToINT(#datetime DATETIME, #length int)
RETURNS
BIGINT WITH SCHEMABINDING AS
BEGIN
RETURN CONVERT(BIGINT,
SUBSTRING(
REPLACE(REPLACE(
REPLACE(REPLACE(
CONVERT(CHAR(25), GETDATE(), 121)
,'-','')
,':','')
,' ','')
,'.','')
,0
,#length+1)
)
END
GO
Is this what you need
SELECT REPLACE(CONVERT(VARCHAR(10),'2010-01-01 00:00:00.000',101),'-','')
When you pass '2010-01-01 00:00:00.000' directly in your code, the SELECT statement looks at it as a string and not a datetime data type. Its not the same as selecting a datetime field directly.
There is no need to do outer CAST because SQL Server will do implicit conversion, here is a proof.
DECLARE #t DATETIME = '2010-01-10 00:00:00.000',#u INT
SELECT #u = CONVERT(CHAR(8), #t, 112)
IF ISNUMERIC(#u) = 1
PRINT 'Integer'