fetching null value with specify condition with left join - sql

i have the following data in table leave:-
EMPNO NAME DATEFROM DATETO
111 xxx 2014-08-03 00:00:00.000 2014-09-05 00:00:00.000
222 yyy 2014-10-03 00:00:00.000 2014-10-05 00:00:00.000
but when i write below query :
select empno,name,sum(datediff(day,DATEFROM,DATETO)+1) as holiday
from leave
where DATEFROM>='2014-08-01'
and DATETO<='2014-08-31'
and empno=111
group by name ,empno
then I didnt get any value..but with this query i want below output:
EMPNO NAME holiday
111 xxx 29
august has 31 days..
then how can i get?
it will only count the day of august(08) month

In your query you are specifying DateTo MUST be <= 2014-08-31... but the DateTo value on your record is 2014-09-05. So that is excluding it.
I'm assuming you want all records that start in August, so you want DateFrom in both conditions.
Per your updates/comments:
declare #data table (empno int, name varchar(5), datefrom datetime, dateto datetime)
insert into #data
select 111,'xxx','2014-08-03 00:00:00.000','2014-09-05 00:00:00.000'
declare #maxdate datetime, #mindate datetime
set #mindate = '2014-08-01'
set #maxdate = '2014-08-31'
select empno,name,sum(datediff(day,DATEFROM,case when dateto > #maxdate then #maxdate else dateto end)+1) as holiday
from #data
where DATEFROM >=#mindate and DATEFROM <=#maxdate and empno=111 group by name ,empno
Per your last question regarding weekends. What you need to do is cross join a calendar table to the event, and omit the days you don't want. From there, instead of a datediff() you can simply count the remaining days.
declare #data table (empno int, name varchar(5), datefrom datetime, dateto datetime)
insert into #data
select 111,'xxx','2014-08-03 00:00:00.000','2014-09-05 00:00:00.000'
declare #maxdate datetime, #mindate datetime
set #mindate = '2014-08-01'
set #maxdate = '2014-08-31'
;with cal as
(
select #mindate as dDate, datename(dw, #mindate) as DayWeek
union all
select dDate+1, datename(dw, dDate+1) as DayWeek
from cal
where dDate+1 <= #maxdate
)
select empno, name, count(*) as holiday
from #data d
cross join cal c
where c.ddate between d.datefrom and d.dateto
and c.dayweek not in ('saturday','sunday')
and DATEFROM >=#mindate and DATEFROM <=#maxdate and empno=111
group by name ,empno

Related

SQL all records that covers to dates in SQL server 2016

I have a table as the below:(date format is dd/MM/yyyy) (SQL server 2016)
ID FromDate ToDate
1 01/01/2018 30/01/2018
2 02/02/2018 02/03/2018
3 03/03/2018 03/04/2018
4 04/04/2018 05/05/2018
I need to get the records that covers all the period I supply( as from-to dates).
For example if I supplied the dates (StartDate=10/02/2018) and (EndDate=01/05/2018) the query should return the records 2,3,4
since the startDate is within the record of ID=2 and the endDate within the record of ID=4 and the rest of the dates is between the records 2,3,4/.
If there is a break in the dates then it should return nothing,for example if I supplied the dates (StartDate=01/02/2018) and (EndDate=01/05/2018)
nothing should be returned since 01/02/2018 is not covered by any dates.
Also as an another example:
ID FromDate ToDate
1 01/01/2018 30/01/2018
2 02/02/2018 02/03/2018
3 06/03/2018 03/04/2018
4 04/04/2018 05/05/2018
if I supplied (StartDate=10/02/2018) and (EndDate=01/05/2018)
should return nothing, since there is a break of dates between 02/03/2018 and 06/03/2018
Edit: for date breaks
declare #t table
(id int identity,
FromDate date,
ToDate date
);
insert into #t(FromDate, ToDate)
values
('20180101', '20180201'),
('20180202', '20180302'),
('20180303', '20180403'),
('20180404', '20180505');
declare #fromdate date = '20180210',
#todate date = '20180501';
select *
from #t
where FromDate <= #todate
and Todate >= #fromdate;
with d
as
(
select *, lag(ToDate) over(order by FromDate) as PreviousToDate
from #t
where FromDate <= #todate
and Todate >= #fromdate
)
select Id, FromDate, ToDate
from d
where not exists(select * from d as bd where bd.FromDate<>dateadd(day, 1, bd.PreviousToDate));
create table t
(id int identity,
FromDate date,
ToDate date
)
insert into t(FromDate, ToDate)
values
('20180101', '20180201'),
('20180202', '20180302'),
('20180303', '20180403'),
('20180404', '20180505')
declare #fromdate date = '20180210',
#todate date = '20180501';
declare #fromdatetopick date,#todatetopick date,#idtopick int,#count int,#fromdatepick date,#todatepick date,#idpick int,#datediff int,#maxdatediff int
set #count=0
set #maxdatediff=0
declare abc cursor for
select *
from t
where FromDate >= #fromdate
or (Todate <= #todate and Todate>=#fromdate)
open abc
fetch next from abc into #idtopick,#fromdatetopick,#todatetopick
while ##FETCH_STATUS=0
begin
if (#count=0)
begin
set #todatepick=#todatetopick
end
else
begin
set #fromdatepick=#fromdatetopick
set #datediff=DATEDIFF(day,#todatepick,#fromdatepick)
if (#datediff>#maxdatediff)
begin
set #maxdatediff=#datediff
end
end
set #todatepick=#todatetopick
set #count=#count+1
fetch next from abc into #idtopick,#fromdatetopick,#todatetopick
end
close abc
deallocate abc
if (#maxdatediff>1)
begin
select * from t where id=null
end
else
begin
select *
from t
where FromDate >= #fromdate
or (Todate <= #todate and Todate>=#fromdate)
end
You would seem to want:
select t.*
from (select t.*,
lag(todate) over (order by fromdate) as prev_todate
from t
) t
where fromdate < #todate and
todate > #fromdate and
(prev_todate is null and fromdate = #fromdate or
prev_todate = dateadd(day, -1, fromdate)
);
Note that this handles the situation where the dates start on your desired fromdate.

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;

SQL Select birthdays in specific date range

I have following:
DECLARE #TempTable TABLE
(
[Id] [int],
[FirstName] [varchar](40) NULL,
[Birthdate] [date] NULL
);
insert into #TempTable values (1, 'A', convert(date, '05/25/2017', 101))
insert into #TempTable values (2, 'B', convert(date, '06/25/2017', 101))
What I need is query which will return all Birthdays in range from StartDate to to EndDate.
To be more here is what I expect to get as a result:
Case 1:
If date range is set to:
DECLARE #StartDate datetime = '05/01/2017';
DECLARE #EndDate datetime = '07/01/2017';
Query should return:
1 A 2017-05-25
2 B 2017-06-25
Case 2:
If date range is set to:
DECLARE #StartDate datetime = '05/01/2017';
DECLARE #EndDate datetime = '06/01/2017';
Query should return:
1 A 2017-05-25
Case 3:
If date range is set to:
DECLARE #StartDate datetime = '05/01/2015';
DECLARE #EndDate datetime = '07/01/2017';
Query should return:
1 A 2017-05-25
1 A 2016-05-25
1 A 2015-05-25
2 B 2017-06-25
2 B 2016-06-25
2 B 2015-06-25
Case 4:
If date range is set to:
DECLARE #StartDate datetime = '05/01/2015';
DECLARE #EndDate datetime = '06/01/2017';
Query should return
1 A 2017-05-25
1 A 2016-05-25
1 A 2015-05-25
2 B 2016-06-25
2 B 2015-06-25
Firstly we creates all dates for given range . then apply logic
DECLARE #StartDate datetime = '05/01/2015';
DECLARE #EndDate datetime = '06/01/2017';
;With DateSequence as
(
Select #StartDate as Bdate
union all
Select dateadd(day, 1, Bdate)
from DateSequence
where Bdate < #EndDate
)
Select ID,FirstName,Bdate as BirthDate from DateSequence
cross join #TempTable
where Bdate between #StartDate and #EndDate and
month(Bdate)= month(BirthDate) and day(Bdate) = day(BirthDate)
order by ID asc , Bdate desc
option (MaxRecursion 2000)
OutPut :
At its most basic level, you can do this by creating a list of everyone's birthday for 100 years, then filtering...
with numbers as
(
select 0 as NN
union all
select NN+1
from numbers
where NN < 100
)
select id, dateadd(yy,NN,BirthDate) as Birthdays
from numbers
cross join #TempTable
where dateadd(yy,NN,BirthDate) between #StartDate and #EndDate

Find the start and end date of two different dates in SQL Server, Group by month

If input is provided like below
declare #from datetime = '2016-09-15'
declare #to datetime = '2016-12-25'
Then output should be like below
Month Start_date End_date
September 2016-09-15 2016-09-30
October 2016-10-01 2016-10-31
November 2016-11-01 2016-11-30
December 2016-12-01 2016-12-25
If input is provided like this:
declare #from datetime = '2016-12-05'
declare #to datetime = '2016-12-25'
Then output should be like this:
Month Start_date End_date
December 2016-12-05 2016-12-25
Thanks in advance.
Try this, I hope this helps.
DECLARE #fromDate DATE = '2016-09-05', #toDate DATE = '2016-12-25', #tempStartDate DATE
DECLARE #tempTable TABLE(Month NVARCHAR(50), Start_Date DATE, End_Date DATE)
SELECT #tempStartDate = #fromDate
WHILE(CAST(#tempStartDate AS DATE) <= CAST(#toDate AS DATE))
BEGIN
INSERT INTO #tempTable
SELECT DATENAME(MONTH, #tempStartDate),#tempStartDate,
CASE WHEN DATEPART(MONTH,#tempStartDate) = DATEPART(MONTH,#toDate) THEN #toDate ELSE DATEADD(s,-1,DATEADD(mm, DATEDIFF(m,0,#tempStartDate)+1,0)) END
SET #tempStartDate = DATEADD(s,1,DATEADD(mm, DATEDIFF(m,0,#tempStartDate)+1,0))
END
SELECT * FROM #tempTable
You can do this with an ad-hoc tally table. Using spt_values, but any larger table will do.
Furthermore, this approach would be faster than a recursive cte, especially for larger sets
declare #from date = '2016-09-15'
declare #to date = '2016-12-25'
Select Month = max(DateName(MONTH,D))
,Start_date = min(D)
,End_date = max(D)
From (Select Top (DateDiff(DD,#from,#to)+1) D=DateAdd(DD,Row_Number() Over (Order By (Select null))-1,#from) From master..spt_values ) A
Group By Year(D),Month(D)
Order By min(d)
Returns
Month Start_date End_date
September 2016-09-15 2016-09-30
October 2016-10-01 2016-10-31
November 2016-11-01 2016-11-30
December 2016-12-01 2016-12-25
Edit - As Requested
The process is really not that complicated. We use Row_Number() in concert with DateAdd() to generate a list of dates within the specified date range.
The sub-query produces the following
D
2016-09-15
2016-09-16
2016-09-17
2016-09-18
2016-09-19
2016-09-20
2016-09-21
2016-09-22
2016-09-23
...
2016-12-20
2016-12-21
2016-12-22
2016-12-23
2016-12-24
2016-12-25
Then it becomes a simple matter of getting the min/max dates by year/month.
Again, I used master..spt_values, but virtually any table would do. If you don't have a tally/numbers table, I would strongly recommend one.
It can be accomplished with a recursive CTE
declare #from datetime = '2016-09-15'
declare #to datetime = '2016-12-25'
;with cte_r
AS
(
SELECT
#from AS Dte1
,#to AS Dte2
,DATEADD(MM,DATEDIFF(MM,0,#from),0) AS MonthStrt
,DATEADD(DD,-1,DATEADD(MM,DATEDIFF(MM,0,#from)+1,0)) AS MonthEnd
UNION ALL
SELECT
#from AS Dte1
,#to AS Dte2
,DATEADD(MM,1,MonthStrt)
,DATEADD(MM,1,MonthEnd)
FROM
cte_r
WHERE
MonthEnd <= #to
)
SELECT
CASE WHEN MonthStrt <= #from THEN #from ELSE MonthStrt END AS Stat_Date
,CASE WHEN MonthEnd >= #to THEN #to ELSE MonthEnd END AS End_Date
FROM
cte_r
declare #from date = '2016-09-15'
declare #to date = '2016-12-25';
with cte as
(select datename(month,#from )as month1,1 as n , #from as startdate,eomonth(#from)as endate
union all
select datename(month,dateadd(day,n,#from)),n+1,dateadd(day,n,#from),eomonth(dateadd(day,n,#from))
from cte where dateadd(day,n,#from)<#to
)select month1,min(startdate),max(endate) from cte
group by month1
order by 1 desc

Easiest way to populate a temp table with dates between and including 2 date parameters

What is the easiest way to populate a temp table with dates including and between 2 date parameters. I only need the 1st day of the month dates.
So for example if #StartDate = '2011-01-01' and #EndDate = '2011-08-01'
Then I want this returned in the table
2011-01-01
2011-02-01
2011-03-01
2011-04-01
2011-05-01
2011-06-01
2011-07-01
2011-08-01
This works even if the #StartDate is not the first of the month. I'm assuming that if it's not the start of the month, you want to begin with the first of the next month. Otherwise remove the +1.:
;WITH cte AS (
SELECT CASE WHEN DATEPART(Day,#StartDate) = 1 THEN #StartDate
ELSE DATEADD(Month,DATEDIFF(Month,0,#StartDate)+1,0) END AS myDate
UNION ALL
SELECT DATEADD(Month,1,myDate)
FROM cte
WHERE DATEADD(Month,1,myDate) <= #EndDate
)
SELECT myDate
FROM cte
OPTION (MAXRECURSION 0)
declare #StartDate date = '2014-01-01';
declare #EndDate date = '2014-05-05';
;WITH cte AS (
SELECT #StartDate AS myDate
UNION ALL
SELECT DATEADD(day,1,myDate) as myDate
FROM cte
WHERE DATEADD(day,1,myDate) <= #EndDate
)
SELECT myDate
FROM cte
OPTION (MAXRECURSION 0)
declare #StartDate datetime
declare #EndDate datetime
select #StartDate = '2011-01-01' , #EndDate = '2011-08-01'
select #StartDate= #StartDate-(DATEPART(DD,#StartDate)-1)
declare #temp table
(
TheDate datetime
)
while (#StartDate<=#EndDate)
begin
insert into #temp
values (#StartDate )
select #StartDate=DATEADD(MM,1,#StartDate)
end
select * from #temp
Works even if the #StartDate is not the first day of the month by going back to the initial day of the month of StartDate
this is tested in SQL 2008 R2
Declare #StartDate datetime = '2015-03-01'
Declare #EndDate datetime = '2015-03-31'
declare #temp Table
(
DayDate datetime
);
WHILE #StartDate <= #EndDate
begin
INSERT INTO #temp (DayDate) VALUES (#StartDate);
SET #StartDate = Dateadd(Day,1, #StartDate);
end ;
select * from #temp
Result:
DayDate
-----------------------
2015-03-01 00:00:00.000
2015-03-02 00:00:00.000
2015-03-03 00:00:00.000
2015-03-04 00:00:00.000
...
Interestingly, it is faster to create from enumerated data as per this article.
DECLARE #StartDate DATE = '10001201';
DECLARE #EndDate DATE = '20000101';
DECLARE #dim TABLE ([date] DATE)
INSERT #dim([date])
SELECT d
FROM
(
SELECT
d = DATEADD(DAY, rn - 1, #StartDate)
FROM
(
SELECT TOP (DATEDIFF(DAY, #StartDate, #EndDate))
rn = ROW_NUMBER() OVER (ORDER BY s1.[object_id])
FROM
sys.all_objects AS s1
CROSS JOIN
sys.all_objects AS s2
ORDER BY
s1.[object_id]
) AS x
) AS y;
On my machine, it's around 60% faster with large date ranges. The recursion method can populate 2000 years worth of data in around 3 seconds though, and looks a lot nicer, so I don't really recommend this method just for incrementing days.
Correction for null dates:
IF OBJECT_ID('tempdb..#dim') IS NOT NULL
DROP TABLE #dim
CREATE TABLE #dim ([date] DATE)
if not #Begin_Date is null and not #End_Date is null
begin
INSERT #dim([date])
SELECT d
FROM(
SELECT
d = DATEADD(DAY, rn - 1, #Begin_Date)
FROM
(
SELECT TOP (DATEDIFF(DAY, #Begin_Date, #End_Date))
rn = ROW_NUMBER() OVER (ORDER BY s1.[object_id])
FROM
sys.all_objects AS s1
CROSS JOIN
sys.all_objects AS s2
ORDER BY
s1.[object_id]
) AS x
) AS y;
end
CREATE TABLE #t (d DATE)
INSERT INTO #t SELECT GETDATE()
GO
INSERT #t SELECT DATEADD(DAY, -1, MIN(d)) FROM #t
GO 10