SQL Current status for given day (FOR loop) - sql

For simplicity lets assume that I have a view with three fields
date_in (date)
Container (varchar)
date_out (date)
Now, the container is IN if the date_in is lesser or equal to given date and date_out is null or greater than given date. Now I am trying to count the containers for given time period. In pseudocode between two values STARTDATE and ENDDATE it would be something like
FOR X =STARTDATE, X<= ENDDADE, X++ {
if date_in <=X and date_out>x
count (container)
}
or closer to SQL:
declare #startdate date,
#d date;
set #startdate = '1/01/2014'
set #d = #startdate
"FOR on the #d variable would go here" {
select #d as SNAP_DATE, count (container) where date_in <#d
and (date_out is null or date_out> #d)
}
It might be simple - I guess I could make a new table and manually do multiple SELECT INTO (and later query from this new table) but its not very elegant solution.
Edit: just to precise - in the end I'd like to have something like:
DATE Count
1/02/2014 10
2/02/2014 15
...
7/03/2014 19

You could do this procedurally as follows:
Use a while loop to loop from start to end date.
Use a table variable to store each date-count pair.
Select from the table variable to get the summarised result.
declare #start date = '1/01/2014'
declare #end date = '7/03/2014'
declare #tbl table(Date date, Count int)
while(#start < #end)
begin
insert into #tbl
select #start, count(*)
from your_view
where (in_date < #start)
and ((out_date is null) or (out_date > #start))
set #start = dateadd(day, 1, #start)
end
select * from #tbl

You might be able to do something like the following. It uses a numbers table, which can be a real or derived table. It contains rows of integers. You need a table that begins with 0 and has enough values to cover your date range. Check here for more information on a numbers table.
DECLARE #StartDate DATE = '1/2/2014'
DECLARE #EndDate DATE = '1/4/2014'
SELECT DATEADD(d, n.num, #StartDate) AS DATE, COUNT(*) AS COUNT
FROM Numbers n
JOIN MyView mv ON mv.date_in < DATEADD(d, n.num, #StartDate)
AND (mv.date_out IS NULL OR mv.date_out > DATEADD(d, n.num, #StartDate))
WHERE DATEADD(d, n.num, #StartDate) BETWEEN #StartDate AND #EndDate
GROUP BY DATEADD(d, n.num, #StartDate)
ORDER BY DATEADD(d, n.num, #StartDate)
The numbers in the numbers table are converted to the list of dates between the date range. Each date is joined to your view based on the criteria you need.

Related

Add a record for every Week-Day of the year to a blank table

I have a blank table that has two columns [ID] and [MyDate].
I would like to populate that table with all of the days of the current year MINUS weekends.
Is there a way to do this with a SQL query?
In this case I am using MSSQL T-SQL
I do not have any example code, as I am at a loss on where to get started for this scenario.
Using a numbers (Tally) table helps you to avoid using loops.
If you don't already have a numbers table, you can use this script to create it:
SELECT TOP 10000 IDENTITY(int,0,1) AS Number
INTO Tally
FROM sys.objects s1
CROSS JOIN sys.objects s2
ALTER TABLE Tally ADD CONSTRAINT PK_NumbersTest PRIMARY KEY CLUSTERED (Number)
For more information about the creation of a numbers table, read this SO post.
Now that you have a numbers table, you can use a cte to generate the dates you want. I've used DATEFROMPARTS and GETDATE() to get Jauary 1st of the current year, if you are using a version of sql server below 2012 you need to use other methods for that:
DECLARE #StartDate Date,
#EndDate Date
SELECT #StartDate = DATEFROMPARTS(YEAR(GetDate()), 1, 1)
SELECT #EndDate = DATEADD(Year, 1, #StartDate)
Now, create a CTE to get the dates required using the numbers table, and insert the records from the cte to the table:
;WITH CTE AS
(
SELECT DATEADD(Day, Number, #StartDate) As TheDate
FROM Tally
WHERE DATEADD(Day, Number, #StartDate) < #EndDate
)
INSERT INTO WeekDays
SELECT TheDate
FROM CTE
WHERE DATEPART(WeekDay, TheDate) BETWEEN 2 AND 6
See a live demo on rextester.
This will do it. Here the 1 and the 7 represents Sunday and Saturday
CREATE TABLE T (
ID INT NOT NULL IDENTITY(1,1),
MyDate DATE NOT NULL)
DECLARE #Start DATE
DECLARE #End DATE
SET #Start = '20170101'
SET #End = '20171231'
WHILE #Start <= #End
BEGIN
IF (DATEPART(DW, #Start) NOT IN (1,7))
BEGIN
INSERT INTO T (MyDate) VALUES (#Start)
END
SET #Start = DATEADD(DAY, 1, #Start)
END
SELECT * FROM T
Here's my quick attempt at your problem. Just use your table instead
select
CAST('2017-03-15' as datetime) as datestuff
into #test
Delete from #test
DECLARE #Y datetime = CAST('2017-12-31' AS DATE)
while #y != '2017-01-01'
begin
if DATENAME(DW, #y) not IN ('SUNDAY', 'SATURDAY')
BEGIN
INSERT INTO #test
SELECT #y
END
SET #Y = DATEADD(DD, -1, #Y)
end
select * from #test

Selecting all dates from a table within a date range and including 1 row per empty date

I am trying to refactor some code in an ASP.Net website and having a problem with a stored procedure I am writing.
What I want to do is get a date range, then select all data within that range from a table BUT if a date is not present I need to still select a row.
My idea for this as you can see in the code below is to create a temporary table, and populate it with all the dates within my date range, then join this onto the table I am selecting from however this does not work. Am I doing something wrong here? The tempDate column is always null in this join however I have checked the table and it deffinately has data in it.
-- Parameters
DECLARE #DutyDate datetime='2012-01-01 00:00:00'
DECLARE #InstructorID nvarchar(2) = N'29'
DECLARE #datesTBL TABLE (tempDate DATETIME)
-- Variables
DECLARE #StartDate DATETIME
DECLARE #EndDate DATETIME
SELECT
#StartDate =StartDate,
#EndDate = EndDate
FROM
DutyPeriodTbl
WHERE
(StartDate <= #DutyDate)
AND
(EndDate >= #DutyDate)
DECLARE #d DATETIME = #StartDate
WHILE #d<=#EndDate
BEGIN
INSERT INTO #datesTBL VALUES (CONVERT(DATETIME, #d, 102))
SET #d=DATEADD(day,1,#d)
END
SELECT
dt.tempDate ,
InstructorID, EventStart,
EventEnd, cancelled,
cancelledInstructor,
EventType, DevName,
Room, SimLocation,
ClassLocation, Event,
Duration, TrainingDesc,
Crew, Notes,
LastAmended, InstLastAmended,
ChangeAcknowledged, Type,
OtherType, OtherTypeDesc,
CourseType
FROM
OpsInstructorEventsView iv
LEFT OUTER JOIN
#datesTBL dt
ON
CONVERT(DATETIME, iv.EventStart, 102) = CONVERT(DATETIME, dt.tempDate, 102)
WHERE
InstructorID = #InstructorID
AND
EventStart BETWEEN CONVERT(DATETIME, #StartDate, 102) AND CONVERT(DATETIME, #EndDate, 102)
ORDER BY
EventStart
There are several ways of dealing with missing rows, but all are about having another set of data to combine with your current results.
That could be derived from your results, created by a CTE or other process (such as your example), or (my preference) by using a permanent template to join against.
The template in your case could just be a table of dates, like your #datesTBL. The difference being that it's created in advance with, for example, 100 years worth of dates.
Your query may then be similar to your example, but I would try the following...
SELECT
dt.tempDate ,
InstructorID, EventStart,
EventEnd, cancelled,
cancelledInstructor,
EventType, DevName,
Room, SimLocation,
ClassLocation, Event,
Duration, TrainingDesc,
Crew, Notes,
LastAmended, InstLastAmended,
ChangeAcknowledged, Type,
OtherType, OtherTypeDesc,
CourseType
FROM
#datesTBL dt
LEFT OUTER JOIN
OpsInstructorEventsView iv
ON iv.EventStart >= dt.tempDate
AND iv.EventStart < dt.tempDate + 1
AND iv.InstructorID = #InstructorID
WHERE
dt.tempDate >= #StartDate
AND dt.tempDate <= #EndDate
ORDER BY
dt.tempDate,
iv.EventStart
This puts the calendar template on the LEFT, and so makes many queries easier as you know the calendar's date field is always populated, is always a date only (no time part) value, is in order, is simple to GROUP BY, etc.
Well, idea is the same, but i would write function, that returns table with all dates in period. Look at this:
Create Function [dbo].[Interval]
(
#DateFrom Date,
#DateTo Date
)
Returns #tab Table
(
MyDate DateTime
)
As
Begin
Declare #Days int
Declare #i int
Set #Days = DateDiff(Day, #DateFrom, #DateTo)
Set #i = 0;
While (#Days > #i)
Begin
Insert Into #tab(MyDate)
Values (DateAdd(Day, #i, #DateTo))
Set #i = #i + 1
End
return
End
And reuse the function whenever you need it..
Select *
From [dbo].[Interval]('2011-01-01', GETDATE())

How can I generate a temporary table filled with dates in SQL Server 2000?

I need to make a temporary table that holds of range of dates, as well as a couple of columns that hold placeholder values (0) for future use. The dates I need are the first day of each month between $startDate and $endDate where these variables can be several years apart.
My original sql statement looked like this:
select dbo.FirstOfMonth(InsertDate) as Month, 0 as Trials, 0 as Sales
into #dates
from customer
group by dbo.FirstOfMonth(InsertDate)
"FirstOfMonth" is a user-defined function I made that pretty much does what it says, returning the first day of the month for the provided date with the time at exactly midnight.
This produced almost exactly what I needed until I discovered there were occasionally gaps in my dates where I had a few months were there were no records insert dates. Since my result must still have the missing months I need a different approach.
I have added the following declarations to the stored procedure anticipating their need for the range of the dates I need ...
declare $startDate set $startDate = select min(InsertDate) from customer
declare $endDate set $endDate = select max(InsertDate) from customer
... but I have no idea what to do from here.
I know this question is similar to this question but, quite frankly, that answer is over my head (I don't often work with SQL and when I do it tends to be on older versions of SQL Server) and there are a few minor differences that are throwing me off.
I needed something similar, but all DAYS instead of all MONTHS.
Using the code from MatBailie as a starting point, here's the SQL for creating a permanent table with all dates from 2000-01-01 to 2099-12-31:
CREATE TABLE _Dates (
d DATE,
PRIMARY KEY (d)
)
DECLARE #dIncr DATE = '2000-01-01'
DECLARE #dEnd DATE = '2100-01-01'
WHILE ( #dIncr < #dEnd )
BEGIN
INSERT INTO _Dates (d) VALUES( #dIncr )
SELECT #dIncr = DATEADD(DAY, 1, #dIncr )
END
This will quickly populate a table with 170 years worth of dates.
CREATE TABLE CalendarMonths (
date DATETIME,
PRIMARY KEY (date)
)
DECLARE
#basedate DATETIME,
#offset INT
SELECT
#basedate = '01 Jan 2000',
#offset = 1
WHILE (#offset < 2048)
BEGIN
INSERT INTO CalendarMonths SELECT DATEADD(MONTH, #offset, date) FROM CalendarMonths
SELECT #offset = #offset + #offset
END
You can then use it by LEFT joining on to that table, for the range of dates you require.
I would probably use a Calendar table. Create a permanent table in your database and fill it with all of the dates. Even if you covered a 100 year range, the table would still only have ~36,525 rows in it.
CREATE TABLE dbo.Calendar (
calendar_date DATETIME NOT NULL,
is_weekend BIT NOT NULL,
is_holiday BIT NOT NULL,
CONSTRAINT PK_Calendar PRIMARY KEY CLUSTERED (calendar_date)
)
Once the table is created, just populate it once in a loop, so that it's always out there and available to you.
Your query then could be something like this:
SELECT
C.calendar_date,
0 AS trials,
0 AS sales
FROM
dbo.Calendar C
WHERE
C.calendar_date BETWEEN #start_date AND #end_date AND
DAY(C.calendar_date) = 1
You can join in the Customers table however you need to, outer joining on FirstOfMonth(InsertDate) = C.calendar_date if that's what you want.
You can also include a column for day_of_month if you want which would avoid the overhead of calling the DAY() function, but that's fairly trivial, so it probably doesn't matter one way or another.
This of course will not work in SQL-Server 2000 but in a modern database where you don't want to create a permanent table. You can use a table variable instead creating a table so you can left join the data try this. Change the DAY to HOUR etc to change the increment type.
declare #CalendarMonths table (date DATETIME, PRIMARY KEY (date)
)
DECLARE
#basedate DATETIME,
#offset INT
SELECT
#basedate = '01 Jan 2014',
#offset = 1
INSERT INTO #CalendarMonths SELECT #basedate
WHILE ( DATEADD(DAY, #offset, #basedate) < CURRENT_TIMESTAMP)
BEGIN
INSERT INTO #CalendarMonths SELECT DATEADD(HOUR, #offset, date) FROM #CalendarMonths where DATEADD(DAY, #offset, date) < CURRENT_TIMESTAMP
SELECT #offset = #offset + #offset
END
A starting point of a useful kludge to specify a range or specific list of dates:
SELECT *
FROM
(SELECT CONVERT(DateTime,'2017-1-1')+number AS [Date]
FROM master..spt_values WHERE type='P' AND number<370) AS DatesList
WHERE DatesList.Date IN ('2017-1-1','2017-4-14','2017-4-17','2017-12-25','2017-12-26')
You can get 0 to 2047 out of master..spt_values WHERE type='P', so that's five and a half year's worth of dates if you need it!
Tested below and it works, though it's a bit convoluted.
I assigned arbitrary values to the dates for the test.
DECLARE #SD smalldatetime,
#ED smalldatetime,
#FD smalldatetime,
#LD smalldatetime,
#Mct int,
#currct int = 0
SET #SD = '1/15/2011'
SET #ED = '2/02/2012'
SET #FD = (DATEADD(dd, -1*(Datepart(dd, #SD)-1), #sd))
SET #LD = (DATEADD(dd, -1*(Datepart(dd, #ED)-1), #ED))
SET #Mct = DATEDIFF(mm, #FD, #LD)
CREATE TABLE #MyTempTable (FoM smalldatetime, Trials int, Sales money)
WHILE #currct <= #Mct
BEGIN
INSERT INTO #MyTempTable (FoM, Trials, Sales)
VALUES
(DATEADD(MM, #currct, #FD), 0, 0)
SET #currct = #currct + 1
END
SELECT * FROM #MyTempTable
DROP TABLE #MyTempTable
For SQL Server 2000, this stackoverflow post looks promising for a way to temporarily generate dates calculated off of a start and end date. It's not exactly the same but quite similar. This post has a very in-depth answer on truncating dates, if needed.
In case anyone stumbles on this question and is working in PostgreSQL instead of SQL Server 2000, here is how you might do it there...
PostgreSQL has a nifty series generating function. For your example, you could use this series of all days instead of generating an entire calendar table, and then do groupings and matchups from there.
SELECT current_date + s.a AS dates FROM generate_series(0,14,7) AS s(a);
dates
------------
2004-02-05
2004-02-12
2004-02-19
(3 rows)
SELECT * FROM generate_series('2008-03-01 00:00'::timestamp,
'2008-03-04 12:00', '10 hours');
generate_series
---------------------
2008-03-01 00:00:00
2008-03-01 10:00:00
2008-03-01 20:00:00
2008-03-02 06:00:00
2008-03-02 16:00:00
2008-03-03 02:00:00
2008-03-03 12:00:00
2008-03-03 22:00:00
2008-03-04 08:00:00
(9 rows)
I would also look into date_trunc from PostgreSQL using 'month' for the truncator field to maybe refactor your original query to easily match with a date_trunc version of the calendar series.
select top (datediff(D,#start,#end)) dateadd(D,id-1,#start)
from BIG_TABLE_WITH_NO_JUMPS_IN_ID
declare #start datetime
set #start = '2016-09-01'
declare #end datetime
set #end = '2016-09-30'
create table #Date
(
table_id int identity(1,1) NOT NULL,
counterDate datetime NULL
);
insert into #Date select top (datediff(D,#start,#end)) NULL from SOME_TABLE
update #Date set counterDate = dateadd(D,table_id - 1, #start)
The code above should populate the table with all the dates between the start and end. You would then just join on this table to get all of the dates needed. If you only needed a certain day of each month, you could dateadd a month instead.
SELECT P.Id
, DATEADD ( DD, -P.Id, P.Date ) AS Date
FROM (SELECT TOP 1000 ROW_NUMBER () OVER (ORDER BY (SELECT NULL)) AS Id, CAST(GETDATE () AS DATE) AS Date FROM master.dbo.spt_values) AS P
This query returns a table calendar for the last 1000 days or so. It can be put in a temporary or other table.
Create a table variable containing a date for each month in a year:
declare #months table (reportMonth date, PRIMARY KEY (reportMonth));
declare #start date = '2018', #month int = 0; -- base 0 month
while (#month < 12)
begin
insert into #months select dateAdd(month, #month, #start);
select #month = #month + 1;
end
--verify
select * from #months;
This is by far the quickest method I have found (much quicker than inserting rows 1 by 1 in a WHILE loop):
DECLARE #startDate DATE = '1900-01-01'
DECLARE #endDate DATE = '2050-01-01'
SELECT DATEADD(DAY, sequenceNumber, #startDate) AS TheDate
INTO #TheDates
FROM (
SELECT ones.n + 10*tens.n + 100*hundreds.n + 1000*thousands.n + 10000*tenthousands.n AS sequenceNumber
FROM
(VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) ones(n),
(VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) tens(n),
(VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) hundreds(n),
(VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) thousands(n),
(VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) tenthousands(n)
WHERE ones.n + 10*tens.n + 100*hundreds.n + 1000*thousands.n + 10000*tenthousands.n <= DATEDIFF(day, #startDate, #endDate)
) theNumbers
SELECT *
FROM #TheDates
ORDER BY TheDate
The recursive answer:
DECLARE #startDate AS date = '20220315';
DECLARE #endDate AS date = '20230316'; -- inclusive
WITH cte_minutes(dt)
AS (
SELECT
DATEFROMPARTS(YEAR(#startDate), MONTH(#startDate), 1)
UNION ALL
SELECT
DATEADD(month, 1, dt)
FROM
cte_minutes
WHERE DATEADD(month, 1, dt) < #endDate
)
SELECT
dt
into #dates
FROM
cte_minutes
WHERE
dt >= #startDate
AND
dt <= #endDate
OPTION (MAXRECURSION 2000);
DROP TABLE dbo.#dates

Grouping by contiguous dates, ignoring weekends in SQL

I'm attempting to group contiguous date ranges to show the minimum and maximum date for each range. So far I've used a solution similar to this one: http://www.sqlservercentral.com/articles/T-SQL/71550/ however I'm on SQL 2000 so I had to make some changes. This is my procedure so far:
create table #tmp
(
date smalldatetime,
rownum int identity
)
insert into #tmp
select distinct date from testDates order by date
select
min(date) as dateRangeStart,
max(date) as dateRangeEnd,
count(*) as dates,
dateadd(dd,-1*rownum, date) as GroupID
from #tmp
group by dateadd(dd,-1*rownum, date)
drop table #tmp
It works exactly how I want except for one issue: weekends. My data sets have no records for weekend dates, which means any group found is at most 5 days. For instance, in the results below, I would like the last 3 groups to show up as a single record, with a dateRangeStart of 10/6 and a dateRangeEnd of 10/20:
Is there some way I can set this up to ignore a break in the date range if that break is just a weekend?
Thanks for the help.
EDITED
I didn't like my previous idea very much. Here's a better one, I think:
Based on the first and the last dates from the set of those to be grouped, prepare the list of all the intermediate weekend dates.
Insert the working dates together with weekend dates, ordered, so they would all be assigned rownum values according to their normal order.
Use your method of finding contiguous ranges with the following modifications:
1) when calculating dateRangeStart, if it's a weekend date, pick the nearest following weekday;
2) accordingly for dateRangeEnd, if it's a weekend date, pick the nearest preceding weekday;
3) when counting dates for the group, pick only weekdays.
Select from the resulting set only those rows where dates > 0, thus eliminating the groups formed only of the weekends.
And here's an implementation of the method, where it is assumed, that a week starts on Sunday (DATEPART returns 1) and weekend days are Sunday and Saturday:
DECLARE #tmp TABLE (date smalldatetime, rownum int IDENTITY);
DECLARE #weekends TABLE (date smalldatetime);
DECLARE #minDate smalldatetime, #maxDate smalldatetime, #date smalldatetime;
/* #1 */
SELECT #minDate = MIN(date), #maxDate = MAX(date)
FROM testDates;
SET #date = #minDate - DATEPART(dw, #minDate) + 7;
WHILE #date < #maxDate BEGIN
INSERT INTO #weekends
SELECT #date UNION ALL
SELECT #date + 1;
SET #date = #date + 7;
END;
/* #2 */
INSERT INTO #tmp
SELECT date FROM testDates
UNION
SELECT date FROM #weekends
ORDER BY date;
/* #3 & #4 */
SELECT *
FROM (
SELECT
MIN(date + CASE DATEPART(dw, date) WHEN 1 THEN 1 WHEN 7 THEN 2 ELSE 0 END)
AS dateRangeStart,
MAX(date - CASE DATEPART(dw, date) WHEN 1 THEN 2 WHEN 7 THEN 1 ELSE 0 END)
AS dateRangeEnd,
COUNT(CASE WHEN DATEPART(dw, date) NOT IN (1, 7) THEN date END) AS dates,
DATEADD(d, -rownum, date) AS GroupID
FROM #tmp
GROUP BY DATEADD(d, -rownum, date)
) s
WHERE dates > 0;

What is a good way to find gaps in a set of datespans?

What is a way to find gaps in a set of date spans?
For example, I have these date spans:
1/ 1/11 - 1/10/11
1/13/11 - 1/15/11
1/20/11 - 1/30/11
Then I have a start and end date of 1/7/11 and 1/14/11.
I want to be able to tell that between 1/10/11 and 1/13/11 there is a gap so the start and end date is not possible. Or I want to return only the datespans up to the first gap encountered.
If this can be done in SQL server that would be good.
I was thinking to go through each date to find out if it lands in a datespan... if it does not then there's a gap on that day.
Jump to 2nd last code block for: *I want to be able to tell that
between 1/10/11 and 1/13/11 there is
a gap so the start and end date is*
not possible.
Jump to last code block for: *I want to return only
the datespans up to the first gap
encountered.*
First of all, here's a virtual table to discuss
create table spans (date1 datetime, date2 datetime);
insert into spans select '20110101', '20110110';
insert into spans select '20110113', '20110115';
insert into spans select '20110120', '20110130';
This is a query that will list, individually, all the dates in the calendar
declare #startdate datetime, #enddate datetime
select #startdate = '20110107', #enddate = '20110114'
select distinct a.date1+v.number
from spans A
inner join master..spt_values v
on v.type='P' and v.number between 0 and datediff(d, a.date1, a.date2)
-- we don't care about spans that don't intersect with our range
where A.date1 <= #enddate
and #startdate <= A.date2
Armed with this query, we can now test to see if there are any gaps, by
counting the days in the calendar against the expected number of days
declare #startdate datetime, #enddate datetime
select #startdate = '20110107', #enddate = '20110114'
select case when count(distinct a.date1+v.number)
= datediff(d,#startdate, #enddate) + 1
then 'No gaps' else 'Gap' end
from spans A
inner join master..spt_values v
on v.type='P' and v.number between 0 and datediff(d, a.date1, a.date2)
-- we don't care about spans that don't intersect with our range
where A.date1 <= #enddate
and #startdate <= A.date2
-- count only those dates within our range
and a.date1 + v.number between #startdate and #enddate
Another way to do this is to just build the calendar from #start
to #end up front and look to see if there is a span with this date
declare #startdate datetime, #enddate datetime
select #startdate = '20110107', #enddate = '20110114'
-- startdate+v.number is a day on the calendar
select #startdate + v.number
from master..spt_values v
where v.type='P' and v.number between 0
and datediff(d, #startdate, #enddate)
-- run the part above this line alone to see the calendar
-- the condition checks for dates that are not in any span (gap)
and not exists (
select *
from spans
where #startdate + v.number between date1 and date2)
The query returns ALL dates that are gaps in the date range #start - #end
A TOP 1 can be added to just see if there are gaps
To return all records that are before the gap, use the query as a
derived table in a larger query
declare #startdate datetime, #enddate datetime
select #startdate = '20110107', #enddate = '20110114'
select *
from spans
where date1 <= #enddate and #startdate <= date2 -- overlaps
and date2 < ( -- before the gap
select top 1 #startdate + v.number
from master..spt_values v
where v.type='P' and v.number between 0
and datediff(d, #startdate, #enddate)
and not exists (
select *
from spans
where #startdate + v.number between date1 and date2)
order by 1 ASC
)
Assuming MySQL, something like this would work:
select #olddate := null;
select start_date, end_date, datediff(end_date, #olddate) as diff, #olddate:=enddate
from table
order by start_date asc, end_date asc
having diff > 1;
Basically: cache the previous row's end_date in the #olddate variable, and then do a diff on that "old" value with the currel enddate. THe having clause will return only the records where the difference between two rows is greater than a day.
disclaimer: Haven't tested this, but the basic query construct should work.
I want to be able to tell that between
1/10/11 and 1/13/11 there is a gap so
the start and end date is not
possible.
I think you're asking this question: does the data in your table have a gap between the start date and the end date?
I created a one-column table, date_span, and inserted your date spans into it.
You can identify a gap by counting the number of days between start date and end date, and comparing that the the number of rows in date_span for the same range.
select
date '2011-01-14' - date '2011-01-07' + 1 as elapsed_days,
count(*) from date_span
where cal_date between '2011-01-07' and '2011-01-14';
returns
elapsed_days count
-- --
8 6
Since they're not equal, there's a gap in the table "date_span" between 2011-01-07 and 2011-01-14. I'll stop there for now, because I'm really not certain what you're trying to do.