Getting FRACTIONAL difference (YEARS) between two dates in T SQL - sql

I need to detect a difference between two dates, such that when Date_1 = 12-jan-2010 and Date_2 = 01-jan-2016 I would NOT get 6 but a number < 6.
SELECT DATEDIFF(YEAR,'12-jan-2010','01-jan-2016')
DATEDIFF returns 6 in the above case.

It depends on required precision, try following:
SELECT DATEDIFF(DAY, #d1, #d2)/365.25

I guess this resolves,
DECLARE #STARTDATE DATE='12-jan-2010'
DECLARE #ENDDATE DATE='01-jan-2016'
DECLARE #TOTALDAY DECIMAL(18,2)=DATEDIFF(day,#STARTDATE,#ENDDATE)
DECLARE #AVGYEAR DECIMAL(18,2)= ((365*DATEDIFF(YEAR,#STARTDATE,#ENDDATE))+
dbo.LEAP_YEAR(#STARTDATE,#ENDDATE))/CONVERT(DECIMAL(18,2),
DATEDIFF(YEAR,#STARTDATE,#ENDDATE))
SELECT CONVERT(decimal(18,2),#TOTALDAY/#AVGYEAR) AS DiffDate ---This will provide you result in decimal
this the function which return no of leap years between dates.
ALTER FUNCTION LEAP_YEAR(#START DATE,#END DATE)
RETURNS INT
AS BEGIN
DECLARE #COUNT INT = 0,#Z INT = DATEPART(YYYY,#START)
DECLARE #X INT =DATEPART(YYYY,#START)
DECLARE #Y INT =DATEPART(YYYY,#END)
IF (DATEPART(MM,#START) >2)
SET #X=#X+1
IF (DATEPART(MM,#END) <2)
SET #Y=#Y-1
WHILE (#X <= #Y)
BEGIN
SET #COUNT = #COUNT +
(CASE WHEN (#X%4 = 0 AND #X%100 !=0) OR #X%400 = 0
THEN 1
ELSE 0 END)
SET #X = #X + 1
END
RETURN #COUNT
END

If I've got it right. Just Add result of DATEDIFF (6 in this case) to the start date if it > the end date then just subtract 1 year so you will get 5 full years:
DECLARE #StartD DATETIME;
DECLARE #FinalD DATETIME;
SET #StartD = '12-jan-2010';
SET #FinalD = '11-jan-2016';
SELECT DATEDIFF(YEAR,#StartD,#FinalD)
- CASE WHEN DATEADD(YEAR,DATEDIFF(YEAR,#StartD,#FinalD),#StartD)>#FinalD
THEN 1 ELSE 0 END

First of all, I wish to thank all those that spent time trying to provide a simple and reliable solution. Finally, I decided to resolve it as follows:
Supposed that I want to know if #n full years passed between two dates, then:
DECLARE #n INT ;
DECLARE #Old_Date DATETIME ;
DECLARE #New_Date DATETIME ;
SET #n = <some_value> ;
SET #Old_Date = <some_value> ;
SET #New_Date = <some_value> ;
IF (DATEADD(YEAR ,#n , #Old_Date) <= #New_Date)
SET #Result = 'YES' ;
ELSE
SET #Result = 'NO' ;
[of course, a check needs to be included to verify that #Old_Date < #New_Date]
I can't say this can be proved (mathematically) as correct in all possible scenarios, but it provides the needed answer to me.
Thanks to all again.

Related

Create a trigger to take the signintime and signouttime and put to hours worked for SQL

I'm trying to create a trigger to take the signintime and signouttime and put to hours worked.
I started with the code below, but its not quite working the way I want
BEGIN
declare #au_in char( 11 )
declare #au_out char( 11 )
declare #rowcount char( 11 )
set rowcount 0
select jargonf1_sqladmin.StaffRegisterV2.signInTime,jargonf1_sqladmin.StaffRegisterV2.signOutTime from jargonf1_sqladmin.StaffRegisterV2
set rowcount 1
while #rowcount <> 0
BEGIN
select #au_in = signInTime from jargonf1_sqladmin.StaffRegisterV2
select #au_out = signOutTime from jargonf1_sqladmin.StaffRegisterV2
DECLARE #Sign_In datetime
DECLARE #Sign_Out datetime
DECLARE #WorkedHours DECIMAL(18,2)
DECLARE #hours varchar(30)
DECLARE #minutes varchar(2)
DECLARE #seconds varchar(30)
set #Sign_In = #au_in
set #Sign_Out = #au_out
set #seconds = abs(datediff(second, #Sign_In, #Sign_Out))
set #hours = #seconds / 3600
set #minutes = (#seconds - (#hours * 3600)) / 60
set #seconds = (#seconds - (#hours * 3600) - (#minutes * 60))
set #WorkedHours = #hours + '.' + #minutes
SELECT #WorkedHours AS WorkedHours
set #rowcount = 2 + 1
END
set rowcount 0
END
Does this work at all? I see a couple of issues immediately:
First, you use DECLARE #WorkedHours DECIMAL(18,2), but then you concatenate a string using set #WorkedHours = #hours + '.' + #minutes. This must result in an error converting varchar to int.
Second, your loop should never run more than once (I think this is the problem you are reporting) because you are using a constant: set #rowcount = 2 + 1 when I think you want set#rowcount = #rowcount + 1. But then you need to test for the number of rows you want to convert.
Honestly, this procedure is something that I think you can do with a SELECT query that has a subquery to get the datediff, then just calculates the hours and minutes from the value returned by the subquery.
ok I read through my code again, corrected school boy errors and got it to work, but can someone help with the lineSELECT #au_in,#au_out AS SignIN;
as I want to name the second column but it gives error likeSELECT #au_in,#au_out AS SignIN, SIGNOUT;any ideas??

Handle Julian and Real Times in one query

Hi Below is some sample data
DECLARE #Time TABLE (
[Time] VARCHAR(250),
[Count] VARCHAR(250)
)
INSERT INTO #Time ([Time],[Count])
VALUES
('13:52','2'),
('13:53','2'),
('13:54','5'),
('13:55','3'),
('13:56','3'),
('13:57','1'),
('13:58','4'),
('13:59','1'),
('130','72'),
('1300','61'),
('1301','40'),
('1302','51'),
('1303','53'),
('1304','59'),
('1305','62'),
('1306','36'),
('1307','56'),
('1308','52')
Select * from #Time
Is there a way to handle both data types so that it outputs as a real time
I use a function that partly works but am getting and error each time:
Msg 8116, Level 16, State 1, Line 1
Argument data type varchar is invalid for argument 2 of dateadd function.
The function I use is as follows and wondered if this can be adopted/changed to account for the poor data eg both types of Times (Julian/Regular).
CREATE FUNCTION [dbo].[udf_TR_PROTOS_JulianTimeToSQLDateTime]
(
-- Add the parameters for the function here
#JulianTime INT
)
RETURNS DATETIME
AS
BEGIN
-- Declare the return variable here
DECLARE #Result DATETIME
-- Add the T-SQL statements to compute the return value here
IF #JulianTime >= 0 AND #JulianTime < 1440 -- this ensures that the result will be in the range of a datetime data type
SET #Result = DATEADD(MINUTE, #JulianTime, CAST('00:00' AS TIME))
ELSE
SET #Result = CAST('00:00' AS TIME)
-- Return the result of the function
RETURN #Result
END
GO
ADDITION:
The COMPLETE datetime function is here:
CREATE FUNCTION [dbo].[udf_TR_PROTOS_JulianDateTimeToSQLDateTime] (
-- Add the parameters for the function here
#JulianDate INT,
#JulianTime INT = 0
)
RETURNS DATETIME
AS
BEGIN
-- Declare the return variable here
DECLARE #Result DATETIME
-- Add the T-SQL statements to compute the return value here
IF #JulianDate > 640333 -- this ensures that the result will be in the range of a datetime data type
BEGIN
SET #Result = DATEADD(DAY, (#JulianDate-429), CAST('Jan 1 0001' AS DATETIME2))
IF #JulianTime < 1440 AND #JulianTime >= 0 -- this ensures that the time is between 00:00 and 23:59
SET #Result = DATEADD(MINUTE, #JulianTime, #Result)
END
ELSE
SET #Result = 'Jan 1 1753'
-- Return the result of the function
RETURN #Result
END
GO
I am not sure what exactly are you trying to do from your give information. But I tried to modify the function to return time when I pass it JulianTime as a varchar. Here is the code -- (try to make changes accordingly though)
CREATE FUNCTION [dbo].[udf_TR_PROTOS_JulianTimeToSQLDateTime]
(
-- Add the parameters for the function here
#JulianTime VARCHAR(255)
)
RETURNS TIME(0)
AS
BEGIN
-- Declare the return variable here
DECLARE #Result TIME(0), #HOUR VARCHAR(2), #MINUTES VARCHAR(2)
-- DECLARE #JulianTime VARCHAR(255)
-- SET #JulianTime = '13:02'
SET #HOUR = SUBSTRING(#JulianTime, 1, 2)
IF(LEN(#JulianTime) = 4)
SET #MINUTES = SUBSTRING(#JulianTime, 3, 2)
IF(LEN(#JulianTime) = 5)
SET #MINUTES = SUBSTRING(#JulianTime, 4, 2)
SET #Result = CONCAT(#HOUR, ':', #MINUTES)
-- PRINT #RESULT
-- Return the result of the function
RETURN #Result
END
GO
I've updated the FUNCTION.
CREATE FUNCTION [dbo].[udf_TR_PROTOS_JulianTimeToSQLDateTime]
(
#timeString varchar(250)
)
RETURNS TIME
AS
BEGIN
DECLARE #Result TIME ;
/* Ensure that we're working with a fixed date */
DECLARE #epoch datetime = '19000101' ;
/* Check the string for ':' */
IF CHARINDEX(':',#timeString) > 0
/* Can #timeString be parsed as a valid time? */
SET #Result = TRY_PARSE(#timeString AS time) ; /* Returns NULL if bad time string. */
ELSE
/* No ':' so check if valid julian time. */
IF TRY_CONVERT(int,#timeString) IS NOT NULL AND CONVERT(int,#timeString) BETWEEN 0 AND 1439
SET #Result = CAST( DATEADD(minute, CONVERT(int,#timeString), #epoch) AS time) ;
ELSE
SET #Result = NULL ;
RETURN #Result ;
END
http://dbfiddle.uk/?rdbms=sqlserver_2016&fiddle=1ca82cd76b2932593262601b1742f602
This will only work if you're using something greater than SQL 2012+ because of TRY_PARSE and TRY_CONVERT. If you're lower than 2012, you can modify that bit to do what those functions essentially do.
Select
CASE WHEN CHARINDEX(':',[Time]) > 0 THEN CAST([Time] AS TIME) ELSE DATEADD(MINUTE, TRY_CAST([Time] AS INT), CAST('00:00' AS TIME)) END AS [RealTime],
*
from #Time
This appears to do the trick, thanks for the valuable input
For SQL 2008, use this function:
CREATE FUNCTION [dbo].[udf_TR_PROTOS_JulianTimeToSQLDateTime]
(
#timeString varchar(250)
)
RETURNS TIME
AS
BEGIN
DECLARE #Result time ;
/* Ensure that we're working with a fixed date */
DECLARE #epoch datetime = '19000101' ;
/* Check the string for ':' */
IF CHARINDEX(':',#timeString) > 0
IF ISDATE(#timeString) = 1
/* Is #timeString a valid time object? */
SET #Result = CAST(#timeString AS time) ;
ELSE
SET #Result = NULL ;
ELSE
/* No ':' so check if valid julian time. */
IF ISNUMERIC(#timeString) = 1 AND CONVERT(int,#timeString) BETWEEN 0 AND 1439
SET #Result = CAST( DATEADD(minute, CONVERT(int,#timeString), #epoch) AS time) ;
ELSE
SET #Result = NULL ;
RETURN #Result ;
END

With SQL Server add x number of days to a date in sql but also pass in the number of business days in a week

In SQL Server I would like to add x number of business days to a date but also pass in the amount of business days in a week. ie could be 5,6 ,7.
I found this on stack overflow that handles 5 days but not sure how to modify it so that you could specify the number of working days per week.
CREATE FUNCTION[dbo].[AddBusinessDays]
(#Date date, #n INT)
RETURNS DATE AS
BEGIN
DECLARE #d INT;
SET #d = 4 - SIGN(#n) * (4-DATEPART(DW, #Date));
RETURN DATEADD(D, #n + ((ABS(#n) + #d - 2) / 5) * 2 * SIGN(#n) - #d / 7, #Date);
END
This is it, I have converted it to a stored procedure for better understanding.
I have used SQL Server.
Alter procedure [dbo].[AddBusinessDaysP]
(#Date date,#n INT,#wds INT)
as
BEGIN
--exec AddBusinessDaysP '9/25/2014',2,4
Declare #totWeekEnds int
Set #totWeekEnds = (Case When #n > 7 then (#n / 7) else 1 end) * (7-#wds)
Declare #totDays int
Set #totDays = #n + #totWeekEnds
Select #n as DaysToAdd,#wds as DaysInWeek,#totWeekEnds as TotalWeekEnds,#totDays as TotalDaysToAdd,Dateadd(dd,#totDays,#Date) as Answer
END
I have also converted SP into Function as you want.
Alter FUNCTION[dbo].[AddBusinessDays](#Date date,#n INT,#wds INT)
RETURNS DATE AS
BEGIN
--Select [dbo].[AddBusinessDays]('9/25/2014',2,5)
Declare #totWeekEnds int
Set #totWeekEnds = (Case When #n > 7 then (#n / 7) else 1 end) * (7-#wds)
Declare #totDays int
Set #totDays = #n + #totWeekEnds
Return Dateadd(dd,#totDays,#Date)
END

Check if date is exists among the dates then add extra one day to that date

i want to add the days(for example 3 days) to the given date. Before adding days we can check the holidays which are already configured in one table.
Here is my sample code. But i am unable to achieve it.
declare #HolidaysList NVARCHAR(250) = '2014-06-29,2014-07-02,2014-07-18,2014-07-30,2014-10-26'
DECLARE #RDATE DATE = '2014-06-28'
declare #addDays int = 3
declare #count int = 0
while(#count < #addDays)
BEGIN
set #RDATE = DATEADD(DAY,1,#RDATE)
--print '1 ' +cast( #RDATE as nvarchar(100))
if exists(SELECT ITEM FROM fnSplit(#HolidaysList,',') WHERE item = #RDATE)
begin
SELECT #RDATE= CONVERT(VARCHAR(10),DATEADD(DAY,1,#RDATE),101)
PRINT 'if '+ CAST( #HRDATE AS NVARCHAR(100))
end
set #count = #count+1
END
PRINT #RDATE
Here fnSplit is a function which returns a table.
In the above script i have to add 3 days to #RDate. Before adding i have to check holidays list i.e in #HolidaysList. If holiday is come then we can add extra date.
in the above script the output is: 2014-08-03 because 29th is holiday and 2nd is also holiday. so output will be 2014-08-03
You can do this without loops:
DECLARE #Holidays TABLE (Item DATE);
INSERT #Holidays
VALUES ('2014-06-29'),('2014-07-02'),('2014-07-18'),('2014-07-30'),('2014-10-26');
DECLARE #RDATE DATE = '2014-06-28',
#addDays INT = 3;
WITH CTE AS
( SELECT *,
RowNumber = ROW_NUMBER() OVER(ORDER BY d.Date)
FROM ( SELECT DATEADD(DAY, ROW_NUMBER() OVER(ORDER BY object_id), #RDATE)
FROM sys.all_objects
) AS d (Date)
WHERE NOT EXISTS
( SELECT 1
FROM #Holidays AS h
WHERE h.Item = d.Date
)
)
SELECT Date
FROM CTE
WHERE RowNumber = #addDays;
The principal is this part:
SELECT Date = DATEADD(DAY, ROW_NUMBER() OVER(ORDER BY object_id), #RDATE)
FROM sys.all_objects
Will just generate a list of dates onwards from your starting date. You then exclude all holidays from this list using NOT EXISTS, and add a rank to all these days using ROW_NUMBER(). It is then just a case of selecting the date with the required rank.
declare #HolidaysList NVARCHAR(250) = '2014-06-29,2014-06-30,2014-07-01,2014-07-30,2014-07-18,2014-10-26'
DECLARE #RDATE DATE = '2014-06-28'
declare #addDays int = 3
declare #count int = 0
DECLARE #EXISTS int = 0
while(#count < #addDays)
BEGIN
set #RDATE = DATEADD(DAY,1,#RDATE)
if exists(SELECT ITEM FROM fnSplit(#HolidaysList,',') WHERE item = #RDATE)
set #EXISTS = #EXISTS+1
set #count = #count+1
END
if(#EXISTS is not null)
set #RDATE = DATEADD(DAY,#EXISTS,#RDATE)
print #RDATE

SQL Server FOR EACH Loop

I have the following SQL query:
DECLARE #MyVar datetime = '1/1/2010'
SELECT #MyVar
This naturally returns '1/1/2010'.
What I want to do is have a list of dates, say:
1/1/2010
2/1/2010
3/1/2010
4/1/2010
5/1/2010
Then i want to FOR EACH through the numbers and run the SQL Query.
Something like (pseudocode):
List = 1/1/2010,2/1/2010,3/1/2010,4/1/2010,5/1/2010
For each x in List
do
DECLARE #MyVar datetime = x
SELECT #MyVar
So this would return:-
1/1/2010
2/1/2010
3/1/2010
4/1/2010
5/1/2010
I want this to return the data as one resultset, not multiple resultsets, so I may need to use some kind of union at the end of the query, so each iteration of the loop unions onto the next.
edit
I have a large query that accepts a 'to date' parameter, I need to run it 24 times, each time with a specific to date which I need to be able to supply (these dates are going to be dynamic) I want to avoid repeating my query 24 times with union alls joining them as if I need to come back and add additional columns it would be very time consuming.
SQL is primarily a set-orientated language - it's generally a bad idea to use a loop in it.
In this case, a similar result could be achieved using a recursive CTE:
with cte as
(select 1 i union all
select i+1 i from cte where i < 5)
select dateadd(d, i-1, '2010-01-01') from cte
Here is an option with a table variable:
DECLARE #MyVar TABLE(Val DATETIME)
DECLARE #I INT, #StartDate DATETIME
SET #I = 1
SET #StartDate = '20100101'
WHILE #I <= 5
BEGIN
INSERT INTO #MyVar(Val)
VALUES(#StartDate)
SET #StartDate = DATEADD(DAY,1,#StartDate)
SET #I = #I + 1
END
SELECT *
FROM #MyVar
You can do the same with a temp table:
CREATE TABLE #MyVar(Val DATETIME)
DECLARE #I INT, #StartDate DATETIME
SET #I = 1
SET #StartDate = '20100101'
WHILE #I <= 5
BEGIN
INSERT INTO #MyVar(Val)
VALUES(#StartDate)
SET #StartDate = DATEADD(DAY,1,#StartDate)
SET #I = #I + 1
END
SELECT *
FROM #MyVar
You should tell us what is your main goal, as was said by #JohnFx, this could probably be done another (more efficient) way.
You could use a variable table, like this:
declare #num int
set #num = 1
declare #results table ( val int )
while (#num < 6)
begin
insert into #results ( val ) values ( #num )
set #num = #num + 1
end
select val from #results
This kind of depends on what you want to do with the results. If you're just after the numbers, a set-based option would be a numbers table - which comes in handy for all sorts of things.
For MSSQL 2005+, you can use a recursive CTE to generate a numbers table inline:
;WITH Numbers (N) AS (
SELECT 1 UNION ALL
SELECT 1 + N FROM Numbers WHERE N < 500
)
SELECT N FROM Numbers
OPTION (MAXRECURSION 500)
declare #counter as int
set #counter = 0
declare #date as varchar(50)
set #date = cast(1+#counter as varchar)+'/01/2013'
while(#counter < 12)
begin
select cast(1+#counter as varchar)+'/01/2013' as date
set #counter = #counter + 1
end
Off course an old question. But I have a simple solution where no need of Looping, CTE, Table variables etc.
DECLARE #MyVar datetime = '1/1/2010'
SELECT #MyVar
SELECT DATEADD (DD,NUMBER,#MyVar)
FROM master.dbo.spt_values
WHERE TYPE='P' AND NUMBER BETWEEN 0 AND 4
ORDER BY NUMBER
Note : spt_values is a Mircrosoft's undocumented table. It has numbers for every type. Its not suggestible to use as it can be removed in any new versions of sql server without prior information, since it is undocumented. But we can use it as quick workaround in some scenario's like above.
[CREATE PROCEDURE [rat].[GetYear]
AS
BEGIN
-- variable for storing start date
Declare #StartYear as int
-- Variable for the End date
Declare #EndYear as int
-- Setting the value in strat Date
select #StartYear = Value from rat.Configuration where Name = 'REPORT_START_YEAR';
-- Setting the End date
select #EndYear = Value from rat.Configuration where Name = 'REPORT_END_YEAR';
-- Creating Tem table
with [Years] as
(
--Selecting the Year
select #StartYear [Year]
--doing Union
union all
-- doing the loop in Years table
select Year+1 Year from [Years] where Year < #EndYear
)
--Selecting the Year table
selec]