Find available openings using SQL - sql

I have a table of appointments with records having two fields - start_date and end_date, both datetime. There is no overlap of time periods in the table.
Given a specific period (search_start and search_end), I need to generate a list of all openings between those appointments (from and to) using SQL.
For example: given two appointments in the table:
September 15, 2016 08:00 to September 15, 2016 09:00
September 15, 2016 10:00 to September 15, 2016 12:00
And given search parameters start= September 1, 2016 00:00 and end= September 30, 2016 23:59, the results should be
September 1, 2016 00:00 to September 15, 2016 08:00
September 15, 2016 09:00 to September 15, 2016 10:00
September 15, 2016 12:00 to September 30, 2016 23:59
Here is a script to generate a sample table:
CREATE TABLE [dbo].[Table_1](
[from_date] [datetime] NOT NULL,
[to_date] [datetime] NULL,
CONSTRAINT [PK_Table_1] PRIMARY KEY CLUSTERED ( [from_date] ASC )
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
INSERT [dbo].[Table_1] ([from_date], [to_date]) VALUES (CAST(0x0000A6820083D600 AS DateTime), CAST(0x0000A682009450C0 AS DateTime))
INSERT [dbo].[Table_1] ([from_date], [to_date]) VALUES (CAST(0x0000A68200A4CB80 AS DateTime), CAST(0x0000A68200C5C100 AS DateTime))
I am using MSSQL 2008 R2

Using your values I got the output you wanted : )
DECLARE #start datetime = '2016-09-01 00:00:00'
DECLARE #finish datetime = '2016-09-30 23:59:00'
WITH rn AS (SELECT *, ROW_NUMBER() OVER (ORDER BY start) AS rn FROM opening)
SELECT CASE WHEN r1.rn = 1 THEN #start
ELSE r2.finish
END as START,
CASE WHEN r1.rn IS NULL THEN #finish
ELSE r1.start
END AS FINISH
FROM rn r1
FULL OUTER JOIN rn r2
ON r1.rn = r2.rn + 1
WHERE ISNULL(r1.start, 0) != #start
opening is your schedule/appointment table. start is the starting date in your table and finish is the end date in your table. #start is the starting date and #finish is the end date. You obvious don't need to use #start, #finish. I just put it there for testing.

SELECT Start_Date, End_Date
FROM TableName
BETWEEN Start_Date AND End_Date
This question seems easy?? I am not sure, if I have oversimplified your question?
You just have to make sure, you add the time bit as well!

I am thinking like Mfredy on this question. But I will give my standard spiel on what to look out for on data time calculations.
Here are some common issues.
Some (like Mysql) sql engines store the data in GMT (some time zone in england). Then they calculate the number of hours off of GMT to get the actual time. So if you are 8 hours from GMT you may be 8 hours off if you used a MsSql client connecting to a MySql database. Because MsSql does not understand this 8 hour delta for GMT used by MySql. You can also see this in applications and operating systems.
The other issue is you are thinking in dates you have to make sure you are thinking in Date and Time unless you converted the date time to a date.
Watch out for < vs <=. If you are filtering for the end date. Say for example Last day in january is to be included but first day of feb is not. It is better to do < 02/01 that it is to do <= 1/31. You may chop off records if you compare using <= and don't go down to the last millisecond before 02/01.

I've had a go trying to re-create your scenario, and then solve it. In some places I've simplified things for the sake of the example, but you should be able to extrapolate it.
I'm sure there are also far more elegant ways, but is a starting point, and I quite enjoyed figuring it out.
The approach I wanted to take was build a list of the available 'time slots'
First, I created a table with the available Hours for bookings
CREATE TABLE [dbo].[HOURS](
HourID [int] NULL)
INSERT INTO dbo.[hours] VALUES (9),(10),(11),(12),(13),(14),(15),(16),(17)
Then did the same for the minute intervals (I went with 5 min intervals for simplicity)
CREATE TABLE [dbo].MINS(
MinID [int] NULL )
INSERT INTO dbo.mins VALUES (5),(10),(15),(20),(25),(30),(35),(40),(45),(50),(55),(60)
and same again for dates I wanted to work with
CREATE TABLE [dbo].DATES(
DATES [Date] NULL)
INSERT INTO dbo.DATES values
('20160901'),('20160902')
Using these tables, I created a view listing all available 'slots'
CREATE VIEW AllTimeSlots AS
SELECT
cast(dates AS datetime) + cast(DATEADD(hour, hourid, DATEADD(minute, minid, DATEADD(second, 00, 0))) AS datetime) AS TimeSlot
FROM dbo.[hours] CROSS JOIN dbo.mins CROSS JOIN dates
I then created a table containing the appointments
CREATE TABLE [dbo].Appointments(
AppointmentStart [Datetime] NULL,
AppointmentEnd [DateTime] null
)
INSERT INTO dbo.Appointments
VALUES
('20160901 10:40:00 AM','20160901 10:55 AM'),('20160901 01:00:00 PM','20160901 02:05:00 PM')
Then, I created another view with married up the slots and the bookings. Take note of the joins I'm using to block out all slots between the two times
CREATE VIEW SlotAvailability
AS
SELECT TimeSlot,CASE WHEN AppointmentStart IS NULL THEN 'Free' ELSE 'Booked' END AS [Availability]
FROM (
SELECT Timeslot,apt.AppointmentStart, apt.AppointmentEnd FROM
dbo.AllTimeSlots ats
LEFT OUTER JOIN dbo.Appointments apt
on ats.TimeSlot >= appointmentstart and ats.timeslot <= appointmentend) q1
Doing
Select Timeslot, [availability] from SlotAvailability where [availability] = 'free'
will list all the available time slots.
The last bit, which I haven't quite got to (and ran out of time for now) is then converting this to a start-end time for each 'free' slot - tried a couple of methods, but didn't crack it - I think if you join that table (view) to itself of timeslot+5mins, you should be able to then min/max values based on whether it's the start / end of a free block

The way I normally address this today is to use the APPLY operator to join the table to itself. There's not enough info in the question to craft a complete query for you, but here's the general pattern:
SELECT *
FROM MyTable t1
OUTER APPLY (
SELECT TOP 1 *
FROM MyTable t
WHERE t.KeyFields = t1.KeyFields
AND t.SequenceField > t1.SequenceField
) t2
Now I can compare each row directly with the one the follows in my WHERE clause, such that the WHERE clause the filter it down to only show rows where there is a gap.
You can also sometimes accomplish this via the LAG or LEAD Windowing Functions.

Related

Adding minutes of runtime from on/off records during a time period

I have a SQL database that collects temperature and sensor data from the barn.
The table definition is:
CREATE TABLE [dbo].[DataPoints]
(
[timestamp] [datetime] NOT NULL,
[pointname] [nvarchar](50) NOT NULL,
[pointvalue] [float] NOT NULL
)
The sensors report outside temperature (degrees), inside temperature (degrees), and heating (as on/off).
Sensors create a record when the previous reading has changed, so temperatures are generated every few minutes, one record for heat coming ON, one for heat going OFF, and so on.
I'm interested in how many minutes of heat has been used overnight, so a 24-hour period from 6 AM yesterday to 6 AM today would work fine.
This query:
SELECT *
FROM [home_network].[dbo].[DataPoints]
WHERE (pointname = 'Heaters')
AND (timestamp BETWEEN '2022-12-18 06:00:00' AND '2022-12-19 06:00:00')
ORDER BY timestamp
returns this data:
2022-12-19 02:00:20 | Heaters | 1
2022-12-19 02:22:22 | Heaters | 0
2022-12-19 03:43:28 | Heaters | 1
2022-12-19 04:25:31 | Heaters | 0
The end result should be 22 minutes + 42 minutes = 64 minutes of heat, but I can't see how to get this result from a single query. It also just happens that this result set has two complete heat on/off cycles, but that will not always be the case. So, if the first heat record was = 0, that means that at 6 AM, the heat was already on, but the start time won't show in the query. The same idea applies if the last heat record is =1 at, say 05:15, which means 45 minutes have to be added to the total.
Is it possible to get this minutes-of-heat-time result with a single query? Actually, I don't know the right approach, and it doesn't matter if I have to run several queries. If needed, I could use a small app that reads the raw data, and applies logic outside of SQL to arrive at the total. But I'd prefer to be able to do this within SQL.
This isn't a complete answer, but it should help you get started. From the SQL in the post, I'm assuming you're using SQL Server. I've formatted the code to match. Replace #input with your query above if you want to test on your own data. (SELECT * FROM [home_network].[dbo]...)
--generate dummy table with sample output from question
declare #input as table(
[timestamp] [datetime] NOT NULL,
[pointname] [nvarchar](50) NOT NULL,
[pointvalue] [float] NOT NULL
)
insert into #input values
('2022-12-19 02:00:20','Heaters',1),
('2022-12-19 02:22:22','Heaters',0),
('2022-12-19 03:43:28','Heaters',1),
('2022-12-19 04:25:31','Heaters',0);
--Append a row number to the result
WITH A as (
SELECT *,
ROW_NUMBER() OVER(ORDER BY(SELECT 1)) as row_count
from #input)
--Self join the table using the row number as a guide
SELECT sum(datediff(MINUTE,startTimes.timestamp,endTimes.timestamp))
from A as startTimes
LEFT JOIN A as endTimes on startTimes.row_count=endTimes.row_count-1
--Only show periods of time where the heater is turned on at the start
WHERE startTimes.row_count%2=1
Your problem can be divided into 2 steps:
Filter sensor type and date range, while also getting time span of each record by calculating date difference between timestamp of current record and the next one in chronological order.
Filter records with ON status and summarize the duration
(Optional) convert to HH:MM:SS format to display
Here's the my take on the problem with comments of what I do in each step, all combined into 1 single query.
-- Step 3: Convert output to HH:MM:SS, this is just for show and can be reduced
SELECT STUFF(CONVERT(VARCHAR(8), DATEADD(SECOND, total_duration, 0), 108),
1, 2, CAST(FLOOR(total_duration / 3600) AS VARCHAR(5)))
FROM (
-- Step 2: select records with status ON (1) and aggregate total duration in seconds
SELECT sum(duration) as total_duration
FROM (
-- Step 1: Use LEAD to get next adjacent timestamp and calculate date difference (time span) between the current record and the next one in time order
SELECT TOP 100 PERCENT
DATEDIFF(SECOND, timestamp, LEAD(timestamp, 1, '2022-12-19 06:00:00') OVER (ORDER BY timestamp)) as duration,
pointvalue
FROM [dbo].[DataPoints]
-- filtered by sensor name and time range
WHERE pointname = 'Heaters'
AND (timestamp BETWEEN '2022-12-18 06:00:00' AND '2022-12-19 06:00:00')
ORDER BY timestamp ASC
) AS tmp
WHERE tmp.pointvalue = 1
) as tmp2
Note: As the last record does not have next adjacent timestamp, it will be filled with the end time of inspection (In this case it's 6AM of the next day).
I do not really think it would be possible to achieve within single query.
Option 1:
implement stored procedure where you can implement some logic how to calculate these periods.
Option 2:
add new column (duration) and on insert new record calculate difference between NOW and previous timestamp and update duration for previous record

SQL Server : average count of alerts per day, not including days with no alerts

I have a table that acts as a message log, with the two key tables being TIMESTAMP and TEXT. I'm working on a query that grabs all alerts (from TEXT) for the past 30 days (based on TIMESTAMP) and gives a daily average for those alerts.
Here is the query so far:
--goback 30 days start at midnight
declare #olderdate as datetime
set #olderdate = DATEADD(Day, -30, DATEDIFF(Day, 0, GetDate()))
--today at 11:59pm
declare #today as datetime
set #today = dateadd(ms, -3, (dateadd(day, +1, convert(varchar, GETDATE(), 101))))
print #today
--Grab average alerts per day over 30 days
select
avg(x.Alerts * 1.0 / 30)
from
(select count(*) as Alerts
from MESSAGE_LOG
where text like 'The process%'
and text like '%has alerted%'
and TIMESTAMP between #olderdate and #today) X
However, I want to add something that checks whether there were any alerts for a day and, if there are no alerts for that day, doesn't include it in the average. For example, if there are 90 alerts for a month but they're all in one day, I wouldn't want the average to be 3 alerts per day since that's clearly misleading.
Is there a way I can incorporate this into my query? I've searched for other solutions to this but haven't been able to get any to work.
This isn't written for your query, as I don't have any DDL or sample data, thus I'm going to provide a very simple example instead of how you would do this.
USE Sandbox;
GO
CREATE TABLE dbo.AlertMessage (ID int IDENTITY(1,1),
AlertDate date);
INSERT INTO dbo.AlertMessage (AlertDate)
VALUES('20190101'),('20190101'),('20190105'),('20190110'),('20190115'),('20190115'),('20190115');
GO
--Use a CTE to count per day:
WITH Tots AS (
SELECT AlertDate,
COUNT(ID) AS Alerts
FROM dbo.AlertMessage
GROUP BY AlertDate)
--Now the average
SELECT AVG(Alerts*1.0) AS DayAverage
FROM Tots;
GO
--Clean up
DROP TABLE dbo.AlertMessage;
You're trying to compute a double-aggregate: The average of daily totals.
Without using a CTE, you can try this as well, which is generalized a bit more to work for multiple months.
--get a list of events per day
DECLARE #Event TABLE
(
ID INT NOT NULL IDENTITY(1, 1)
,DateLocalTz DATE NOT NULL--make sure to handle time zones
,YearLocalTz AS DATEPART(YEAR, DateLocalTz) PERSISTED
,MonthLocalTz AS DATEPART(MONTH, DateLocalTz) PERSISTED
)
/*
INSERT INTO #Event(EntryDateLocalTz)
SELECT DISTINCT CONVERT(DATE, TIMESTAMP)--presumed to be in your local time zone because you did not specify
FROM dbo.MESSAGE_LOG
WHERE UPPER([TEXT]) LIKE 'THE PROCESS%' AND UPPER([TEXT]) LIKE '%HAS ALERTED%'--case insenitive
*/
INSERT INTO #Event(DateLocalTz)
VALUES ('2018-12-31'), ('2019-01-01'), ('2019-01-01'), ('2019-01-01'), ('2019-01-12'), ('2019-01-13')
--get average number of alerts per alerting day each month
-- (this will not return months with no alerts,
-- use a LEFT OUTER JOIN against a month list table if you need to include uneventful months)
SELECT
YearLocalTz
,MonthLocalTz
,AvgAlertsOfAlertingDays = AVG(CONVERT(REAL, NumDailyAlerts))
FROM
(
SELECT
YearLocalTz
,MonthLocalTz
,DateLocalTz
,NumDailyAlerts = COUNT(*)
FROM #Event
GROUP BY YearLocalTz, MonthLocalTz, DateLocalTz
) AS X
GROUP BY YearLocalTz, MonthLocalTz
ORDER BY YearLocalTz ASC, MonthLocalTz ASC
Some things to note in my code:
I use PERSISTED columns to get the month and year date parts (because I'm lazy when populating tables)
Use explicit CONVERT to escape integer math that rounds down decimals. Multiplying by 1.0 is a less-readable hack.
Use CONVERT(DATE, ...) to round down to midnight instead of converting back and forth between strings
Do case-insensitive string searching by making everything uppercase (or lowercase, your preference)
Don't subtract 3 milliseconds to get the very last moment before midnight. Change your semantics to interpret the end of a time range as exclusive, instead of dealing with the precision of your datatypes. The only difference is using explicit comparators (i.e. use < instead of <=). Also, DATETIME resolution is 1/300th of a second, not 3 milliseconds.
Avoid using built-in keywords as column names (i.e. "TEXT"). If you do, wrap them in square brackets to avoid ambiguity.
Instead of dividing by 30 to get the average, divide by the count of distinct days in your results.
select
avg(x.Alerts * 1.0 / x.dd)
from
(select count(*) as Alerts, count(distinct CAST([TIMESTAMP] AS date)) AS dd
...

Generate Dates starting from a date returned by a condition - SQL

A series of dates with a specified interval can be generated using a variable and a static date as per the linked question that I asked earlier. However when there's a where clause to produce a start date, the dates generation seems to stop and only shows the first interval date. I also checked other posts, those that I found e.g. 1, e.g. 2, e.g. 3 are shown with a static date or using CTE.. I am looking for a solution without storedprocedures/functions...
This works:
SELECT DATE(DATE_ADD('2012-01-12',
INTERVAL #i:=#i+30 DAY) ) AS dateO
FROM members, (SELECT #i:=0) r
where #i < DATEDIFF(now(), date '2012-01-12')
;
These don't:
SELECT DATE_ADD(date '2012-01-12',
INTERVAL #j:=#j+30 DAY) AS dateO, #j
FROM `members`, (SELECT #j:=0) s
where #j <= DATEDIFF(now(), date '2012-01-12')
and mmid = 100
;
SELECT DATE_ADD(stdate,
INTERVAL #k:=#k+30 DAY) AS dateO, #k
FROM `members`, (SELECT #k:=0) t
where #k <= DATEDIFF(now(), stdate)
and mmid = 100
;
SQLFIDDLE REFERENCE
Expected Results:
Be the same as the first query results given it starts generating dates with stDate of mmid=100.
Preferably in ANSI SQL so it can be supported in MYSQL, SQL Server/MS Access SQL as Oracle has trunc and rownum given per this query with 14 votes and PostGres has generatge_Series function. I would like to know if this is a bug or a limitation in MYSQL?
PS: I have asked a similar quetion before. It was based on static date values where as this one is based on a date value from a table column based on a condition.
The simplest way to insure cross-platform compatibility is to use a calendar table. In its simplest form
create table calendar (
cal_date date primary key
);
insert into calendar values
('2013-01-01'),
('2013-01-02'); -- etc.
There are many ways to generate dates for insertion.
Instead of using a WHERE clause to generate rows, you use a WHERE clause to select rows. To select October of this year, just
select cal_date
from calendar
where cal_date between '2013-10-01' and '2013-10-31';
It's reasonably compact--365,000 rows to cover a period of 1000 years. That ought to cover most business scenarios.
If you need cross-platform date arithmetic, you can add a tally column.
drop table calendar;
create table calendar (
cal_date date primary key,
tally integer not null unique check (tally > 0)
);
insert into calendar values ('2012-01-01', 1); -- etc.
To select all the dates of 30-day intervals, starting on 2012-01-12 and ending at the end of the calendar year, use
select cal_date
from calendar
where ((tally - (select tally
from calendar
where cal_date = '2012-01-12')) % 30 ) = 0;
cal_date
--
2012-01-12
2012-02-11
2012-03-12
2012-04-11
2012-05-11
2012-06-10
2012-07-10
2012-08-09
2012-09-08
2012-10-08
2012-11-07
2012-12-07
If your "mmid" column is guaranteed to have no gaps--an unspoken requirement for a calendar table--you can use the "mmid" column in place of my "tally" column.

Find rows in a database with no time in a datetime column

During testing I have failed to notice an incorrect date/time entry into the database on certain orders. Instead of entering the date and time I have only been entering the date. I was using the correct time stamp createodbcdatetime(now()) however I was using cfsqltype="cf_sql_date" to enter it into the database.
I am lucky enough to have the order date/time correctly recorded, meaning I can use the time from the order date/time field.
My question being can I filter for all rows in the table with only dates entered. My data below;
Table Name: tbl_orders
uid_orders dte_order_stamp
2000 02/07/2012 03:02:52
2001 03/07/2012 01:24:21
2002 03/07/2012 08:34:00
Table Name: tbl_payments
uid_payment dte_pay_paydate uid_pay_orderid
1234 02/07/2012 03:02:52 2000
1235 03/07/2012 2001
1236 03/07/2012 2002
I need to be able to select all payments with no time entered from tbl_payments, i can then loop around the results grabbing the time from my order table add it to the date from my payment table and update the field with the new date/time.
I can pretty much handle the re-inserting the date/time. It's just selecting the no time rows I'm not sure about?
Any help would be appreciated.
The following is the select statements for both orders and payments and if they need to be joined.(just fyi)
SQL Server 2008, Cold Fusion 9
SELECT
dbo.tbl_orders.uid_orders,
dbo.tbl_orders.dte_order_stamp,
dbo.tbl_payment.dte_pay_paydate,
dbo.tbl_payment.uid_pay_orderid
FROM
dbo.tbl_orders
INNER JOIN dbo.tbl_payment ON (dbo.tbl_orders.uid_orders = dbo.tbl_payment.uid_pay_orderid)
SELECT
dbo.tbl_orders.uid_orders,
dbo.tbl_orders.dte_order_stamp
FROM dbo.tbl_orders
SELECT
uid_paymentid,
uid_pay_orderid,
dte_pay_paydate,
FROM
dbo.tbl_payment
Select the records where the hours, minutes, seconds and millisecond value is zero.
select *
from table
where datePart(hour, datecolumn) = 0
and datePart(minute, datecolumn) = 0
and datePart(second, datecolumn) = 0
and datePart(millisecond, datecolumn) = 0
You can probably get those values by casting to time and checking for 0:
SELECT * FROM table WHERE CAST(datetimecolumn AS TIME) = '00:00'
That may not be particularly efficient though, depending on how smart SQL Server's indexes are.
Something like this should work:
....
WHERE CAST(CONVERT(VARCHAR, dbo.tbl_payment.dte_pay_paydate, 101) AS DATETIME) =
dbo.tbl_payment.dte_pay_paydate
This will return all rows where the time is missing.

How to store absolute and relative date ranges in SQL database?

I'm trying to model a DateRange concept for a reporting application. Some date ranges need to be absolute, March 1, 2011 - March 31, 2011. Others are relative to current date, Last 30 Days, Next Week, etc. What's the best way to store that data in SQL table?
Obviously for absolute ranges, I can have a BeginDate and EndDate. For relative ranges, having an InceptionDate and an integer RelativeDays column makes sense. How do I incorporate both of these ideas into a single table without implementing context into it, ie have all four columns mentioned and use XOR logic to populate 2 of the 4.
Two possible schemas I rejected due to having context-driven columns:
CREATE TABLE DateRange
(
BeginDate DATETIME NULL,
EndDate DATETIME NULL,
InceptionDate DATETIME NULL,
RelativeDays INT NULL
)
OR
CREATE TABLE DateRange
(
InceptionDate DATETIME NULL,
BeginDaysRelative INT NULL,
EndDaysRelative INT NULL
)
Thanks for any advice!
I don't see why your second design doesn't meet your needs unless you're in the "no NULLs never" camp. Just leave InceptionDate NULL for "relative to current date" choices so that your application can tell them apart from fixed date ranges.
(Note: not knowing your DB engine, I've left date math and current date issues in pseudocode. Also, as in your question, I've left out any text description and primary key columns).
Then, either create a view like this:
CREATE VIEW DateRangesSolved (Inception, BeginDays, EndDays) AS
SELECT CASE WHEN Inception IS NULL THEN Date() ELSE Inception END,
BeginDays,
EndDays,
FROM DateRanges
or just use that logic when you SELECT from the table directly.
You can even take it one step further:
CREATE VIEW DateRangesSolved (BeginDate, EndDate) AS
SELECT (CASE WHEN Inception IS NULL THEN Date() ELSE Inception END + BeginDays),
(CASE WHEN Inception IS NULL THEN Date() ELSE Inception END + EndDays)
FROM DateRanges
Others are relative to current date, Last 30 Days, Next Week, etc.
What's the best way to store that data in SQL table?
If you store those ranges in a table, you have to update them every day. In this case, you have to update each row differently every day. That might be a big problem; it might not.
There usually aren't many rows in that kind of table, often less than 50. The table structure is obvious. Updating should be driven by a cron job (or its equivalent), and you should run very picky exception reports every day to make sure things have been updated correctly.
Normally, these kinds of reports should produce no output if things are fine. You have the added complication that driving such a report from cron will produce no output if cron isn't running. And that's not fine.
You can also create a view, which doesn't require any maintenance. With a few dozen rows, it might be slower than a physical table, but it might still fast enough. And it eliminates all maintenance and administrative work for these ranges. (Check for off-by-one errors, because I didn't.)
create view relative_date_ranges as
select 'Last 30 days' as range_name,
(current_date - interval '30' day)::date as range_start,
current_date as range_end
union all
select 'Last week' as range_name,
(current_date - interval '7' day)::date as range_start,
current_date as range_end
union all
select 'Next week' as range_name,
(current_date + interval '7' day)::date as range_start,
current_date as range_end
Depending on the app, you might be able to treat your "absolute" ranges the same way.
...
union all
select 'March this year' as range_name,
(extract(year from current_date) || '-03-01')::date as range_start,
(extract(year from current_date) || '-03-31')::date as range_end
Put them in separate tables. There is absolutely no reason to have them in a single table.
For the relative dates, I would go so far as to simply make the table the parameters you need for the date functions, i.e.
CREATE TABLE RelativeDate
(
Id INT Identity,
Date_Part varchar(25),
DatePart_Count int
)
Then you can know that it is -2 WEEK or 30 DAY variance and use that in your logic.
If you need to see them both at the same time, you can combine them LOGICALLY in a query or view without needing to mess up your data structure by cramming different data elements into the same table.
Create a table containing the begindate and the offset. The precision of the offset is up to you to decide.
CREATE TABLE DateRange(
BeginDate DATETIME NOT NULL,
Offset int NOT NULL,
OffsetLabel varchar(100)
)
to insert into it:
INSERT INTO DateRange (BeginDate, Offset, OffsetLabel)
select '20110301', DATEDIFF(sec, '20110301', '20110331'), 'March 1, 2011 - March 31, 2011'
Last 30 days
INSERT INTO DateRange (BeginDate, Duration, OffsetLabel)
select '20110301', DATEDIFF(sec, current_timestamp, DATEADD(day, -30, current_timestamp)), 'Last 30 Days'
To display the values later:
select BeginDate, EndDate = DATEADD(sec, Offset, BeginDate), OffsetLabel
from DateRange
If you want to be able to parse the "original" vague descriptions you will have to look for a "Fuzzy Date" or "Approxidate" function. (There exists something like this in the git source code. )