SQL, Selecting between date/times - sql

This is for (MS SQL Server 2008) It allows the user to schedule other users for a start and end date/time without overlaps.
I'm addressing the business rule of not being able to overlap individuals during a schedule. E.G, If user 1 is scheduled from 1/1/2012 to 1/4/2012, user 2 should not be able to submit a request for 1/2/2012 to 1/4/2012. This part is already taken care of, and here is my SQL.
SELECT * FROM fooTable
WHERE
Prim = 1
AND
Group = 10
AND
(('2012-06-01 08:01' between startdate and enddate
OR '2012-06-03 08:01' between startdate and enddate)
OR
('2012-06-01 08:01' < startdate) AND ('2012-06-03 8:01' > enddate))
I now have a requirement to allow a schedule to overlap in the sense that the new shift can begin as the last one ends. E.G, My end date is 1/1/2012 at 8:00pm - I should be able to schedule someone to start at 1/1/2012 and 8:00pm.
Need some help thinking this one through.

First off, don't use Group as a column name. It is a reserved word in every SQL standard. I renamed it to grp for the purpose of my answer.
Trying to schedule a new shift from '2012-06-01 08:00' to '2012-06-03 08:00' ...
INSERT INTO tbl (prim, grp, startdate, enddate)
SELECT 1, 10, '2012-06-01 08:00', '2012-06-03 08:00'
WHERE NOT EXISTS (
SELECT *
FROM tbl
WHERE prim = 1
AND grp = 10
AND '2012-06-03 08:00' > startdate -- not >= to allow sharing a border
AND '2012-06-01 08:00' < enddate -- and not BETWEEN ... AND either
)
Note that I compare:
new_end &gt old_start
new_start &lt old_end
If you use BETWEEN .. AND .. you include the borders of a shift in your test. It's the same as using >= and <=. You need to use > and < to allow borders to overlap.
Well, and try my largely simplified syntax. Not sure about what you had there originally.
Here is a working demo on sqlfiddle.com to play with.

Related

Full Outer Join Subqueries works until last part then ORA-013113

I've been task to implement a query that will identify and provide datetime marks using a specific date and time range for each user. Basically first scan last scan throughout the entire shift at specific intervals.
I set a UI that prompts user for date time that will be used within the query and then smaller subqueries to parse out the data I need. Individually every works as I expect but when I get to the last set it breaks and I encounter the following error:
ORA-03113: end-of-file on communication channel
I can't seem to work around this or think of an alternative way to structure the query to what I need.
I need help w/ either a better method of achieving the same results or a fix to what I already have in place.
This is for an Oracle database 12c
With
UI As
( Select
-- Deactivate UI While testing
#('Start of Shift', #DATETIME) sDATE,
#('End of Shift', #DATETIME) EDate
#Oracle(From Dual) -- Oracle don't allow "Select from Nothing"
)
Select s90.uname, Trim(s90.fname)||' '||(s90.lname) "_Full Name", MIN(FRST.datetimecol) "_Day Start", Max(FRST.datetimecol) "_Before 1st break"
, MIN(SCND.datetimecol) "_After 1st Break", Max(SCND.datetimecol) "_Before Lunch"
, MIN(THRD.datetimecol) "_After Lunch", MAX(THRD.datetimecol) "_Before Last Break"
, MIN(FRTH.datetimecol) "_After Last Break", MAX(FRTH.datetimecol) "_End of Shift"
From
(Select T1.Username, t1.datetimecol
From
syslogtable T1
where datetimecol > (Select sdate from UI) and datetimecol < (Select edate from UI) and to_char(datetimecol, 'hh24:mi:ss') >= '05:00:00' and to_char(datetimecol, 'hh24:mi:ss') <= '08:00:59') FRST
Full Outer Join
(Select T2.Username, t2.datetimecol
From
syslogtable T2
where datetimecol > (Select sdate from UI) and datetimecol < (Select edate from UI) and to_char(datetimecol, 'hh24:mi:ss') >= '08:00:00' and to_char(datetimecol, 'hh24:mi:ss') <= '10:00:59') SCND
On FRST.Username = SCND.Username
-- code continues in same format then I close out the fourth part w/ below
-- from frst to thrd - no issues but when add frth it stops
,
usermastertable s90
Where FRST.Username = s90.uname
Group By s90.uname, Trim(s90.fname)||' '||(s90.lname)
Order by s90.uname
I expect to receive usernamecol, full name then the date/time stamps in the applicable columns

Query to find out entries where dates don't overlap

Can anyone help me create a query which will populate a list of DJs who are not already booked in.
My user will select a start date (and time), and an end date (and time) - and then click a button to select a DJ.
I only want those DJs which are available between those time slots to appear in the list.
Here are the two tables which are involved
all I need in the listbox is the DJ Number, and the DJ Name
So far I have this... but it isn't working:
SELECT tblDJ.DJ_No AS [DJ ID], tblDJ.DJ_Name AS Name FROM tblDJ
WHERE (((tblDJ.[DJ_No]) Not In
(SELECT tblBooking.[FK_DJ_No]
FROM tblBooking
WHERE ( (tblBooking.End_Date) >= 01-04-2020 19:30:00 )))) ....etc....
I'm just entering a date in here for now, but obviously it will be stored in a variable once implemented.
Thanks
Implementing OVERLAPS of two intervals would look like:
1st_start_date <= 2nd_end_date and 1st_end_date >= 2nd_start_date
where 1st and 2nd values are markers of different events.
You could use that logic in combination with NOT EXISTS to discard those djs that are unavailable at a given time:
select dj_no, dj_name
from tbldj d
where not exists (
select 1
from tblbooking b
where b.fk_dj_no = d.dj_no
and b.start_date <= #END DATE#
and b.end_date >= #START DATE#
)
You just need to replace #START DATE# and #END DATE# with your values.
This does work because there are following assumptions:
Start date of the first event is prior to end date of that event
Start date of the second event is prior to end date of that event
Which seems logical, right?
The date in the SQL needs to be wrapped between two # in order for MS-Access to recognize it as a date:
select *
from tblDJ
where DJ_No not in
(
select FK_DJ_No
from tblBooking
where End_Date >= #2020-04-01 19:30:00#
)
Other than that you query will work.

SQL overlapping dates

Here, we have many meetings during the day. Usually from 9:00am-11:30am, 1:00pm-3:30pm, 4:00pm-6:30pm.
I'm having a hard time scheduling these meetings. I don't get how to check if they overlap for the same person who is leading the meeting.
Ex:
Should raise an error if Person1 has a meeting scheduled for 10:00am- 11:00am and another for 10:45am-11:30am.
I've come to this solution, but it is only partially working:
if exists (
select 1
from
Meeting M
where
M.IdPerson = #IdPerson --Stored procedure parameter
and
(#StartDate <= M.EndDate or -- 1
#EndDate >= M.StartDate or -- 2
(#StartDate > M.StartDate and #DtFim < A.Data_Termino))) -- 3
return 1
In the image below, I explain the three conditions in the where clause. The green lines are the new meeting start and end dates. The red lines are the old's.
How can I make this work?
I think the logic you want is:
if exists (select 1
from Meeting M
where M.IdPerson = #IdPerson and --Stored procedure parameter
#StartDate <= M.EndDate and
#EndDate >= M.StartDate
)
return 1
(I'm not sure if you want <= or < for these comparisons.) This assumes that StartDate and EndDate also store the time component.
Two time periods overlaps if the first starts before the second ends, and it ends after the second starts.

SQL Querying a slowly changing dimension

I have a table with Staion Platforms and when they were operational and when they were closed.
I want to get the count of stations that were operational between 01/01/2010 and 31/01/2010
On the right side is the result Im looking for. I've highlighted in Yellow those that should not be included in the result. The stations need to be grouped by Location. MAP_id is the PK.
The way I think it should work is to go to each row and check whether the start and end dates fall within Jan 2010.
Can this Query work?
SELECT ...
FROM ...
WHERE
'2010/01/31' between startdate and
case when enddate is null then Getdate()
else enddate
end
Any help would be appriciated.
Your condition is: "operational between 01/01/2010 and 31/01/2010". I would expect to see both these dates in the query.
This is an example of an overlapping time-intervals problem. What this is saying is that the station started on or before 01/01/2010 and ended on or after 31/01/2010. Here is the where clause for that logic:
where startdate <= '2010-01-01' and
(enddate is null or enddate >= '2010-01-31');
Note that the logic might be slightly different, depending on whether the end points are included or not.
You can also write this as:
where startdate <= '2010-01-01' and
coalesce(enddate, getdate()) >= '2010-01-31');

Sqlite3: Need to Cartesian On date

I have a table which is a list of games that have been played in a sqlite3 database. The field "datetime" is the a datetime of when game ended. The field "duration" is the number of seconds the game lasted. I want to know what percent of the past 24 hours had at least 5 games running simutaniously. I figured out to tell how many games running at a given time are:
select count(*)
from games
where strftime('%s',datetime)+0 >= 1257173442 and
strftime('%s',datetime)-duration <= 1257173442
If I had a table that was simply a list of every second (or every 30 seconds or something) I could do an intentional cartisian product like this:
select count(*)
from (
select count(*) as concurrent, d.second
from games g, date d
where strftime('%s',datetime)+0 >= d.second and
strftime('%s',datetime)-duration <= d.second and
d.second >= strftime('%s','now') - 24*60*60 and
d.second <= strftime('%s','now')
group by d.second) x
where concurrent >=5
Is there a way to create this date table on the fly? Or that I can get a similar effect to this without having to actually create a new table that is simply a list of all the seconds this week?
Thanks
First, I can't think of a way to approach your problem by creating a table on the fly or without the aid of an extra table. Sorry.
My suggestion is for you to rely on a static Numbers table.
Create a fixed table with the format:
CREATE TABLE Numbers (
number INTEGER PRIMARY KEY
);
Populate it with the number of seconds in 24h (24*60*60 = 84600). I would use any scripting language to do that using the insert statement:
insert into numbers default values;
Now the Numbers table has the numbers 1 through 84600. Your query will them be modified to be:
select count(*)
from (
select count(*) as concurrent, strftime('%s','now') - 84601 + n.number second
from games g, numbers n
where strftime('%s',datetime)+0 >= strftime('%s','now') - 84601 + n.number and
strftime('%s',datetime)-duration <= strftime('%s','now') - 84601 + n.number
group by second) x
where concurrent >=5
Without a procedural language in the mix, that is the best you'll be able to do, I think.
Great question!
Here's a query that I think gives you what you want without using a separate table. Note this is untested (so probably contains errors) and I've assumed datetime is an int column with # of seconds to avoid a ton of strftime's.
select sum(concurrent_period) from (
select min(end_table.datetime - begin_table.begin_time) as concurrent_period
from (
select g1.datetime, g1.num_end, count(*) as concurrent
from (
select datetime, count(*) as num_end
from games group by datetime
) g1, games g2
where g2.datetime >= g1.datetime and
g2.datetime-g2.duration < g1.datetime and
g1.datetime >= strftime('%s','now') - 24*60*60 and
g1.datetime <= strftime('%s','now')+0
) end_table, (
select g3.begin_time, g1.num_begin, count(*) as concurrent
from (
select datetime-duration as begin_time,
count(*) as num_begin
from games group by datetime-duration
) g3, games g4
where g4.datetime >= g3.begin_time and
g4.datetime-g4.duration < g3.begin_time and
g3.begin_time >= strftime('%s','now') - 24*60*60 and
g3.begin_time >= strftime('%s','now')+0
) begin_table
where end_table.datetime > begin_table.begin_time
and begin_table.concurrent < 5
and begin_table.concurrent+begin_table.num_begin >= 5
and end_table.concurrent >= 5
and end_table.concurrent-end_table.num_end < 5
group by begin_table.begin_time
) aah
The basic idea is to make two tables: one with the # of concurrent games at the begin time of each game, and one with the # of concurrent games at the end time. Then join the tables together and only take rows at "critical points" where # of concurrent games crosses 5. For each critical begin time, take the critical end time that happened soonest and that hopefully gives all the periods where at least 5 games were running concurrently.
Hope that's not too convoluted to be helpful!
Kevin rather beat me to the punchline there (+1), but I'll post this variation as it differs at least a little.
The key ideas are
Map the data in to a stream of events with attributes time and 'polarity' (=start or end of game)
Keep a running total of how many games are open at the time of each event
(this is done by forming a self-join on the event stream)
Find the event times where the number of games (as Kevin says) transitions up to 5, or down to 4
A little trick: add up all the down-to-4 times and take away the up-to-5s - the order is not important
The result is the number of seconds spent with 5 or more games open
I don't have sqllite, so I've been testing with MySQL, and I've not bothered to limit the time window to preserve some sanity. Shouldn't be difficult to revise.
Also, and more importantly, I've not considered what to do if games are open at the beginning or end of the period!
Something tells me there's a big simplification to be had here, but I've not spotted it yet.
SELECT SUM( event_time )
FROM (
SELECT -ga.event_type * ga.event_time AS event_time,
SUM( ga.event_type * gb.event_type ) event_type
FROM
( SELECT UNIX_TIMESTAMP( g1.endtime - g1.duration ) AS event_time
, 1 event_type
FROM games g1
UNION
SELECT UNIX_TIMESTAMP( g1.endtime )
, -1
FROM games g1 ) AS ga,
( SELECT UNIX_TIMESTAMP( g1.endtime - g1.duration ) AS event_time
, 1 event_type
FROM games g1
UNION
SELECT UNIX_TIMESTAMP( g1.endtime )
, -1
FROM games g1 ) AS gb
WHERE
ga.event_time >= gb.event_time
GROUP BY ga.event_time
HAVING SUM( ga.event_type * gb.event_type ) IN ( -4, 5 )
) AS gr
Why don't you trim the date and keep only the time, if you filter your data for any given date every time is unique. In this way you'll only need a table with numbers from 1 to 86400 (or less if you take bigger intervals), you may create two columns, "from" and "to" to define the intervals.
I'm not familiar with SQLite functions but according to the manual you have to use the strftime function with this format: HH:MM:SS.