Convert datetime to float with specified format in T-SQL - sql-server

I need to convert a DateTime into a float with the following format:
YYYY.[fractionalPart]
(example 4/1/2020 3:40:00 AM should get converted to 2020.249051)
In Excel, this is as simple as
YEAR(myDate) + YEARFRAC(startOfYear, myDate, 1)
but I can't find a similarly straightforward way to do this in T-SQL.
I wrote the following scalar function to do the job:
ALTER function [dbo].[DecimalDate] (#Datetime datetime)
RETURNS FLOAT
AS
BEGIN
DECLARE #yearPart int
DECLARE #secondsPart float --fractional portion of the year, in seconds
DECLARE #secondsFullYear float --total number of seconds in year
DECLARE #FirstDayOfYear Date
Set #yearPart = DATEPART(yy,#Datetime)
Set #FirstDayOfYear = CONVERT(Date, CAST(#yearPart AS varchar)) -- [alternative:] DATEADD(yy, DATEDIFF(yy,0,#Datetime), 0)
SET #secondsPart = datediff(second,#FirstDayOfYear,#Datetime)
SET #secondsFullYear = datediff(second,#FirstDayOfYear, DATEADD(yy, 1, #FirstDayOfYear))
RETURN (SELECT #yearPart + #secondsPart/#secondsFullYear AS DecimalDate )
END
...but this is slower than I had hoped, and it also just seems way too complicated for such a simple task. (If only T-SQL supported YEARFRAC, it would certainly simplify things!)
Any suggestions from you SQL gurus?

I don't know how better this is, but it's shorter:
SELECT CAST(DATEDIFF(s, #d, DATEFROMPARTS(YEAR(#d), 1, 1)) as FLOAT) /
DATEDIFF(s, DATEFROMPARTS(YEAR(#d) + 1, 1, 1), DATEFROMPARTS(YEAR(#d), 1, 1)) +
YEAR(#d)
(SQLFiddle)

I suspect this will run slightly faster:
ALTER FUNCTION [dbo].[DecimalDate](#DateTime datetime)
RETURNS FLOAT
AS
BEGIN
DECLARE #year INT
DECLARE #daysInYear INT
SET #year = DATEPART(year, #DateTime)
SET #daysInYear = 337 + DATEPART(DAY, EOMONTH(DATEFROMPARTS(#year, 2, 1)))
RETURN #year + (DATEPART(DAYOFYEAR, #DateTime) - 1 + (DATEDIFF(second, CONVERT(DATE, #DateTime), #DateTime)/86400.0) ) / #daysInYear
END

The main problem is not in your code per se, it's the scalar function - they tend to be slow in SQL Server.
Apart from rewriting the code to be as inline a possible, you can use some other tricks such as ones shown in my code below:
create function dbo.YearFraction (
#dt datetime
)
/*
User-defined substitution of the Excel' YEARFRAC() function.
20150915, RFW - initial release
*/
returns float with schemabinding, returns null on null input as begin
return (
select
year(#dt) + cast(datediff(second, c.FDY, #dt) as float) / datediff(second, c.FDY, dateadd(year, 1, c.FDY))
from (
select cast(cast(year(#dt) as varchar) + '0101' as datetime) as [FDY]
) c
);
end;
go

Related

SQL Selecting only records matching year and month between two dates

I am making a SQL procedure to get me a selection of workers from the table,
where each worker has startJob and endJob date.
What I need is to make selection of all the workers who were on the job
during specified #year and #month. (#year & #month are input params)
I would really appreciate an advice how to solve this the easiest way.
Thx for any help.
If i proper understood you then try something like:
CREATE PROCEDURE [usp_Procedure]
#Year int
, #Month int
AS
BEGIN
select * from workers
where #Year between YEAR(startJob) and YEAR(endJob)
and #Month between MONTH(startJob) and MONTH(endJob)
END
You can make a date from the year and month parameters and compare to the start and end job fields.
This will return the first day of the month/year entered, and return results where this date is between the StartJob and EndJob
select *
from worker
where
CAST(
CAST(#Year AS VARCHAR(4)) +
RIGHT('0' + CAST(#month AS VARCHAR(2)), 2) +
RIGHT('0' + CAST(01 AS VARCHAR(2)), 2)
AS DATETIME)
between
StartJob and EndJob
Create a DATE based on the start of the month for the given year
DECLARE #Year INT = 1999
DECLARE #Month INT = 1
DECLARE #Date DATE = CONVERT(DATE, CONVERT(CHAR, #Month) + '-' + CONVERT(CHAR, #Month) + '-01')
-- SQL Server should now correctly handle the logic to filter out the relevant rows
SELECT *
FROM worker w
WHERE #Date BETWEEN w.startJob AND w.endJob
If you'd rather use the last day of the month, then you could use EOMONTH(#Month) for your DAY.
FINAL SOLVED SELECT ROWS BETWEEN TWO DATE USING ONLY MONTH AND YEAR
begin
declare #trvl_start_date as date
declare #trvl_stop_date as date
declare #trim_start as varchar(20)
declare #trim_end as varchar(20)
declare #trvl_start_date_new as date
declare #trvl_stop_date_new as date
set #trvl_start_date ='2015-10-12'
set #trvl_stop_date='2016-01-01'
set #trim_start = LEFT(#trvl_start_date, CHARINDEX('-', #trvl_start_date) + 2) + '-01'
set #trim_end = LEFT(#trvl_stop_date, CHARINDEX('-', #trvl_stop_date) + 2) + '-01'
print #trim_start
print #trim_end
set #trvl_start_date_new = #trim_start
set #trvl_stop_date_new =dateadd(month,-1,#trim_end)
print #trvl_start_date_new
print #trvl_stop_date_new
SELECT fee_catg_srno FROM dbo.fee_catg_tbl
WHERE (acd_yr = 1) AND (recycle = 1) AND (acc_head_srno = 3) AND pay_date between #trvl_start_date_new and #trvl_stop_date_new
end

How to convert number of minutes to hh:mm format in TSQL?

I have a select query that has DURATION column to calculate number of Minutes . I want to convert those minutes to hh:mm format.
Duration has values like 60, 120,150
For example:
60 becomes 01:00 hours
120 becomes 02:00 hours
150 becomes 02:30 hours
Also, this is how I retrieve DURATION (Minutes)
DATEDIFF(minute, FirstDate,LastDate) as 'Duration (Minutes)'
You can convert the duration to a date and then format it:
DECLARE
#FirstDate datetime,
#LastDate datetime
SELECT
#FirstDate = '2000-01-01 09:00:00',
#LastDate = '2000-01-01 11:30:00'
SELECT CONVERT(varchar(12),
DATEADD(minute, DATEDIFF(minute, #FirstDate, #LastDate), 0), 114)
/* Results: 02:30:00:000 */
For less precision, modify the size of the varchar:
SELECT CONVERT(varchar(5),
DATEADD(minute, DATEDIFF(minute, #FirstDate, #LastDate), 0), 114)
/* Results: 02:30 */
This function is to convert duration in minutes to readable hours and minutes format. i.e 2h30m. It eliminates the hours if the duration is less than one hour, and shows only the hours if the duration in hours with no extra minutes.
CREATE FUNCTION [dbo].[MinutesToDuration]
(
#minutes int
)
RETURNS nvarchar(30)
AS
BEGIN
declare #hours nvarchar(20)
SET #hours =
CASE WHEN #minutes >= 60 THEN
(SELECT CAST((#minutes / 60) AS VARCHAR(2)) + 'h' +
CASE WHEN (#minutes % 60) > 0 THEN
CAST((#minutes % 60) AS VARCHAR(2)) + 'm'
ELSE
''
END)
ELSE
CAST((#minutes % 60) AS VARCHAR(2)) + 'm'
END
return #hours
END
To use this function :
SELECT dbo.MinutesToDuration(23)
Results: 23m
SELECT dbo.MinutesToDuration(120)
Results: 2h
SELECT dbo.MinutesToDuration(147)
Results: 2h27m
Hope this helps!
I'm not sure these are the best options but they'll definitely get the job done:
declare #durations table
(
Duration int
)
Insert into #durations(Duration)
values(60),(80),(90),(150),(180),(1000)
--Option 1 - Manually concatenate the values together
select right('0' + convert(varchar,Duration / 60),2) + ':' + right('0' + convert(varchar,Duration % 60),2)
from #Durations
--Option 2 - Make use of the time variable available since SQL Server 2008
select left(convert(time,DATEADD(minute,Duration,0)),5)
from #durations
GO
DECLARE #Duration int
SET #Duration= 12540 /* for example big hour amount in minutes -> 209h */
SELECT CAST( CAST((#Duration) AS int) / 60 AS varchar) + ':' + right('0' + CAST(CAST((#Duration) AS int) % 60 AS varchar(2)),2)
/* you will get hours and minutes divided by : */
For those who need convert minutes to time with more than 24h format:
DECLARE #minutes int = 7830
SELECT CAST(#minutes / 60 AS VARCHAR(8)) + ':' + FORMAT(#minutes % 60, 'D2') AS [Time]
Result:
130:30
This seems to work for me:
SELECT FORMAT(#mins / 60 * 100 + #mins % 60, '#:0#')
Thanks to A Ghazal, just what I needed. Here's a slightly cleaned up version of his(her) answer:
create FUNCTION [dbo].[fnMinutesToDuration]
(
#minutes int
)
RETURNS nvarchar(30)
-- Based on http://stackoverflow.com/questions/17733616/how-to-convert-number-of-minutes-to-hhmm-format-in-tsql
AS
BEGIN
return rtrim(isnull(cast(nullif((#minutes / 60)
, 0
) as varchar
) + 'h '
,''
)
+ isnull(CAST(nullif((#minutes % 60)
,0
) AS VARCHAR(2)
) + 'm'
,''
)
)
end
select convert(varchar(5),dateadd(mi,DATEDIFF(minute, FirstDate,LastDate),'00:00'),114)
In case someone is interested in getting results as
60 becomes 01:00 hours, 120 becomes 02:00 hours, 150 becomes 02:30 hours, this function might help:
create FUNCTION [dbo].[MinutesToHHMM]
(
#minutes int
)
RETURNS varchar(30)
AS
BEGIN
declare #h int
set #h= #minutes / 60
declare #mins varchar(2)
set #mins= iif(#minutes%60<10,concat('0',cast((#minutes % 60) as varchar(2))),cast((#minutes % 60) as varchar(2)))
return iif(#h <10, concat('0', cast(#h as varchar(5)),':',#mins)
,concat(cast(#h as varchar(5)),':',#mins))
end
I would do the following (copy-paste the whole stuff below into immediate window / query window and execute)
DECLARE #foo int
DECLARE #unclefoo smalldatetime
SET #foo = DATEDIFF(minute, CAST('2013.01.01 00:00:00' AS datetime),CAST('2013.01.01 00:03:59' AS datetime)) -- AS 'Duration (Minutes)'
SET #unclefoo = DATEADD(minute, #foo, '2000.01.01')
SELECT CAST(#unclefoo AS time)
#foo stores the value you generate in your question. The "trick" comes by then:
we create a smalldatetime variable (in my case it's yyyy.mm.dd format) and increment it with your int value, then display (or store if you want) the time part only.
declare function dbo.minutes2hours (
#minutes int
)
RETURNS varchar(10)
as
begin
return format(dateadd(minute,#minutes,'00:00:00'), N'HH\:mm','FR-fr')
end
How to get the First and Last Record time different in sql server....
....
Select EmployeeId,EmployeeName,AttendenceDate,MIN(Intime) as Intime ,MAX(OutTime) as OutTime,
DATEDIFF(MINUTE, MIN(Intime), MAX(OutTime)) as TotalWorkingHours
FROM ViewAttendenceReport WHERE AttendenceDate >='1/20/2020 12:00:00 AM' AND AttendenceDate <='1/20/2020 23:59:59 PM'
GROUP BY EmployeeId,EmployeeName,AttendenceDate;
If you want a notation of XX days YY hours and ZZ min, just try:
SELECT
CAST(f.TimeAmount / 1440 AS VARCHAR(8)) + 'd ' +
CAST((f.TimeAmount % 1440) / 60 AS VARCHAR(8)) + 'h ' +
FORMAT(f.TimeAmount % 60, 'D2') + 'min' AS [TIME_TEXT]
FROM
MyTable f

SQL Server - how to dynamically determine financial year?

Every year I have to update my company's financial reports to include the new financial year (as the year isn't coterminus with the calendar year), so I do.....
Case
when ST_date >= '1996.11.01 00:00:00' and st_date < '1997.11.01 00:00:00'
then '96-97'
[etc]
end as year,
Every year I have to remember which reports I need to amend - most years I forget one!
...Is there a simple dynamic way to determine this?
You could definitely write a simple stored function in SQL Server to determine the financial year based on the date:
CREATE FUNCTION dbo.GetFinancialYear (#input DATETIME)
RETURNS VARCHAR(20)
AS BEGIN
DECLARE #FinYear VARCHAR(20)
SET #FinYear =
CASE
WHEN #INPUT >= '19961101' AND #input < '19971101' THEN '96-97'
WHEN #INPUT >= '19971101' AND #input < '19981101' THEN '97-98'
ELSE '(other)'
END
RETURN #FinYear
END
and then just use that in all your queries.
SELECT
somedate, dbo.GetFinancialYear(somedate)
......
If you need to add a new financial year - just update the one function, and you're done !
Update: if you want to make this totally dynamic, and you can rely on the fact that the financial year always starts on Nov 1 - then use this approach instead:
CREATE FUNCTION dbo.GetFinancialYear (#input DATETIME)
RETURNS VARCHAR(20)
AS BEGIN
DECLARE #FinYear VARCHAR(20)
DECLARE #YearOfDate INT
IF (MONTH(#input) >= 11)
SET #YearOfDate = YEAR(#input)
ELSE
SET #YearOfDate = YEAR(#input) - 1
SET #FinYear = RIGHT(CAST(#YearOfDate AS CHAR(4)), 2) + '-' + RIGHT(CAST((#YearOfDate + 1) AS CHAR(4)), 2)
RETURN #FinYear
END
This will return:
05/06 for a date such as 2005-11-25
04/05 for a date such as 2005-07-25
Have a look at this example:
declare #ST_Date datetime = '20120506'
SELECT
convert(char(2),DateAdd(m,-10,#ST_DATE),2)+'-'+
convert(char(2),DateAdd(m,+ 2,#ST_DATE),2) as year
As a column expression:
convert(char(2),DateAdd(m,-10,ST_DATE),2)+'-'+
convert(char(2),DateAdd(m,+ 2,ST_DATE),2) as year
Pretty trivial!
The way I handle these problems (financial year, pay period etc) is to recognize the fact that financial years are the same as any year, except they start X months later. The straightforward solution is therefore to shift the FY by the number of months back to the calendar year, from which to do any "annual" comparisons or derivation of "year" (or "month").
Declare #FinancialMonth Varchar(100)=NULL,#Month smallint,#Date DateTime='04/06/2013'
BEGIN TRY
SELECT #FinancialMonth='01-'+IsNULL(#FinancialMonth,'April')+'-'+Cast(year(getdate()) as varchar)
SELECT #Month=(Month(Cast(#FinancialMonth as datetime))-1) * -1
END TRY
BEGIN CATCH
SELECT
ERROR_NUMBER() AS ErrorNumber,'Invalid Financial Month' ErrorMessage
END CATCH
SELECT Month((CONVERT([varchar](10),dateadd(month,(#Month),#Date),(101)))) FinancialMonth,
Year((CONVERT([varchar](10),dateadd(month,(#Month),#Date),(101)))) FinancialYear
,DatePart(qq,(CONVERT([varchar](10),dateadd(month,(#Month),#Date),(101)))) FinancialQuarter
This one works for me and sets it as the actual FY end date.
SET #enddatefy = convert(DATE, str(datepart(yyyy,DateAdd(m,-6,#enddate))+1)+'0630',112)
SET #enddatefyid = str(datepart(yyyy,DateAdd(m,-6,#enddate))+1)+'0630'
datename(YEAR, DATEADD(M,-3,Date)) +'-'+ cast((datepart(YEAR, DATEADD(M,-3,Date)) + 1) %100 as varchar(2))
Calculate on Column 'Date'
Financial year ranges from 1st April to 31st March
Create FUNCTION dbo.GetFinancialYear (#input DATETIME)
RETURNS VARCHAR(20)
AS BEGIN
DECLARE #FinYear VARCHAR(20)
IF (MONTH(#input) > 3)
SET #FinYear = RIGHT(CAST(Year(#input) AS CHAR(4)), 4) + '-' + RIGHT(CAST((Year(#input) + 1) AS CHAR(4)), 2)
ELSE
SET #FinYear = RIGHT(CAST((Year(#input) - 1) AS CHAR(4)), 4) + '-' + RIGHT(CAST(Year(#input) AS CHAR(4)), 2)
RETURN #FinYear
END
Declare #date1 datetime = '2017-07-01'
Select Case
When Month(#date1)>=7 Then 'FY'+Convert(NVARCHAR(10),(Right(year(getdate()),2)+1))
Else 'FY'+Convert(NVARCHAR(10),(Right(year(getdate()),2)))
End
This works for me, where the financial year starts in July.
CASE WHEN DatePart(mm, [YourDate]) >= 7
THEN convert(varchar(10), YEAR([YourDate])) +' / '+ Convert(varchar(10), YEAR([YourDate]) + 1 )
ELSE Convert(varchar(10), YEAR([YourDate]) - 1) +' / '+ Convert(varchar(10), YEAR([YourDate]) )
END AS [Financial Year],

Subtract two time values in SQL Server

How to subtract two time values in SQL Server 2008. I am using time variables in a stored procedure.
Please help.
You can use DATEDIFF():
SELECT DATEDIFF(Day, startDate, endDate)
FROM table
SELECT DATEDIFF(Second, date, GETDATE())
FROM table
DECLARE #END TIME = '16:00:00.0000000' ,
#START TIME = '01:00:00.0000000'
SELECT convert(TIME,dateadd(ms,DateDiff(ss, #START, #END )*1000,0),114)
following expression works for me
declare #starttime Time, #endtime Time
set #starttime='18:45'
set #endtime='22:45'
select DATEDIFF(HH,#starttime, #endtime)
output: 4
Even You are using offset value or normal dates this code will give you appropriate answers.
BEGIN TRY
DECLARE #OffSetVal1 VARCHAR(39) = '2019-12-02 09:15:29 +14:00'
, #OffSetVal2 VARCHAR(39) = '2019-12-02 09:15:29 +12:30'
, #minutes INT = 0
, #OffSetDiff VARCHAR(19) = ''
, #HourDiff INT = 0
SET #HourDiff = DATEDIFF(HOUR,#OffSetVal1,#OffSetVal2) -- To Check digits of hours.
SET #minutes = DATEDIFF(MINUTE,#OffSetVal1,#OffSetVal2) -- To Convert minutes to hours.
SET #OffSetDiff = #minutes / 60 + (#minutes % 60) / 100.0 -- To Check '+' Or '-' And To Get Value.
SELECT CASE WHEN CAST(#OffSetDiff AS FLOAT) <= 0
THEN (CASE WHEN #HourDiff < 10
THEN FORMAT(CAST(#OffSetDiff AS FLOAT),'0#.00')
ELSE FORMAT(CAST(#OffSetDiff AS FLOAT),'0#.00')
END)
ELSE (CASE WHEN #HourDiff < 10
THEN '+'+FORMAT(CAST(#OffSetDiff AS FLOAT),'0#.00')
ELSE '+'+FORMAT(CAST(#OffSetDiff AS FLOAT),'0#.00')
END)
END
END TRY
BEGIN CATCH
PRINT N'It seems you provided an invalid DateTimeOffSet Parameter. '
END CATCH
ANS :- +01.30 (# The offset differences with hour and minute and if you don't want that '+' simply remove from code & then run the code)

In TSQL, how would you convert from an int to a datetime and give the age?

What would be the sql for the following,
I have a date of birth in an int field,
ie YYYYMMDD = 19600518
I would like to get the age.
None of the other answers are actually calculating age. They're calculating the number of year boundaries and not taking the birthday into account.
To calculate the age, you'll need to do something like this:
DECLARE #d DATETIME
SET #d = CONVERT(DATETIME, CONVERT(VARCHAR(8), 19600518), 112)
SELECT DATEDIFF(year, #d, GETDATE())
- CASE WHEN DATEADD(year, DATEDIFF(year, #d, GETDATE()), #d) <= GETDATE()
THEN 0 ELSE 1 END AS Age
Most of the other answers are not calculating age - just whole years (e.g. Jan 1 2009 is one "year" after Dec 31 2008). Thus, if you use most of the calculations on this page, you will return an incorrect age for half of the year, on average. Luke is the only person who has seen this but his answer strikes me as too complicated - there is an easier way:
Select CAST(DATEDIFF(hh, [birthdate], GETDATE()) / 8766 AS int) AS Age
(NOTE: Thanks go to 'Learning' for making a great catch on my original algorithm - this is a revision that uses hours instead of days)
Because the rounding here is very granular, this is almost perfectly accurate for every day of every year. The exceptions are so convoluted that they are almost humorous: every fourth year the age returned will be one year too young if we A) ask for the age before 6:00 AM, B) on the person's birthday and C) their birthday is after February 28th. Of course, depending on what time someone was born this might 'technically' be correct! In my setting, this is a perfectly acceptable compromise.
Here is a loop that prints out ages to show that this works.
Declare #age int;
Declare #BirthDate datetime;
Declare #Year int;
Set #Year = 2008;
WHILE (#Year > 1930)
BEGIN
-- Put today's date where you see '-03-18'
SET #BirthDate = CAST(Cast(#Year as varchar(4)) + '-03-18' AS DATETIME)
SELECT #age=CAST(DATEDIFF(hh, #BirthDate, GETDATE()) / 8766 AS int);
Print Cast(#Year as varchar) + ' Age: ' + Cast(#age as varchar);
Set #Year = #Year - 1;
END;
Finally, this is the version that will also convert Paul's integer date to a real date:
CAST(DATEDIFF(hh, Convert(Datetime, Convert(varchar(8), [birthdate]), 112), GETDATE()) / 8766 AS int) AS Age
DECLARE #dateSt VARCHAR(8)
DECLARE #startDt DATETIME
-- Set the start date string
SET #dateSt = '19600518'
-- Make it a DATETIME (the ISO way)
SET #startDt = CAST(SUBSTRING(#dateSt, 1, 4) + '-' +
SUBSTRING(#dateSt, 5, 2) + '-' +
SUBSTRING(#dateSt, 7, 2) AS DATETIME)
-- Age in Days
SELECT DATEDIFF(D, #startDt, getdate())
Age in years :
select datediff(YY, convert(datetime, convert(varchar, 19600518)), getdate())
[EDIT]
-- I forgot to declare the variables
declare #birthday datetime;
set #birthday = convert(datetime,convert(varchar, 19600518), 112);
declare #datetoday datetime;
set #datetoday = getdate();
select
(
CASE
WHEN dateadd(year, datediff (year, #birthday, #datetoday), #birthday) <= #datetoday
THEN datediff (year, #birthday, #datetoday)
ELSE datediff (year, #birthday, #datetoday) - 1
END) as age;
Here's a one-liner way to do it:
CONVERT(DATETIME, CONVERT(VARCHAR(8),19600518), 112)
But beware! This relies on T-SQL, and probably won't work in other SQL environments.
Please note that the "style" of 112 is simply the "ISO" date format of yyyymmdd. (Something I found in some CONVERT documentation.)
This is a reason why you should NOT ever store dates as anything except a datetime datatype. The best fix is to change your datatype and convert all the dates once (wouldn't be surprised if there are few invalid ones in there either). then you never have to do these workarounds again.
I worked it out and got same as #Learning
select dob, datediff(year, convert(datetime, convert(varchar(8),[dob])) ,getdate()) as age
from [mytable]
where IsDate(convert(varchar(8),[dob])) = 1
NB. I needed the IsDate as well as there were some invalid dates in the data.
Edit. Here is an article from SQLServerCentral on calculating age.

Resources