SQL query to find available future dates except weekends - sql

I have table called "detail" where i am storing start date and end date of jobs.I have one more table called "leaves" which is also have leave startdate and leave enddate fields.I need to find the nearest available dates of a user without weekends and leave dates.
DECLARE #PackagerLastAssignedDate DATETIME
SELECT #PackagerLastAssignedDate = MAX(EndDate) FROM detail WHERE userId = 1
SELECT lveStartDate,lveEndDate FROM Leaves WHERE UserId = 1 and lveStartDate > #PackagerLastAssignedDate
Thanks In advance
Berlin.M

Try this one -
DECLARE
#DateFrom DATETIME
, #DateTo DATETIME
SELECT
#DateFrom = '20130101'
, #DateTo = '20130202'
SELECT [Date]
FROM (
SELECT [Date] = DATEADD(DAY, sv.number, t.DateFrom)
FROM (
SELECT
DateFrom = #DateFrom
, diff = DATEDIFF(DAY, #DateFrom, #DateTo)
) t
JOIN [master].dbo.spt_values sv ON sv.number <= diff
WHERE sv.[type] = 'p'
) t2
WHERE DATENAME(WEEKDAY, [Date]) NOT IN ('Saturday', 'Sunday')
AND NOT EXISTS (
SELECT 1
FROM dbo.Leaves l
WHERE l.UserId = 1
AND t2.[Date] BETWEEN l.lveStartDate AND l.lveEndDate
)

Related

my end goal is to see end of month data for previous month

My end goal is to see end of month data for previous month.
Our processing is a day behind so if today is 7/28/2021 our Process date is 7/27/2021
So, I want my data to be grouped.
DECLARE
#ProcessDate INT
SET #ProcessDate = (SELECT [PrevMonthEnddatekey] FROM dbo.dimdate WHERE datekey = (SELECT [datekey] FROM sometable [vwProcessDate]))
SELECT
ProcessDate
, LoanOrigRiskGrade
,SUM(LoanOriginalBalance) AS LoanOrigBalance
,Count(LoanID) as CountofLoanID
FROM SomeTable
WHERE
ProcessDate in (20210131, 20210228,20210331, 20210430, 20210531, 20210630)
I do not want to hard code these dates into my WHERE statement. I have attached a sample of my results.
I am GROUPING BY ProcessDate, LoanOrigRiskGrade
Then ORDERING BY ProcessDate, LoanOrigIRskGrade
It looks like you want the last day of the month for months within a specified range. You can parameterize that.
For SQL Server:
DECLARE #ProcessDate INT
SET #ProcessDate = (
SELECT [PrevMonthEnddatekey]
FROM dbo.dimdate
WHERE datekey = (
SELECT [datekey]
FROM sometable [vwProcessDate]
)
)
DECLARE #startDate DATE
DECLARE #endDate DATE
SET #startDate = '2021-01-01'
SET #endDate = '2021-06-30'
;
with d (dt, eom) as (
select #startDate
, convert(int, replace(convert(varchar(10), eomonth(#startDate), 102), '.', ''))
union all
select dateadd(month, 1, dt)
, eomonth(dateadd(month, 1, dt))
from d
where dateadd(month, 1, dt) < #endDate
)
SELECT ProcessDate
, LoanOrigRiskGrade
, SUM(LoanOriginalBalance) AS LoanOrigBalance
, Count(LoanID) as CountofLoanID
FROM SomeTable
inner join d on d.eom = SomeTable.ProcessDate
Difficult to check without sample data.

Find number of days that intersect a given date range in a table of date ranges

I want to find the total number of days in a date range that overlap a table of date ranges.
For example, I have 7 days between 2 dates in the table below. I want to find the days between between them that also fall this date range: 2019-08-01 to 2019-08-30.
It should return 1 day.
This is the data source query:
SELECT LeaveId, UserId, StartDate, EndDate, Days
FROM TblLeaveRequest
WHERE UserId = 218
LeaveID UserID StartDate EndDate Days
----------- ----------- ----------------------- ----------------------- -----------
22484 218 2019-07-26 00:00:00.000 2019-08-01 00:00:00.000 7
I believe this might help you:
--create the table
SELECT
22484 LeaveID,
218 UserID,
CONVERT(DATETIME,'7/26/2019') StartDate,
CONVERT(DATETIME,'8/1/2019') EndDate,
7 Days
INTO #TblLeaveRequest
--Range Paramters
DECLARE #StartRange AS DATETIME = '8/1/2019'
DECLARE #EndRange AS DATETIME = '8/30/2019'
--Find sum of days between StartDate and EndDate that intersect the range paramters
--for UserId=218
SELECT
SUM(
DATEDIFF(
DAY
,CASE WHEN #StartRange < StartDate THEN StartDate ELSE #StartRange END
,DATEADD(DAY, 1, CASE WHEN #EndRange > EndDate THEN EndDate ELSE #EndRange END)
)
) TotalDays
from #TblLeaveRequest
where UserId=218
This assumes that no start dates are greater than end dates. It also assumes that the range parameters always intersect some portion of the range in the table.
If the parameter ranges might not intersect then you'll have to eliminate those cases by excluding negative days:
SELECT SUM( CASE WHEN Days < 0 THEN 0 ELSE Days END ) TotalDays
FROM
(
SELECT
DATEDIFF(
DAY
,CASE WHEN #StartRange < StartDate THEN StartDate ELSE #StartRange END
,DATEADD(DAY, 1, CASE WHEN #EndRange > EndDate THEN EndDate ELSE #EndRange END)
) Days
from #TblLeaveRequest
where UserId=218
) TotalDays
if I understand your problem correctly, you have two ranges of dates and you are looking for the number of days in the intersection.
Considering a Gant chart:
Start_1 .................... End_1
Start_2 .......................End_2
If you can create a table structure like
LeaveID UserID StartDate_1 EndDate_1 StartDate_2 EndDate_2
----------- ----------- ---------- --------- ---------- ---------
22484 218 2019-07-26 2019-08-01 2019-08-01 2019-08-30
You can determine the number of days by
select
leaveID
, UserID
,case
when StartDate_2 <= EndDate_1 then datediff(day,StartDate_2,EndDate_1) + 1
else 0
end as delta_days_intersection
from table
I hope that helps
You can use a numbers (Tally) table to count the number of days:
SQL Fiddle
MS SQL Server 2017 Schema Setup:
CREATE TABLE LeaveRequest
(
LeaveId Int,
UserId Int,
StartDate Date,
EndDate Date,
Days Int
)
Insert Into LeaveRequest
VALUES
(22484, 218, '2019-07-26','2019-08-01', 7)
Query 1:
DECLARE #StartDate Date = '2019-08-01'
DECLARE #EndDate Date = '2019-08-30'
;WITH Tally
AS
(
SELECT ROW_NUMBER() OVER (ORdER By Numbers.Num) AS Num
FROM
(
Values(1),(2),(3),(4),(5),(6),(7),(8),(9)
)Numbers(Num)
Cross APPLY
(
Values(1),(2),(3),(4),(5),(6),(7),(8),(9)
)Numbers2(Num2)
)
SELECT COUNT(DATEADD(d, Num -1, StartDate)) As NumberOfDays
FROM LeaveRequest
CROSS APPLY Tally
WHERE DATEADD(d, Num -1, StartDate) <= EndDate AND
DATEADD(d, Num -1, StartDate) >= #StartDate AND
DATEADD(d, Num -1, StartDate) <= #EndDate
Results:
| NumberOfDays |
|--------------|
| 1 |
CREATE TABLE #LeaveRequest
(
LeaveId Int,
UserId Int,
StartDate Date,
EndDate Date,
Days Int
)
Insert Into #LeaveRequest
VALUES
(22484, 218, '2019-07-26','2019-08-01', 7)
Declare #FromDate datetime='2019-08-01'
declare #ToDate datetime='2019-08-30'
;With CTE as
(
select top (DATEDIFF(day,#FromDate,#ToDate)+1)
DATEADD(day, ROW_NUMBER()over(order by (select null))-1,#FromDate) DT
from sys.objects
)
select * from #LeaveRequest LR
cross apply(select count(*)IntersectingDays
from CTE c where dt between lr.StartDate and lr.EndDate)ca
--or
--select lr.*,c.*
from CTE c
--cross apply
(select lr.* from #LeaveRequest LR
where c.DT between lr.StartDate and lr.EndDate)lr
drop table #LeaveRequest
Better create one Calendar table which will always help you in other queries also.
create table CalendarDate(Dates DateTime primary key)
insert into CalendarDate WITH (TABLOCK) (Dates)
select top (1000000)
DATEADD(day, ROW_NUMBER()over(order by (select null))-1,'1970-01-01') DT
from sys.objects
Then inside CTE write this,
select top (DATEDIFF(day,#FromDate,#ToDate)+1) Dates from CalendarDate
where Dates between #FromDate and #ToDate
DECLARE #FromDate datetime = '2019-08-01'
DECLARE #ToDate datetime = '2019-08-30'
SELECT
IIF(#FromDate <= EndDate AND #ToDate >= StartDate,
DATEDIFF(day,
IIF(StartDate > #FromDate, StartDate, #FromDate),
IIF(EndDate < #ToDate, EndDate, #ToDate)
),
0) AS overlapping_days
FROM TblLeaveRequest;

Converting SQL Server UDF to inline table-valued function

I am new here and new to SQL. I got this tip to create a scalar function that extends the functionality of the built-in DateAdd function (namely to exclude weekends and holidays). It is working fine for a single date but when I use it on a table, it is extremely slow.
I have seen some recommendation to use inline table-valued function instead. Would anyone be so kind to point me in the direction, how I would go about converting the below to inline table-valued function? I greatly appreciate it.
ALTER FUNCTION [dbo].[CalcWorkDaysAddDays]
(#StartDate AS DATETIME, #Days AS INT)
RETURNS DATE
AS
BEGIN
DECLARE #Count INT = 0
DECLARE #WorkDay INT = 0
DECLARE #Date DATE = #StartDate
WHILE #WorkDay < #Days
BEGIN
SET #Count = #Count - 1
SET #Date = DATEADD(DAY, #Count, #StartDate)
IF NOT (DATEPART(WEEKDAY, #Date) IN (1,7) OR
EXISTS (SELECT * FROM RRCP_Calendar WHERE Is_Holiday = 1 AND Calendar_Date = #Date))
BEGIN
SET #WorkDay = #WorkDay + 1
END
END
RETURN #Date
END
This should do the trick...
CREATE FUNCTION dbo.tfn_CalcWorkDaysAddDays
(
#StartDate DATETIME,
#Days INT
)
RETURNS TABLE WITH SCHEMABINDING AS
RETURN
SELECT
TheDate = MIN(x.Calendar_Date)
FROM (
SELECT TOP (#Days)
c.Calendar_Date
FROM
dbo.RRCP_Calendar c
WHERE
c.Calendar_Date < #StartDate
AND c.Is_Holiday = 0
AND c.is_Weekday = 1 -- this should be part of your calendar table. do not calculate on the fly.
ORDER BY
c.Calendar_Date DESC
) x;
GO
Note: for best performance, you'll want a unique, filtered, nonclustered index on on your calendar table...
CREATE UNIQUE NONCLUSTERED INDEX uix_RRCPCalendar_CalendarDate_IsHoliday_isWeekday ON dbo.RRCP_Calendar (
Calendar_Date, Is_Holiday, is_Weekday)
WHERE Is_Holiday = 0 AND is_Weekday = 1;
Try this and see if it returns the same values as your function, just without the loop:
SELECT WorkDays =
DATEADD(WEEKDAY, #Days, #StartDate) -
(SELECT COUNT(*)
FROM RRCP_Calendar
WHERE Is_Holiday = 1
AND Calendar_Date >= #StartDate
AND Calendar_Date <= DATEADD(DAY, #Days, #StartDate)
)
And yes, you can sometimes get substantially better performance with a non-procedural table-valued-function, but you have to set it up right. Look up SARGability and non-procedural table-valued-functions for more info, but if the above query works, this should do the trick:
CREATE FUNCTION dbo.SelectWorkDaysAddDays(#StartDate DATE, #Days INT)
RETURNS TABLE
AS
RETURN
SELECT WorkDays =
DATEADD(WEEKDAY, #Days, #StartDate) -
(SELECT COUNT(*)
FROM RRCP_Calendar
WHERE Is_Holiday = 1
AND Calendar_Date >= #StartDate
AND Calendar_Date <= DATEADD(DAY, #Days, #StartDate)
)
GO
And then you call the function by using an OUTER APPLY join:
SELECT y.foo
, y.bar
, dt.WorkDays
FROM dbo.YourTable y
OUTER APPLY dbo.SelectWorkDaysAddDays(#StartDate, #Days) dt
Say [dbo].[CalcWorkDaysAddDays], getdate(), 2 would return Sept 8,
2017 since it is adding two days. This function is similar to DateAdd
but it is excluding weekends and holidays
The code you've posted doesn't do this.
But if you want the result described, the function can be smth like this:
alter FUNCTION [dbo].[CalcWorkDaysAddDays_inline](#StartDate As DateTime,#Days AS INT)
returns table
as return
with cte as
(
select *,
ROW_NUMBER() over(order by Calendar_Date) as rn
from RRCP_Calendar
where Calendar_Date > #StartDate and #Days > 0
and not (DATEPART(WEEKDAY,Calendar_Date) IN (1,7) or Is_Holiday = 1)
union ALL
select *,
ROW_NUMBER() over(order by Calendar_Date desc) as rn
from RRCP_Calendar
where Calendar_Date < #StartDate and #Days < 0
and not (DATEPART(WEEKDAY,Calendar_Date) IN (1,7) or Is_Holiday = 1)
)
select cast(Calendar_Date as date) as dt
from cte
where rn = abs(#Days);

Add one month until it is greater than some value

I have start date & Mdate as columns in table, i want to do something like below in SQL
Add 1 month to Start_Date until start date > Mdate
I tried using while and if concepts, but no luck.
DECLARE #MIGRATIONDATE DATE, #STRT_DATE DATE, #NEXTD DATE
SET #MIGRATIONDATE =20140725
SET #STRT_DATE = 20140521
SELECT WHILE ( #STRT_DATE > #MIGRATIONDATE)
BEGIN
DATEADD(MM,1,#STRT_DATE))
END
appreciate if you can guide me on this?
I do not know if you would like to get one row of that table. If you want to do it for each row, then you should wrap it in a function and use CROSS APPLY.
declare
#startdate datetime,
#enddate datetime
set #startdate = '20140101'
set #enddate = '20150101'
;WITH date_range (thedate) AS (
select #startdate
UNION ALL SELECT DATEADD(MONTH, 1, thedate)
FROM date_range
WHERE DATEADD(MONTH, 1, thedate) <= #enddate
)
SELECT thedate FROM date_range
If you wanted in a function:
CREATE FUNCTION [dbo].[ExplodeDates](#startdate datetime, #enddate datetime)
returns table as
return (
WITH date_range (thedate) AS (
select #startdate
UNION ALL SELECT DATEADD(DAY, 1, thedate)
FROM date_range
WHERE DATEADD(DAY, 1, thedate) <= #enddate
)
SELECT thedate FROM date_range
);
SELECT Id,thedate
FROM Table1 T1
CROSS APPLY [dbo].[ExplodeDates](T1.StartDate,T1.EndDate)
select
case when start_date<Mdate then dateadd(mm,1,start_date) else start_date end
from yourTable
suppose start_date is 20140721 and MigrationDate is 20140525 then it returns you with gives you accepted result
select case
when convert( date,'20140721') <convert(date,'20140525')
then dateadd(mm,1,'20140721') else '20140721' end
How about (SQL Fiddle):
SELECT DATEADD(month,
DATEDIFF(month, Start_Date, Mdate) + 1,
Start_Date) AS NEW_START_DATE
FROM MyTable;
If there is a possibility of the Start_Date being greater than or equal to Mdate then use the following (SQL Fiddle):
SELECT Start_Date AS OLD_START_DATE,
CASE WHEN DATEDIFF(month, Start_Date, Mdate) > 0
THEN DATEADD(month, DATEDIFF(month, Start_Date, Mdate) + 1, Start_Date)
ELSE Start_Date END AS NEW_START_DATE,
Mdate
FROM MyTable;

Select date + 3 days, not including weekends and holidays

I've found a number of answers to the problem of doing a date-diff, in SQL, not including weekends and holidays. My problem is that I need to do a date comparison - how many child records are there whose work date is within three days of the parent record's send date?
Most of the date-diff answers involve a calendar table, and I think if I can build a sub-select that returns the date+3, I can work out the rest. But I can't figure out how to return a date+3.
So:
CREATE TABLE calendar
(
thedate DATETIME NOT NULL,
isweekday SMALLINT NULL,
isholiday SMALLINT NULL
);
And:
SELECT thedate AS fromdate, xxx AS todate
FROM calendar
What I want is for todate to be fromdate + 72 hours, not counting weekends and holidays. Doing a COUNT(*) where isweekday and not isholiday is simple enough, but doing a DATEADD() is another matter.
I'm not sure where to start.
EDIT:
Changed to include non-workdays as valid fromDates.
WITH rankedDates AS
(
SELECT
thedate
, ROW_NUMBER()
OVER(
ORDER BY thedate
) dateRank
FROM
calendar c
WHERE
c.isweekday = 1
AND
c.isholiday = 0
)
SELECT
c1.fromdate
, rd2.thedate todate
FROM
(
SELECT
c.thedate fromDate
,
(
SELECT
TOP 1 daterank
FROM
rankedDates rd
WHERE
rd.thedate <= c.thedate
ORDER BY
thedate DESC
) dateRank
FROM
calendar c
) c1
LEFT JOIN
rankedDates rd2
ON
c1.dateRank + 3 = rd2.dateRank
You could put a date rank column on the calendar table to simplify this and avoid the CTE:
CREATE TABLE
calendar
(
TheDate DATETIME PRIMARY KEY
, isweekday BIT NOT NULL
, isHoliday BIT NOT NULL DEFAULT 0
, dateRank INT NOT NULL
);
Then you'd set the daterank column only where it's a non-holiday weekday.
This should do the trick, change the number in the "top" to the number of days you want to include.
declare #date as datetime
set #date = '5/23/13'
select
max(_businessDates.thedate)
from (
select
top 3 _Calendar.thedate
from calendar _Calendar
where _Calendar.isWeekday = 1
and _Calendar.isholiday = 0
and _Calendar.thedate >= #date
order by
_Calendar.thedate
) as _businessDates
For a dynamic version that can go forward or backward a certain number of days try this:
declare #date as datetime
declare #DayOffset as int
set #date = '5/28/13'
set #DayOffset = -3
select
(case when #DayOffset >= 0 then
max(_businessDates.thedate)
else
min(_businessDates.thedate)
end)
from (
select
top (abs(#DayOffset) + (case when #DayOffset >= 0 then 1 else 0 end)) _Calendar.thedate
from calendar _Calendar
where _Calendar.isWeekday = 1
and _Calendar.isholiday = 0
and ( (#DayOffset >= 0 and _Calendar.thedate >= #date)
or (#DayOffset < 0 and _Calendar.thedate < #date) )
order by
cast(_Calendar.thedate as int) * (case when #DayOffset >=0 then 1 else -1 end)
) as _businessDates
You can set #DayOffset to a positive or negative number.
You just need DATEADD, unless I'm not understanding your question.
DATEADD(DAY,3,fromdate)
Edit: I see, not counting weekends or Holidays, will update momentarily.
Update: Well looks like Jason nailed it, but on the off chance you're using SQL2012, here's the simple version:
SELECT todate = thedate
fromdate = LEAD(thedate,3) OVER (ORDER BY thedate)
FROM calendar
WHERE isweekday = 1
AND isHoliday = 0
Try this if you need it as a query with dateAdd:
SELECT
allDates.thedate fromDate
,min(nonWeekendHoliday.thedate) toDate
FROM (
SELECT
thedate
FROM
calendar _calendar
) allDates
LEFT JOIN (
SELECT
thedate
FROM
calendar _calendar
WHERE
_calendar.isweekday = 1
AND
_calendar.isholiday = 0
) nonWeekendHoliday
on dateadd(d,3,allDates.thedate) <= nonWeekendHoliday.thedate
where allDates.thedate between '5/20/13' and '5/31/13'
group by
allDates.thedate