Comparing a Date Range to a set of Date Ranges SQL - sql

Ok, so this particular use case is giving me quite a bit of headache.
What i am trying to do is figure out if any StartDate and EndDates in a set is overlapping with a set of Start and EndDates, if they are, the overlapping rows must be returned.
DateRange Table -- contains all DateRanges
DateRangeId StartDate EndDate CustomerJobQuery_Id WorkWish_Id
----------- --------------------------- --------------------------- ------------------- -----------
1 2014-03-31 00:00:00.0000000 2014-08-18 00:00:00.0000000 NULL 1
2 2014-08-25 00:00:00.0000000 2014-09-15 00:00:00.0000000 NULL 1
3 2013-08-24 00:00:00.0000000 2014-09-25 00:00:00.0000000 1 NULL
Workwish Table
Id Candidate_Id
----------- ------------
1 5
CustomerJobQuery Table
Id Customer_CustomerId
----------- -------------------
1 2
this is my DateRange table.. what my StoredProcedure accepts is a CandidateId; i wish to find the Candidate Id's WorkWishes , Match the DateRanges of the WorkWish with ALL of the CustomerJobQuery DateRanges and return the CustomerJobQueries that had overlapping dates.
I figured out how to do the check if a specific DateRange overlaps with another DateRange.. but i am stumped as to how to do this comparison on a set.
Any help is much appreciated!

Try this query:
SELECT c.*
FROM CustomerJobQuery c
WHERE EXISTS (
SELECT 1
FROM Workwish w
INNER JOIN DateRange d1 ON d1.CustomerJobQuery_Id = c.Id
INNER JOIN DateRange d2 ON d2.WorkWish_Id = w.Id
WHERE w.Candidate_Id = 5 -- CandidateId in procedure
-- overlap check:
AND d1.StartDate <= d2.EndDate
AND d2.StartDate <= d1.EndDate
)
Test it here (SQL Fiddle): http://sqlfiddle.com/#!3/10253/3

Related

Find missing rows between three related tables

I have three tables:
Person
person_id
-------------
10001
10002
10003
10004
Dates
date_type date
-------------- -----------------------
PUBLIC_HOLIDAY 2020-04-10 00:00:00.000
PUBLIC_HOLIDAY 2020-04-13 00:00:00.000
Absence
person_id date absence_type
--------- ----------------------- ------------
10001 2020-04-10 00:00:00.000 HOLIDAY
10001 2020-04-13 00:00:00.000 HOLIDAY
10002 2020-04-10 00:00:00.000 HOLIDAY
10003 2020-04-13 00:00:00.000 HOLIDAY
I need to find all of the person_id's in the Person table and the date's from the Dates table who have not booked any absence matching the following criteria:
Dates.date_type = 'PUBLIC_HOLIDAY'
Absence.absence_type = 'HOLIDAY'
Basically, I need to find the people and the dates which are public holidays they have not booked an absence for as holiday.
You can try this below logic-
DEMO HERE
SELECT Person.person_id,Dates.dat,ISNULL(Absence.dat, 'Not Bokked')
FROM Dates
CROSS JOIN Person
LEFT JOIN Absence ON Person.person_id = Absence.person_id AND Dates.dat = Absence.dat
WHERE Dates.date_type = 'PUBLIC_HOLIDAY'
If you wants only information with not booked, just simply add below line to the script-
AND Absence.dat IS NULL
I think that you want a cross join to generate all combinations of persons and dates, and then not exists to filter on those that do not exist in the absence table:
select p.*, d.*
from person p
cross join dates d
where
d.date_type = 'PUBLIC_HOLIDAY'
and not exists (
select 1
from absence a
where a.person_id = p.person_id and a.date = d.date and a.absence_type = 'HOLIDAY'
)
Try:
select distinct person_id from absence a
where absence_type = 'HOLIDAY'
and not exists (select 1 from dates
where date = a.date
and date_type = 'PUBLIC_HOLIDAY')
union all
select person_id from person p
where not exists ( select 1 from absence
where p.person_id = person_id)
If you want to have them with dates, use below query:
select person_id, date from absence a
where absence_type = 'HOLIDAY'
and not exists (select 1 from dates
where date = a.date
and date_type = 'PUBLIC_HOLIDAY')
union all
-- in person table we don;t have any dates
select person_id, null from person p
where not exists ( select 1 from absence
where p.person_id = person_id)
you can use below query. I have tested this in SQL Server 2014.
CREATE TABLE #Person(person_id INT)
INSERT INTO #person
values
(10001),
(10002),
(10003),
(10004);
CREATE TABLE #Dates (date_Type VARCHAR(50), [datevalue] datetime)
INSERT INTO #Dates
VALUES
('PUBLIC_HOLIDAY','2020-04-10 00:00:00.000'),
('PUBLIC_HOLIDAY','2020-04-13 00:00:00.000');
CREATE TABLE #Absence (person_id int, datevalue datetime, absence_type VARCHAR(50))
INSERT INTO #Absence
VALUES
(10001,'2020-04-10 00:00:00.000','HOLIDAY'),
(10001,'2020-04-13 00:00:00.000','HOLIDAY'),
(10002,'2020-04-10 00:00:00.000','HOLIDAY'),
(10003,'2020-04-13 00:00:00.000','HOLIDAY');
SELECT p.person_id, od.datevalue
FROM #Person AS p
CROSS JOIN (SELECT * FROM #Dates WHERE date_type ='PUBLIC_HOLIDAY') AS od
WHERE NOT EXISTS
(
SELECT 1 FROM
#Absence AS a
INNER JOIN #Dates AS d
ON a.datevalue = d.datevalue
WHERE a.absence_type = 'Holiday' AND d.date_type = 'PUBLIC_HOLIDAY'
AND a.person_id = p.person_id and d.datevalue = od.datevalue)
Below is the resultset:
+-----------+-------------------------+
| person_id | datevalue |
+-----------+-------------------------+
| 10003 | 2020-04-10 00:00:00.000 |
| 10004 | 2020-04-10 00:00:00.000 |
| 10002 | 2020-04-13 00:00:00.000 |
| 10004 | 2020-04-13 00:00:00.000 |
+-----------+-------------------------+

Find missing record between date range

At the end of an enormous stored procedure (in SQL Server), I've created two CTE. One with some date ranges (with 6 month intervals) and one with some records.
Let's assume i have date ranges on table B from 2020-01-01 to 2010-01-01 (with 6 months intervals)
Start End
----------------------
2020-01-01 | 2020-07-01
... ...
other years here
... ...
2010-01-01 | 2010-07-01
and on table A this situation:
Name Date
-----------------
John 2020-01-01
John 2019-01-01
John 2018-07-01
... ...
Rob 2020-01-01
Rob 2019-07-01
Rob 2018-07-01
... ...
I'm trying to generate a recordset like this:
Name MissingDate
-----------------
John 2019-07-01
... ...
John 2010-01-01
Rob 2019-01-01
... ...
Rob 2010-01-01
I've got the flu and I barely know who I am at this moment, I hope it was clear and if anyone could help me with this I would really appreciate it.
If you want missing dates (which appear to be by month), then generate all available dates and take out the ones you have.
with cte as (
select start, end
from dateranges
union all
select dateadd(month, 1, start), end
from cte
where start < end
)
select n.name, cte.start
from cte cross join
(select distinct name from tablea) n left join
tablea a
on a.date = cte.start and a.name = n.name
where a.date is null;

Excluding events within date-range while joining tables in SSMS

SQL newbie here. Using Microsoft SQL management studio. Having trouble excluding records that are outside of a particular date range while joining two tables. Please Help!
I have two tables - Event and DateRange.
Event
CustomerID ---------------- EventDate
1 ------------------------- 1/2/14
1 -------------------------- 2/5/15
1 -------------------------- 6/1/13
DateRange
CustomerID ---------------- StartDate ----------- EndDate
1 -------------------------- 1/1/14 -------------- 1/3/14
1 -------------------------- 2/1/15 -------------- 2/3/15
1 -------------------------- 5/30/13 ------------- 6/2/13
I want to join those two tables using CustomerID but I only want to ouput the date that was outside of the Startdate and EndDate date range (row 2 in Event table - 2/5/15).
I tried the following query but what keeps happening is that it kept giving me all the dates back in multiple because for each event date there were two StartDate and EndDate where it didn't fit into.
SQL Query
SELECT
E.CustomerID
,E.EventDate
,D.StartDate
,D.EndDate
FROM
Event AS E
INNER JOIN DateRange AS D
ON E.CustomerID = D.CustomerID
WHERE
(E.EventDate NOT BETWEEN D.StartDate and D.EndDate)
ORDER BY E.CustomerID
The output I got was something like this...
CustomerID ----- EventDate ----- StartDate ----- EndDate
1 -------------- 1/2/14 -------- 2/1/15 -------- 2/3/15
1 -------------- 1/2/14 -------- 5/30/13-------- 6/2/15
1 -------------- 2/5/15 -------- 1/1/14 -------- 1/3/14
1 -------------- 2/5/15 -------- 5/30/13 ------- 6/2/13
1 -------------- 6/1/13 -------- 1/1/14 -------- 1/3/14
1 -------------- 6/1/13 -------- 2/1/15 -------- 2/3/15
It's kind of doing a cross join because all the customerIDs are the same. And I need to keep the customerID because that's the only way to join those two tables. So how can I join these two table to only get the output that I want. Even if I do SELECT DISTINCT just the E.EventDate column by itself it'll still give me all three EventDates.
I pretty much need a count of how many event dates there are that don't fall into the startdate and enddate.
Thanks.
The problem is that you don't have a unique ID on your Event table to exclude your result set from. Since you're joining by CustomerID, it's giving you every row that doesn't meet your criteria. You might want to try something like this.
CREATE TABLE Event (ID INT NOT NULL IDENTITY PRIMARY KEY, CustomerID INT, EventDate DATETIME)
SELECT DISTINCT
E.EventDate
,E.CustomerID
FROM
Event AS E
INNER JOIN DateRange AS D
ON E.CustomerID = D.CustomerID
WHERE
E.ID NOT IN (
SELECT E.ID FROM Event AS E
INNER JOIN DateRange AS D
ON E.CustomerID = D.CustomerID
WHERE E.EventDate BETWEEN D.StartDate and D.EndDate
)
ORDER BY E.CustomerID
As you can see from above, I've created an Identity ID for the Event table. Now you can use a sub query to exclude the "events" from the result set that have dates that fall within a date range in the DateRange table. Which will give you what you're looking for. Hope that helps.
EventDate ------------- CustomerID
2015-02-05 00:00:00.000 1

SQL Server: Finding date given EndDate and # Days, excluding days from specific date ranges

I have a TableA in a database similar to the following:
Id | Status | Start | End
1 | Illness | 2013-04-02 | 2013-04-23
2 | Illness | 2013-05-05 | 2014-01-01
3 | Vacation | 2014-02-01 | 2014-03-01
4 | Illness | 2014-03-08 | 2014-03-09
5 | Vacation | 2014-05-05 | NULL
Imagine it's keeping track of a specific user's "Away" days. Given the following Inputs:
SomeEndDate (Date),
NumDays (Integer)
I want to find the SomeStartDate (Date) that is Numdays non-illness days from EndDate. In other words, say I am given a SomeEndDate value '2014-03-10' and a NumDays value of 60; the matching SomeStartDate would be:
2014-03-10 to 2014-03-09 = 1
2014-03-08 to 2014-01-01 = 57
2013-05-05 to 2013-05-03 = 2
So, at 60 non-illness days, we get a SomeStartDate of '2013-05-03'. IS there any easy way to accomplish this in SQL? I imagine I could loop each day, check whether or not it falls into one of the illness ranges, and increment a counter if not (exiting the loop after counter = #numdays)... but that seems wildly inefficient. Appreciate any help.
Make a Calendar table that has a list of all the dates you will ever care about.
SELECT MIN([date])
FROM (
SELECT TOP(#NumDays) [date]
FROM Calendar c
WHERE c.Date < #SomeEndDate
AND NOT EXISTS (
SELECT 1
FROM TableA a
WHERE c.Date BETWEEN a.Start AND a.END
AND Status = 'Illness'
)
ORDER BY c.Date
) t
The Calendar table method lets you also easily exclude holidays, weekends, etc.
SQL Server 2012:
Try this solution:
DECLARE #NumDays INT = 70, #SomeEndDate DATE = '2014-03-10';
SELECT
[RangeStop],
CASE
WHEN RunningTotal_NumOfDays <= #NumDays THEN [RangeStart]
WHEN RunningTotal_NumOfDays - Current_NumOfDays <= #NumDays THEN DATEADD(DAY, -(#NumDays - (RunningTotal_NumOfDays - Current_NumOfDays))+1, [RangeStop])
END AS [RangeStart]
FROM (
SELECT
y.*,
DATEDIFF(DAY, y.RangeStart, y.RangeStop) AS Current_NumOfDays,
SUM( DATEDIFF(DAY, y.RangeStart, y.RangeStop) ) OVER(ORDER BY y.RangeStart DESC) AS RunningTotal_NumOfDays
FROM (
SELECT LEAD(x.[End]) OVER(ORDER BY x.[End] DESC) AS RangeStart, -- It's previous date because of "ORDER BY x.[End] DESC"
x.[Start] AS RangeStop
FROM (
SELECT #SomeEndDate AS [Start], '9999-12-31' AS [End]
UNION ALL
SELECT x.[Start], x.[End]
FROM #MyTable AS x
WHERE x.[Status] = 'Illness'
AND x.[End] <= #SomeEndDate
) x
) y
) z
WHERE RunningTotal_NumOfDays - Current_NumOfDays <= #NumDays;
/*
Output:
RangeStop RangeStart
---------- ----------
2014-03-10 2014-03-09
2014-03-08 2014-01-01
2013-05-05 2013-05-03
*/
Note #1: LEAD(End) will return the previous End date (previous because of ORDER BY End DESC)
Note #2: DATEDIFF(DAY, RangeStart, RangeStop) computes the num. of days between current start (alias x.RangeStop) and "previous" end (alias x.RangeStar) => Current_NumOfDays
Note #3: SUM( Current_NumOfDays ) computes a running total thus: 1 + 66 + (3)
Note #4: I've used #NumOfDays = 70 (not 60)

Select dates in ranges list

I have table with records, each row contains DATETIME column which describes when row was loaded into table. And I have CTE which creates ranges (count is vary) like one below.
first_day_of_month last_day_of_moth
-------------------------------------------------------
2013-12-01 00:00:00.000 2013-12-31 23:59:59.000
2013-11-01 00:00:00.000 2013-12-31 23:59:59.000
2013-10-01 00:00:00.000 2013-12-31 23:59:59.000
2013-09-01 00:00:00.000 2013-12-31 23:59:59.000
2013-08-01 00:00:00.000 2013-12-31 23:59:59.000
Question: Now I want to select minimal DATETIME value from first table for each range created in CTE. I am absolutely have no idea how to do it. Any ideas/links are appreciated.
For example, it should looks like:
2013-12-10
2013-11-20
2013-10-05
2013-09-13
2013-08-06
UPD: Date or datetime - it is no matter
UPD2: I found that I can join my tables using condition like:
INNER JOIN source_monthes_dates ON
(load_timestamp >= first_day_of_month AND load_timestamp <= last_day_of_moth)
but actually I do not know how to acquire only first date of period.
You can use this query which uses ROW_NUMBER() to get the minimum. ranges is the result of your CTE, table1 is the other table where you have dates.
select x.somedate
from
(select t.somedate,
ROW_NUMBER() OVER (PARTITION BY r.first_day_of_month, r.last_day_of_moth ORDER BY t.somedate) rownumber
from ranges r
inner join table1 t
on r.first_day_of_month <= t.somedate and r.last_day_of_moth >= t.somedate) x
where x.rownumber = 1
SQL Fiddle demo
If you want to get all the ranges and include only those days that match ranges and display null for others, you can join ranges once more:
select ranges.first_day_of_month, ranges.last_day_of_moth, x.somedate
from
ranges
left join
(select t.somedate, r.first_day_of_month, r.last_day_of_moth,
ROW_NUMBER() OVER (PARTITION BY r.first_day_of_month, r.last_day_of_moth ORDER BY t.somedate) rownumber
from ranges r
inner join table1 t
on r.first_day_of_month <= t.somedate and r.last_day_of_moth >= t.somedate) x
on x.first_day_of_month = ranges.first_day_of_month and x.last_day_of_moth = ranges.last_day_of_moth
where isnull(x.rownumber, 1) = 1
SQL Fiddle demo