SQL calculations not returning the expected values - sql

I've taken over a project from a different developer. This developer favoured working in SQL over C# code, however I am admittedly not very strong with SQL and am having trouble fixing a bug in the project.
A holiday system exists in this project. Each user has the following:
Entitled holidays
Holiday taken
Lieu/adjusted holidays
Holiday brought forward
A remaining holiday value is then calculated which takes the above values and generates a result.
For example, Person A has:
20 entitled
7.5 taken
3 in lieu
2 brought forward
The expected result would be 17.5 remaining ((20 + 3 + 2) - 7.5) however the result given from the existing stored procedure for the above example is 22
Another user has the following:
25 entitled
1 taken
2 in lieu
5 brought forward
This user is seeing 30 instead of the expected 31
Since each individual value for the above seems to working, it points to the calculation of the total remaining holiday that's not working. Here is the current procedure for calculating this value:
DECLARE #USER_HOLIDAYS decimal(18,2)
DECLARE #LieuAdjustedHolidays decimal(18,2)
DECLARE #Total_Holidays decimal(18,2)
DECLARE #Remain_Holidays decimal(18,2)
SELECT #USER_HOLIDAYS = SUM(HolidayEntitlement + HolidaysBroghtForward)
FROM cms_user
WHERE UserID = #UserID
SELECT
#LieuAdjustedHolidays = CASE
WHEN SUM(Duration) IS NULL
THEN 0
ELSE SUM(Duration)
END
FROM
No6_LieuAdjustedHolidays
WHERE
UserID = #UserID
AND ItemCreatedWhen >= '' + CAST(DATEPART(yy, GETDATE()) AS VARCHAR(100)) + ' -11-01'
SET #Total_Holidays = SUM(#USER_HOLIDAYS + CASE WHEN #LieuAdjustedHolidays IS NULL THEN 0 ELSE #LieuAdjustedHolidays END )
SELECT
#Remain_Holidays = #Total_Holidays - CASE WHEN SUM(Duration) IS NULL THEN 0 ELSE SUM(Duration) END
FROM
No6_Holidays
WHERE
ItemCreatedby = #UserID
AND status = 'Accepted'
AND StartDate >= '' + CAST(DATEPART(yy, GETDATE()) AS VARCHAR(100)) + ' -11-01'
AND EndDate <= '' + CAST(DATEPART(yy, DATEADD(YEAR, 1, GETDATE())) AS VARCHAR(100)) + '-10-31'
UPDATE cms_user
SET RemainingHolidays = #Remain_Holidays
WHERE UserID = #UserID
SELECT
#Total_Holidays - CASE WHEN SUM(Duration) IS NULL THEN 0 ELSE SUM(Duration) END
FROM
No6_Holidays
WHERE
ItemCreatedby = #UserID
AND status = 'Accepted'
AND StartDate >= '' + CAST(DATEPART(yy, GETDATE()) AS VARCHAR(100)) + ' -11-01'
AND EndDate <= '' + CAST(DATEPART(yy, DATEADD(YEAR, 1, GETDATE())) AS VARCHAR(100)) + '-10-31'
With this part being the section that I don't believe is calculating correctly:
SELECT #Total_Holidays - CASE WHEN SUM(Duration) IS NULL THEN 0 ELSE SUM(Duration) END FROM No6_Holidays
WHERE ItemCreatedby = #UserID and status = 'Accepted' and StartDate >= '' + cast(DATEPART(yy, GETDATE()) as varchar(100)) + ' -11-01'
and EndDate <= '' + cast(DATEPART(yy, DATEADD(YEAR, 1, GETDATE())) as varchar(100)) + '-10-31'
Since I have a very limited understanding of SQL I'm struggling to understand what's going wrong here. Is anyone able to help?
Thanks in advance

Related

How Calculate Expire Date in SQL Query

I have this query for expiry date calculation and its work fine but result show in - eg:-9 year 4 month and 5 day.
I want to show that in normal way like "Expire in 9 years 4 months and 5 day":
DECLARE #TempDate Datetime ,
#ExpiryDate Datetime,
#year int,
#month int,
#day int
SET #ExpiryDate = (SELECT TOP (1) [ExpiryDate] FROM [dbo].[Purchases] WHERE [ProductId] = 1)
SELECT #TempDate = #ExpiryDate
SELECT
#year = DATEDIFF(YEAR, #TempDate, GETDATE()) -
CASE
WHEN (MONTH(#ExpiryDate) > MONTH(GETDATE())) OR
(MONTH(#ExpiryDate) = MONTH(GETDATE()) AND DAY(#ExpiryDate) > DAY(GETDATE()))
THEN 1 ELSE 0
END
SELECT #TempDate = DATEADD(YEAR, #year, #TempDate)
SELECT #month = DATEDIFF(MONTH, #TempDate, GETDATE()) -
CASE
WHEN DAY(#ExpiryDate) > DAY(GETDATE())
THEN 1 ELSE 0
END
SELECT #TempDate = DATEADD(MONTH, #month, #TempDate)
SELECT #day = DATEDIFF(DAY, #TempDate, GETDATE())
SELECT #year AS Years, #month AS Months, #day AS [Days]
If I understand your question correctly, the calculation is working as you expect but you want the Years value to be returned as a positive rather than negative number. If this is the case, you should change the final SELECT to:
SELECT (#year * -1) AS Years, #month AS Months, #day AS [Days];
Alternatively if you want to return the output as a string (i.e. Expire in 9 years 4 months and 5 day), change the final SELECT to:
SELECT 'Expire in ' + CAST((#year * -1) AS VARCHAR(2)) + ' years '
+ CAST(#month AS VARCHAR(2)) + ' months and '
+ CAST(#day AS VARCHAR(2)) + ' day';
Casting as VARCHAR(2) assumes that you expect no more than 99 years, but you may want to increase that number.

SQL Server : multi-part identifier could not be bound

I need a simple output of fields named "36" - "1" with the extended inventory value for everything in the specified item classes. I don't need itemization.
E.g.
Error is:
"Database Connector Error. '42000:[Microsoft][ODBC SQL Server Driver][SQL Server]The multi-part identifier "InvenFiscPerHistTable.FiscalPeriod" could no be bound. [Database Vendor Code: 4104 ]'
Here's my SQL.
DECLARE #CalMonth INT
DECLARE #CalYear INT
DECLARE #CalMoYear DATETIME
SET #CalMonth = CASE WHEN "InvenFiscPerHistTable"."FiscalPeriod" IN (1,2,3) THEN "InvenFiscPerHistTable"."FiscalPeriod" + 9 ELSE "InvenFiscPerHistTable"."FiscalPeriod" - 3 END
SET #CalYear = CASE WHEN "InvenFiscPerHistTable"."FiscalPeriod" IN (1,2,3) THEN "InvenFiscPerHistTable"."FiscalYear" + 1 ELSE "InvenFiscPerHistTable"."FiscalYear" END
SET #CalMoYear = CAST(CONVERT(NVARCHAR, #CalYear) + '-' + CONVERT(NVARCHAR, #CalMonth) + '-' + CONVERT(NVARCHAR,1) AS DATETIME)
--our fiscal year is not the same as the calendar year
--e.g. FiscalPeriod = 1 and FiscalYear = 2016 will be October 2015
SELECT SUM (CASE WHEN #CalMoYear BETWEEN DATEADD(m, -36, GETDATE()-day(GETDATE()-1)) AND DATEADD(m, -35, GETDATE()-day (GETDATE()-1))-1 THEN"InvenFiscPerHistTable"."QOH" * "InvenTable"."UnitCost" ELSE NULL END) AS "36"
,SUM (CASE WHEN #CalMoYear BETWEEN DATEADD(m, -35, GETDATE()-day(GETDATE()-1)) AND DATEADD(m, -34, GETDATE()-day (GETDATE()-1))-1 THEN"InvenFiscPerHistTable"."QOH" * "InvenTable"."UnitCost" ELSE NULL END) AS "35"
,SUM (CASE WHEN #CalMoYear BETWEEN DATEADD(m, -34, GETDATE()-day(GETDATE()-1)) AND DATEADD(m, -33, GETDATE()-day (GETDATE()-1))-1 THEN"InvenFiscPerHistTable"."QOH" * "InvenTable"."UnitCost" ELSE NULL END) AS "34"
--and so on until I have 36 months of history up through last month
FROM "InvenTable"
INNER JOIN "SKUTable" ON "InvenTable"."SKUKey" = "SKUTable"."SKUKey"
INNER JOIN "InvenFiscPerHistTable" ON ("InvenTable"."SKUKey" = "InvenFiscPerHistTable"."SKUKey")
AND ("InvenTable"."WarehouseKey" = "InvenFiscPerHistTable"."WarehouseKey")
INNER JOIN "SKUClassTable" "SKUClassTable" ON "SKUTable"."ICKey" = "SKUClassTable"."ICKey"
WHERE "SKUClassTable"."ItemClassName" IN (
'105-03'
,'105-04'
,'105-05'
,'105-06'
,'150-01'
)
I played around a bit with your query and think it can be rewritten and simplified (in my opinion) to the query below. The only thing you need to do is add more items in the pivot part at the end to get more columns.
The logic is that it calculates the difference in months between the first day of the current month and the date that is created from the FiscalYear and FiscalPeriod (according to your rules).
I believe it should work, unless I missed something vital, but without any test data there's of course a fair amount of guess work involved ;-)
Please give it a try and if it doesn't work I'll remove my answer.
I formatted it a bit less compact than it could be to make it easier to follow.
;WITH CTE AS
(
SELECT
SKUKey, WarehouseKey, FiscalPeriod, FiscalYear, QOH,
diff = DATEDIFF
(
month,
CASE WHEN FiscalPeriod IN (1,2,3)
THEN
CAST(FiscalYear + 1 AS CHAR(4))
+ '-' + CAST(FiscalPeriod + 9 AS VARCHAR(2))
+ '-' + CAST(1 AS CHAR(1))
ELSE
CAST(FiscalYear AS CHAR(4))
+ '-' + CAST(FiscalPeriod - 3 AS VARCHAR(2))
+ '-' + CAST(1 AS CHAR(1))
END,
DATEADD(month, DATEDIFF(month, 0, GETDATE()), 0)
)
FROM InvenFiscPerHistTable
)
SELECT * FROM
(
SELECT diff, QOH * UnitCost as TotalCost
FROM InvenTable i
INNER JOIN SKUTable s ON i.SKUKey = s.SKUKey
INNER JOIN cte ON (i.SKUKey = cte.SKUKey) AND (i.WarehouseKey = cte.WarehouseKey)
INNER JOIN SKUClassTable sc ON s.ICKey = sc.ICKey
WHERE sc.ItemClassName IN ('105-03','105-04','105-05','105-06','150-01')
) A
PIVOT ( SUM(TotalCost) FOR diff IN ([36],[35],[2],[1]) ) p -- add more columns here

Get day of the week in month (2nd Tuesday, etc.)

I need an algorithm for calculating the number of the day of the week in the month. Like 1st Friday of the month, 3rd Monday of the month, etc.)
Any ideas are appreciated.
Here is the final result:
declare #dt date = GetDate()
declare #DayOfWeek tinyint = datepart(weekday,#dt)
declare #DayOfMonth smallint = day(#dt)
declare #FirstDayOfMonth date = dateadd(month,datediff(month,0,#dt),0)
declare #DayOfWeekInMonth tinyint = #DayOfMonth / 7 + 1 -
(case when day(#FirstDayOfMonth) > day(#dt) then 1 else 0 end)
declare #Suffix varchar(2) =
case
when #DayOfWeekInMonth = 1 then 'st'
when #DayOfWeekInMonth = 2 then 'nd'
when #DayOfWeekInMonth = 3 then 'rd'
when #DayOfWeekInMonth > 3 then 'th'
end
select
cast(#DayOfWeekInMonth as varchar(2))
+ #Suffix
+ ' '
+ datename(weekday,#Dt)
+ ' of '
+ datename(month,#dt)
+ ', '
+ datename(year,#Dt)
PS: And if you can think of a better way to state the problem, please do.
Followint code will give you 1st Wednesday of April 2014 for today:
SELECT cast((DATEPART(d, GETDATE() - 1) / 7) + 1 as varchar(12))
+ 'st ' + DATENAME(WEEKDAY, getdate()) + ' of ' +
DATENAME(month, getdate()) + ' ' + DATENAME(year, getdate());
For any date use the code below. It gives 5th Tuesday of April 2014 for #mydate = '2014-04-29' in the example:
DECLARE #mydate DATETIME;
SET #mydate = '2014-04-29';
SELECT
case
when DATEPART(d, #mydate) = 1 then cast((DATEPART(d, #mydate ) / 7) + 1 as varchar(12))
else cast((DATEPART(d, #mydate - 1) / 7) + 1 as varchar(12))
end
+
case
when (DATEPART(d, #mydate - 1) / 7) + 1 = 1 then 'st '
when (DATEPART(d, #mydate - 1) / 7) + 1 = 2 then 'nd '
when (DATEPART(d, #mydate - 1) / 7) + 1 = 3 then 'rd '
else 'th '
end
+ DATENAME(WEEKDAY, #mydate) + ' of ' +
DATENAME(month, #mydate) + ' ' + DATENAME(year, #mydate) as [Long Date Name]
Okeeeey my tuuuurn ,
Please rate my answer Metaphor hhh, Here's the cooode :
declare #v_month nvarchar(2) = '04'
,#v_annee nvarchar(4) = '2014'
declare #v_date date = convert(date,#v_annee+'-'+#v_month+'-01')
declare #v_date_2 date = dateadd(M,1,#v_date)
if OBJECT_ID('temp') is not null
drop table temp
create table temp(_date date, _DayOfMonth nvarchar(20), _order int)
while (#v_date<#v_date_2)
begin
set #v_date =#v_date;
WITH _DayOfWeek AS (
SELECT 1 id, 'monday' Name UNION ALL
SELECT 2 id, 'tuesday' Name UNION ALL
SELECT 3 id, 'wednesday' Name UNION ALL
SELECT 4 id, 'thursday' Name UNION ALL
SELECT 5 id, 'friday' Name UNION ALL
SELECT 6 id, 'saturday' Name UNION ALL
SELECT 7 id, 'sunday' Name)
insert into temp(_date,_DayOfMonth)
SELECT
#v_date
,(select Name from _DayOfWeek where id = DATEPART(WEEKDAY,#v_date))
SET #v_date = DATEADD(DAY,1,#v_date)
END
UPDATE tmp1
SET _order = _order_2
FROM temp tmp1
INNER JOIN
(SELECT *, ROW_NUMBER() OVER(PARTITION BY _DayOfMonth ORDER BY _date ASC) AS _order_2 FROM temp) tmp2
ON tmp1._date = tmp2._date
SELECT * FROM temp
SELECT *
FROM temp
WHERE _DayOfMonth = 'thursday'
AND _order = 3
I hope this will help you :)
Good Luck
OK, here's what I came up with, I'll +1 everyone who answered anyway:
declare #dt date = GetDate()
declare #DayOfWeek tinyint = datepart(weekday,#dt)
declare #DayOfMonth smallint = day(#dt)
declare #FirstDayOfMonth date = dateadd(month,datediff(month,0,#dt),0)
declare #DayOfWeekInMonth tinyint =
#DayOfMonth / 7 + 1
- (case when day(#FirstDayOfMonth) > day(#dt) then 1 else 0 end)
declare #Suffix varchar(2) =
case
when #DayOfWeekInMonth = 1 then 'st'
when #DayOfWeekInMonth = 2 then 'nd'
when #DayOfWeekInMonth = 3 then 'rd'
when #DayOfWeekInMonth > 3 then 'th'
end
select
cast(#DayOfWeekInMonth as varchar(2))
+ #Suffix
+ ' '
+ datename(weekday,#Dt)
+ ' of '
+ datename(month,#dt)
+ ', '
+ datename(year,#Dt)
declare #dt date = getdate()
declare #DayOfMonth smallint = datepart(d, #dt)
declare #Suffix varchar(2) =
case
when floor((#DayOfMonth - 1) / 7.0) = 0 then 'st' -- implies there were no such days previously in the month
when floor((#DayOfMonth - 1) / 7.0) = 1 then 'nd'
when floor((#DayOfMonth - 1) / 7.0) = 2 then 'rd'
else 'th'
end
select cast(floor((#DayOfMonth - 1) / 7.0) + 1 as varchar(1)) + #Suffix +
' ' + datename(weekday, #dt) + ' of ' + datename(month, #dt) +
', ' + datename(year, #dt)
DECLARE #dt DATETIME
SET #dt = DATEADD(d, 6, GETDATE())
SELECT #dt,
CAST((DAY(#dt) / 7) + CASE WHEN DATEPART(weekday, #dt) >= DATEPART(weekday, CAST(MONTH(#dt) AS NVARCHAR) + '/01/' + CAST(YEAR(#dt) AS NVARCHAR)) THEN 1 ELSE 0 END AS NVARCHAR)
+ '' + CASE (DAY(#dt) / 7) + CASE WHEN DATEPART(weekday, #dt) >= DATEPART(weekday, CAST(MONTH(#dt) AS NVARCHAR) + '/01/' + CAST(YEAR(#dt) AS NVARCHAR)) THEN 1 ELSE 0 END
WHEN 1 THEN N'st'
WHEN 2 THEN N'nd'
WHEN 3 THEN N'rd'
ELSE N'th'
END
+ ' ' + DATENAME(dw, #dt)
+ ' of ' + DATENAME(M, #dt)
+ ', ' + CAST(YEAR(#dt) AS NVARCHAR)
Result is a single SELECT (provided the assignment of #dt happened earlier) but is, essentially, the same logic as yours.
This following code will give you DATE for any day of the week in any month or year that you specify. All the variables that I have are to reduce repeating logic to improve code speed.
This code gives you date for 1st Monday in February in 2013
DECLARE #DayNumber INT = 1
,#DayWeekNumber INT = 2
,#MonthNumber INT = 2
,#YearNumber INT = 2013
,#FoM DATE
,#FoMWD INT;
SET #FoM = DATEFROMPARTS(#YearNumber,#MonthNumber,1)
SET #fomwd = DATEPART(WEEKDAY, #FoM);
SELECT CASE WHEN #fomwd = #DayWeekNumber THEN DATEADD(WEEK, #DayNumber - 1, #FoM)
WHEN #fomwd < #DayWeekNumber THEN DATEADD(DAY, #DayWeekNumber - #fomwd, DATEADD(WEEK, #DayNumber - 1, #FoM))
WHEN #fomwd > #DayWeekNumber THEN DATEADD(DAY, #DayWeekNumber - #fomwd, DATEADD(WEEK, #DayNumber, #FoM))
END AS DateOfDay;

TSQL - converting date string to datetime

I have a table that has the following column (with some sample entries provided):
COLUMN NAME: pt_age_old
20 Years 8 Months 3 Weeks
1 Year 3 Months 2 Weeks
58 Years 7 Months
1 Year
7 Years 11 Months 2 Weeks
26 Years 6 Months
56 Years 6 Months
48 Years 6 Months 4 Weeks
29 Years 11 Months
4 Years 3 Months
61 Years 8 Months 4 Weeks
I have tried to cast it to datetime, of course this did not work. Same with convert.
Keep getting the following message:
Msg 241, Level 16, State 1, Line 2
Conversion failed when converting date and/or time from character string.
Main 2 questions are:
Is this type of conversion even possible with this existing string format?
If so, can you steer me in the right direction so I can make this happen?
Thanks!
This could be done using custom code such as below - I've assumed the values you're using are people's ages, and you're trying to work out their date of birth given their age today.
You can see the below code in action here: http://sqlfiddle.com/#!3/c757c/2
create function dbo.AgeToDOB(#age nvarchar(32))
returns datetime
as
begin
declare #pointer int = 0
, #pointerPrev int = 1
, #y nvarchar(6)
, #m nvarchar(6)
, #w nvarchar(6)
, #d nvarchar(6)
, #result datetime = getutcdate() --set this to the date we're working to/from
--convert various ways of expressing units to a standard
set #age = REPLACE(#age,'Years','Y')
set #age = REPLACE(#age,'Year','Y')
set #age = REPLACE(#age,'Months','M')
set #age = REPLACE(#age,'Month','M')
set #age = REPLACE(#age,'Weeks','W')
set #age = REPLACE(#age,'Week','W')
set #age = REPLACE(#age,'Days','D')
set #age = REPLACE(#age,'Day','D')
set #pointer = isnull(nullif(CHARINDEX('Y',#age),0),#pointer)
set #y = case when #pointer > #pointerprev then SUBSTRING(#age,#pointerprev,#pointer - #pointerprev) else null end
set #pointerPrev = #pointer + 1
set #pointer = isnull(nullif(CHARINDEX('M',#age),0),#pointer)
set #m = case when #pointer > #pointerprev then SUBSTRING(#age,#pointerprev,#pointer - #pointerprev) else null end
set #pointerPrev = #pointer + 1
set #pointer = isnull(nullif(CHARINDEX('W',#age),0),#pointer)
set #w = case when #pointer > #pointerprev then SUBSTRING(#age,#pointerprev,#pointer - #pointerprev) else null end
set #pointerPrev = #pointer + 1
set #pointer = isnull(nullif(CHARINDEX('D',#age),0),#pointer)
set #d = case when #pointer > #pointerprev then SUBSTRING(#age,#pointerprev,#pointer - #pointerprev) else null end
set #result = dateadd(YEAR, 0 - isnull(cast(#y as int),0), #result)
set #result = dateadd(MONTH, 0 - isnull(cast(#m as int),0), #result)
set #result = dateadd(Week, 0 - isnull(cast(#w as int),0), #result)
set #result = dateadd(Day, 0 - isnull(cast(#d as int),0), #result)
return #result
end
go
select dbo.AgeToDOB( '20 Years 8 Months 3 Weeks')
NB: there's a lot of scope for optimisation here; I've left it simple above to help keep it clear what's going on.
Technically it is possible to convert the relative time label into a datetime, but you will need a reference date though (20 Years 8 Months 3 Weeks as of '2013-10-16'). The reference date is most likely today (using GETDATE() or CURRENT_TIMESTAMP), but there is a chance it is a different date. You will have to parse the label, convert it to a duration, and then apply the duration to the reference time. This will be inherently slow.
There are at least two possible ways of doing this, write a FUNCTION to parse and convert the relative time label or use a .NET extended function to do the conversion. You would need to identify all of the possible labels otherwise the conversion will fail. Keep in mind that the reference date is important since 2 months is not a constant number of days (Jan-Feb = either 58 or 59 days).
Here is a sample of what the function might look like:
-- Test data
DECLARE #test varchar(50)
, #ref_date datetime
SET #test = '20 Years 8 Months 3 Weeks'
SET #ref_date = '2013-10-16' -- or use GETDATE() / CURRENT_TIMESTAMP
-- Logic in function
DECLARE #pos int
, #ii int
, #val int
, #label varchar(50)
, #result datetime
SET #pos = 0
SET #result = #ref_date
WHILE (#pos <= LEN(#test))
BEGIN
-- Parse the value first
SET #ii = NULLIF(CHARINDEX(' ', #test, #pos), 0)
SET #label = RTRIM(LTRIM(SUBSTRING(#test, #pos, #ii - #pos)))
SET #val = CAST(#label AS int)
SET #pos = #ii + 1
-- Parse the label next
SET #ii = NULLIF(CHARINDEX(' ', #test, #pos), 0)
IF (#ii IS NULL) SET #ii = LEN(#test) + 1
SET #label = RTRIM(LTRIM(SUBSTRING(#test, #pos, #ii - #pos)))
SET #pos = #ii + 1
-- Apply the value and offset
IF (#label = 'Days') OR (#label = 'Day')
SET #result = DATEADD(dd, -#val, #result)
ELSE IF (#label = 'Weeks') OR (#label = 'Week')
SET #result = DATEADD(ww, -#val, #result)
ELSE IF (#label = 'Months') OR (#label = 'Month')
SET #result = DATEADD(mm, -#val, #result)
ELSE IF (#label = 'Years') OR (#label = 'Year')
SET #result = DATEADD(yy, -#val, #result)
END
SELECT #result
-- So if this works correctly,
-- 20 Years 8 Months 3 Weeks from 2013-10-16 == 1993-01-26
Make a function to split it up:
create function f_tst
(
#t varchar(200)
) returns date
begin
declare #a date = current_timestamp
;with split1 as
(
select 1 start, charindex(' ', #t + ' ', 4)+1 stop
where #t like '% %'
union all
select stop, charindex(' ', #t + ' ', stop + 4)+1
from split1
where charindex(' ', #t, stop) > 0
), split2 as
(
select cast(left(x.sub, charindex(' ', x.sub)) as int) number,
substring(x.sub, charindex(' ', x.sub) + 1, 1) unit
from split1
cross apply (select substring(#t, start, stop - start) sub) x
)
select #a = case unit when 'W' then dateadd(ww, -number, #a)
when 'Y' then dateadd(yy, -number, #a)
when 'M' then dateadd(mm, -number, #a)
end
from split2
return #a
end
Test function:
select dbo.f_tst(age)
from (values('20 Years 8 Months 3 Weeks'),('1 Month') ) x(age)
Result:
1993-01-27
2013-09-17
No, date time type presents actual date, like YYYY-MM-DD HH:MM, your strings are not DATE fields, they are age, to have date time you need date of birth and them somehow add this age to it

simulating Excel networkdays in sql

I am sure somebody has worked out to simulate Excel networkdays function in other programming language.
I'd appreciate if you could share.
Thanks
The idea is to calculate the weekdays between the start of each date's week then applying various offsets.
Find the number of days between the Saturday before each date
Divide by 7 and multiply by 5 to get the number of weekdays
Offset the total for whether the start date is after the end date
Offset again for whether the start is after the end and the start is a Saturday
Again for whether the start is after and the end is Sunday
Again for whether the start is not after and the start is a Sunday
Again for whether the start is not after and the end is a Saturday
Add some random dates into a table.
declare #t table ([start] datetime, [end] datetime)
insert into #t values ('2088-01-14 11:56:23','2011-11-10 03:34:09')
insert into #t values ('2024-09-24 10:14:29','2087-09-16 15:52:06')
Then calcuate NETWORKDAYS for those dates.
select [start],[end]
,((datediff(day,0,[end])-datepart(dw,[end]))-(datediff(day,0,[start])-datepart(dw,[start])))/7*5 --[weekdays]
+ datepart(dw,[end]) - datepart(dw,[start]) --[weekday diff]
+ case when datediff(day,0,[start]) > datediff(day,0,[end]) then -1 else 1 end --[start after]
+ case when datediff(day,0,[start]) > datediff(day,0,[end]) and datepart(dw,[start]) = 7 then 1 else 0 end --[start after and start saturday]
+ case when datediff(day,0,[start]) > datediff(day,0,[end]) and datepart(dw,[end]) = 1 then 1 else 0 end --[start after and end sunday]
+ case when datediff(day,0,[start]) <= datediff(day,0,[end]) and datepart(dw,[start]) = 1 then -1 else 0 end --[start not after and start sunday]
+ case when datediff(day,0,[start]) <= datediff(day,0,[end]) and datepart(dw,[end]) = 7 then -1 else 0 end --[start not after and end saturday]
as [networkdays]
from #t
A SQL solution as per SO question "Equivalent of Excel’s NETWORKDAYS function with Jet ADO"
Hope this helps someone out there but this works for me. Requires you to create a dbo.FederalHolidays table (which can be easily populated with online date sources).
Cleanest way I could come up with that is easily scalable.
--Simulate Excel Formula =NETWORKDAYS() excludes weekend days and federal holidays. Requires a Federal Holiday Table, easy to create.
DECLARE #d1 date, #d2 date
SET #d1 = '4/12/2019'
SET #d2 = '4/23/2019'
SELECT
DATEDIFF(dd,#d1,#d2) +1 --First calculate regular date difference +1 to count the start date; does not exclude weekends/holidays.
- (SELECT COUNT(*) --Calculate number of holidays between the date range and subtract it from DATEDIFF().
FROM [dbo].[FederalHolidays]
WHERE DATE BETWEEN #d1 AND #d2)
- (SELECT (DATEDIFF(wk, #d1, #d2) * 2) --Calculate number of weekend days between the date range and subtract it from DATEDIFF().
+(CASE WHEN DATENAME(dw, #d1) = 'Sunday' THEN 1 ELSE 0 END)
+(CASE WHEN DATENAME(dw, #d2) = 'Saturday' THEN 1 ELSE 0 END)) as NetWorkDays
For what it's worth, I created the following function for mysql that assumes Mon-Fri are business days:
DROP FUNCTION IF EXISTS BusinessDays;
DELIMITER //
CREATE FUNCTION BusinessDays (startDate DATE, endDate DATE)
RETURNS INT
DETERMINISTIC
BEGIN
DECLARE startWeekDay INT;
DECLARE allDays INT;
DECLARE fullWeekCount INT;
DECLARE remainderDays INT;
DECLARE maxPossibleRemainderWeekendDays INT;
DECLARE soloSundays INT;
DECLARE totalBusinessDays INT;
SET startWeekDay = WEEKDAY(startDate);
SET allDays = ABS(DATEDIFF(endDate, startDate)) + 1;
SET fullWeekCount = FLOOR(allDays/7);
SET remainderDays = allDays - (fullWeekCount * 7);
SET maxPossibleRemainderWeekendDays = ROUND(2*(startWeekDay+remainderDays-6)/(ABS(2*(startWeekDay+remainderDays-6))+1))+1;
SET soloSundays = ROUND(2*(startWeekDay-6)/(ABS(2*(startWeekDay-6))+1))+1;
SET totalBusinessDays = allDays - (fullWeekCount * 2) - maxPossibleRemainderWeekendDays + soloSundays;
RETURN totalBusinessDays;
END //
DELIMITER ;
Perhaps this will also help, I created a formula (in Excel) that will simulate the NETWORKDAYS function:
= 1 + ( ( B1 - A1) * 5 - ( WEEKDAY(A1) - WEEKDAY(B1) ) * 2 ) / 7 + IF(A1<=B1,IF(WEEKDAY(B1)=7,-1,0) + IF(WEEKDAY(A1)=1,-1,0), IF(WEEKDAY(B1)<>1,-1,0) + IF(WEEKDAY(A1)<>7,-1,0) )
NOTE: WEEKDAY() has Sunday as 0 to Saturday as 6
I have been looking for this capability for quite some time, so went ahead and just created it on my own.
Usage: This is a function you can create in SQL. You can easily modify this to work outside a function if needed, but I prefer functions to take care of these types of calculations.
I added quite a few comments in case anyone was wondering how it worked. What this does is take two inputs:
#startDate - the beginning date you want to add workday to.
#addDays - the whole number of days you want to add to the #startDate.
Calculation process:
It loops repeatedly until the # of working days has been reached. For example, if you enter 365 days, it will cycle around 500 times before it's able to predict the # of working days to add. I've added #weekendCount in case anyone needs to know the # of weekend days excluded before reaching the final end date.
Once completed, the integer #recCounter essentially is the # of days that must be added to the #startDate before the number of working days is reached. I am sure someone can write this better than I, or perhaps someone can make use of this. Hope this helps! :)
CREATE FUNCTION [dbo].[addNetworkDays](#startDate AS DATETIME, #addDays AS Int)
RETURNS DATETIME
AS
BEGIN
DECLARE #recCounter Int
SET #recCounter = 0
DECLARE #weekendCount Int
SET #weekendCount = 0
DECLARE #workdayCount Int
SET #workdayCount = 0
DECLARE #newDate DateTime
WHILE #addDays > #workdayCount
BEGIN
-- Add another day to the start date
SET #recCounter = #recCounter + 1
-- Cumuluate the weekend vs. workday counter, based on the day of the week. This loop will repeat until #workdayCount has reached the #addDays.
-- Note that #weekendCount is not used in any capacity, can be used if you need to know how many weekend days there are.
IF DATEPART(dw, DATEADD(d, #recCounter, #startDate)) IN (1, 7) SET #weekendCount = #weekendCount + 1
IF DATEPART(dw, DATEADD(d, #recCounter, #startDate)) IN (2, 3, 4, 5, 6) SET #workdayCount = #workdayCount + 1
END
-- At this point, the script has completed the cycle, stopping when the detected # of workdays has reached the count of days the user specified.
-- Calculate the new date, based on the # of cycles *days* detected above.
SET #newDate = DATEADD(d, #recCounter, #startDate)
-- If the new/adjusted date falls on a Saturday or Sunday, add additional days to compensate.
IF DATEPART(dw, #newDate) = 1 SET #newDate = DATEADD(d, 1, #newDate)
IF DATEPART(dw, #newDate) = 7 SET #newDate = DATEADD(d, 1, #newDate)
RETURN CAST(#newDate AS DATETIME)
NETWORKDAYS for Australia
WITH PH_NET_WORKDAY AS
(
----BUILD UNDERLYING WORKDAY DATA TABLE
SELECT
DT,
STATE,
CASE
WHEN WORKDAY-PUBLIC_HOLIDAY <0
THEN 0
ELSE WORKDAY-PUBLIC_HOLIDAY
END AS WORKDAY
FROM
(
SELECT
DT,
STATE,
WORKDAY,
----PUBLIC HOLIDAY INFORMATION HERE
CASE
WHEN STATE = 'NSW'
THEN
CASE
WHEN DT IN (
'01-Oct-2018',
'25-Dec-2018',
'26-Dec-2018',
'01-Jan-2019',
'28-Jan-2019',
'19-Apr-2019',
'20-Apr-2019',
'21-Apr-2019',
'22-Apr-2019',
'25-Apr-2019',
'10-Jun-2019',
'07-Oct-2019',
'25-Dec-2019',
'26-Dec-2019'
)
THEN 1
ELSE 0
END
WHEN STATE = 'SA'
THEN
CASE
WHEN DT IN (
'01-Oct-2018',
'24-Dec-2018',
'25-Dec-2018',
'26-Dec-2018',
'31-Dec-2018',
'01-Jan-2019',
'28-Jan-2019',
'11-Mar-2019',
'19-Apr-2019',
'20-Apr-2019',
'22-Apr-2019',
'25-Apr-2019',
'10-Jun-2019',
'07-Oct-2019',
'24-Dec-2019',
'25-Dec-2019',
'26-Dec-2019'
)
THEN 1
ELSE 0
END
WHEN STATE = 'QLD'
THEN
CASE
WHEN DT IN (
'01-Oct-2018',
'25-Dec-2018',
'26-Dec-2018',
'01-Jan-2019',
'28-Jan-2019',
'19-Apr-2019',
'20-Apr-2019',
'21-Apr-2019',
'22-Apr-2019',
'25-Apr-2019',
'06-May-2019',
'07-Oct-2019',
'25-Dec-2019',
'26-Dec-2019'
)
THEN 1
ELSE 0
END
WHEN STATE = 'VIC'
THEN
CASE
WHEN DT IN (
'28-Sep-2018',
'06-Nov-2018',
'25-Dec-2018',
'26-Dec-2018',
'01-Jan-2019',
'28-Jan-2019',
'11-Mar-2019',
'19-Apr-2019',
'20-Apr-2019',
'21-Apr-2019',
'22-Apr-2019',
'25-Apr-2019',
'10-Jun-2019',
'05-Nov-2019',
'25-Dec-2019',
'26-Dec-2019'
)
THEN 1
ELSE 0
END
WHEN STATE = 'TAS'
THEN
CASE
WHEN DT IN (
'25-Dec-2018',
'26-Dec-2018',
'01-Jan-2019',
'28-Jan-2019',
'11-Mar-2019',
'19-Apr-2019',
'22-Apr-2019',
'23-Apr-2019',
'25-Apr-2019',
'10-Jun-2019',
'25-Dec-2019',
'26-Dec-2019'
)
THEN 1
ELSE 0
END
ELSE 0
END AS PUBLIC_HOLIDAY
FROM
(
SELECT
DT.*,
ST.*
FROM
(
SELECT
TRUNC (TO_DATE('01-JAN-2021','DD-MON-YYYY') - ROWNUM) AS DT,
TRIM(TO_CHAR( TRUNC (TO_DATE('01-JAN-2021','DD-MON-YYYY') - ROWNUM) , 'DAY')) AS WEEK_DAY,
CASE
WHEN TRIM(TO_CHAR( TRUNC (TO_DATE('01-JAN-2021','DD-MON-YYYY') - ROWNUM) , 'DAY')) = 'SATURDAY'
THEN 0
WHEN TRIM(TO_CHAR( TRUNC (TO_DATE('01-JAN-2021','DD-MON-YYYY') - ROWNUM) , 'DAY')) = 'SUNDAY'
THEN 0
----BUILD STATE PUBLIC HOLIDAY DATE SET HERE, WE CAN EXCLUDE PUBLIC HOLIDAYS
ELSE 1
END AS WORKDAY
FROM DUAL CONNECT BY ROWNUM < (365.25*8+1)
) DT
,
(
SELECT 'NSW' AS STATE FROM DUAL
UNION
SELECT 'QLD' AS STATE FROM DUAL
UNION
SELECT 'SA' AS STATE FROM DUAL
UNION
SELECT 'ACT' AS STATE FROM DUAL
UNION
SELECT 'VIC' AS STATE FROM DUAL
UNION
SELECT 'ACT' AS STATE FROM DUAL
) ST
)
)
),
----A SAMPLE DATA SET FOR SAME DATES BETWEEN DIFFERENT STATE TO TEST PUBLIC HOLIDAY DIFFERENCES
SAMPLE_DATA AS
(
SELECT
'01-FEB-2019' AS D1,
'11-FEB-2019' AS D2,
'NSW' AS STATE
FROM DUAL
UNION
SELECT
'01-FEB-2019' AS D1,
'11-FEB-2019' AS D2,
'SA' AS STATE
FROM DUAL
UNION
SELECT
'19-APR-2019' AS D1,
'26-APR-2019' AS D2,
'NSW' AS STATE
FROM DUAL
)
----SELECT WORKDAYS FROM PH TABLE AND INSERT INTO SAPLE TABLE
SELECT
SAMPLE_DATA.*,
(
SELECT SUM(WORKDAY)
FROM PH_NET_WORKDAY
WHERE
STATE = SAMPLE_DATA.STATE
AND DT>=SAMPLE_DATA.D1
AND DT<=SAMPLE_DATA.D2
)
AS WORKDAYS
FROM SAMPLE_DATA