SQL join on non-present lines - sql

I have two tables as follow:
position
--------------------------------
position_id description
17 team_lead
18 accountant
19 it_specialist
20 consultant
--------------------------------
assignment
------------------------------------------------------------
position_id people_id start stop
17 1001 2020-01-01 2021-06-01
17 1002 2021-06-01 2021-08-01
18 1003 2020-01-01 2021-12-01
19 1004 2020-01-01 2021-08-01
------------------------------------------------------------
I want to retrieve the positions that have no assignment today (2021-08-12), regardless if they had past assignments or not. The expected output should be:
position_id
17
19
20
How can I do this ? My attempts to use a left join like:
select
position.position_id
from position
left join assignment
on
assignment.position_id = position.position_id
where
/* incorrect. How to match a non-interval ? */
assignment.start >= DATE('now') or assignment.stop <= DATE('now')
fail to properly match what I want.

You can use not exists:
select p.*
from positions p
where not exists (select 1
from assignments a
where a.position_id = p.position_id and
a.start <= current_date and
a.stop >= current_date
);
It is not clear from your question is the stop date is included. That would affect the last comparison.

Related

Select max date for each register, null if does not exists

I have these tables: Employee (id, name, number), Configuration (id, years, licence_days), Periods (id, start_date, end_date, configuration_id, employee_id, period_type):
Employee table:
id name number
---- ----- -------
1 Bob 355
2 John 467
3 Maria 568
4 Josh 871
configuration table:
id years licence_days
---- ----- ------------
1 1 8
2 3 16
3 5 24
Periods table:
id start_date end_date configuration_id employee_id period_type
---- ---------- ------- ---------------- ----------- -----------
1 2021-05-23 2021-05-31 1 1 vaccation
2 2021-05-24 2021-06-01 1 2 vaccation
3 2021-03-01 2021-03-17 2 2 vaccation
4 2021-05-05 2021-05-21 2 2 vaccation
5 2021-01-01 2021-01-17 2 4 vaccation
I want this result:
Result:
employee_id years licence_days max(end_date)
1 1 8 2021-05-31
1 3 16 null
1 5 24 null
2 1 8 2021-06-01
2 3 16 2021-05-21
2 5 24 null
3 1 8 null
3 3 16 null
3 5 24 null
4 1 8 null
4 3 16 2021-01-17
4 5 24 null
i.e., I want to select all Employees with all configuration, and for each one of that, the max end_date of the "vaccation" type (or null if it does not exists).
How can I do that
Oracle supports cross joins, right? So may be something like that?
SELECT e.employee_id, c.years, c.licence_days, max(p.end_date)
FROM Employee e
CROSS JOIN configuration c
LEFT JOIN Periods p
ON e.employee_id = p.employee_id
AND c.configuration_id = p.configuration_id
GROUP BY e.employee_id, c.years, c.licence_days
ORDER BY e.employee_id, c.years
#umberto-petrov chooses wisely with the ANSI CROSS JOIN syntax for a cartesian join. However, in the very weak probability that your requires output of configurations even where there is no employees, you can go with something like :
EDIT: Filtering the Periods join with 'vaccation' as asked in the comments.
If you have to filter for some employee ids, change ON 1 = 1 by ON Employee.id IN (id1, id2, ...). It still keeps every configurations but only takes employees that match the ids.
SELECT Employee.employee_id,
Configuration.years,
Configuration.licence_days,
MAX(Configuration.end_date) max_end_date
FROM Configuration LEFT JOIN Employee ON 1 = 1
LEFT JOIN Periods ON Periods.configuration_id = Configuration.id
AND Periods.employee_id = Employee.id
AND Periods.period_type = 'vaccation'
GROUP BY Employee.employee_id,
Configuration.years,
Configuration.licence_days
ORDER BY Employee.employee_id,
Configuration.years,
Configuration.licence_days
We start from configuration to take every records from this one at least, then made a LEFT CARTESIAN JOIN with Employee and finally a full LET JOIN on Periods for both. That way , if there is no employees, this will output configuration_id and NULL for years, licence_days and max end_date.

Find available time slots for table reservation system

I am creating a database for table reservation system. Each table have it's own time slot list and I want to find available time slots for each table.
So I have the following tables:
time_slots table
----------------------------------
id || table_id || weekday || time
==================================
1 1 1 12:00
2 1 1 12:30
...
11 1 1 16:00
...
17 1 1 19:00
18 1 1 19:30
...
27 1 1 00:00
==================================
reservations table
-----------------------------------------------------------------
id || table_slot_id || start_date || end_date
=================================================================
1 1 2020-06-01 12:00:00 2020-06-01 12:20:00
2 1 2020-06-01 16:00:00 2020-06-01 19:00:00
=================================================================
I wrote query which allows to find all reserved time slots:
SELECT *
FROM table_slots, (SELECT *
FROM reservations
WHERE reservations.start_date::date = '2020-06-01'::date) as res
WHERE table_slots.weekday = extract(dow from '2020-06-01'::date)
AND table_slots.time BETWEEN res.start_date::time and res.end_date::time
ORDER BY table_slots.time
But I don't understand how to write query to find available time slots. So I expect that result should contain all time_slots where time is between 12:30 and 15:30; 19:30 and 00:00. How to do this?
Is it possible to write such queries with minimal overhead?
And the last question, is this good design for such systems? If not then what is the better designs in these scenerios.
Thanks!
You could use not exists. This would give you all available slots on June 1st:
select ts.*
from time_slots ts
where not exists (
select 1
from reservations r
where
r.table_slot_id = ts.id
and r.start_date >= '2020-06-01'::date
and r.start_date < '2020-07-01'::date
and r.start_date::time <= ts.time
and r.end_date::time > ts.time
)
Note that this assumes that reservations do not span across days.
It might be more efficient to express the where clause of the subquery as:
where
r.table_slot_id = ts.id
and r.start_date >= '2020-06-01'::date
and r.start_date <= '2020-06-01'::date + ts.time::interval
and r.end_date > '2020-06-01'::date + ts.time::interval
This would take advantage of an index on reservations(table_slot_id, start_date, end_date).

Finding_Missing_Dates_between_Dates_From_Data_OracleSQL

Need your assistance on finding the missing dates from records, sample below
Currently, i've data for 1, 2, 6 and 10 Jan 2020
select p.effective_date,x.xref_security_id,x.xref_type
from securitydbo.price p
inner join securitydbo.xreference x on x.security_alias = p.security_alias
where p.src_intfc_inst = 253
and p.effective_date between ('01-JAN-2020') and ('10-JAN-2020')
and x.xref_security_id = 'ABC999999999'
Expected Results
Missing_Date Xref_Security_ID Xref_Type Price
1/3/2020 ABC99999999 ISIN 0
1/7/2020 ABC99999999 ISIN 0
1/8/2020 ABC99999999 ISIN 0
1/9/2020 ABC99999999 ISIN 0
I don't have your tables so I created one which looks like result you currently have:
SQL> select * From test order by missing_date;
MISSING_DA XREF_S
---------- ------
01/03/2020 ABC999
01/07/2020 ABC999
01/08/2020 ABC999
01/09/2020 ABC999
In order to get dates that are missing, create a calendar (see the CTE I used, which is just one of row generator techniques) whose
starting date is lower date from your period
add level to it
connect by clause "loops" as many times as there are days in desired period
XREF_SECURITY_ID is NULL for missing dates as there's no match for them in your tables.
SQL> with
2 -- create a calendar for desired period (see CONNECT BY)
3 calendar as
4 (select date '2020-01-01' + level - 1 datum
5 from dual
6 connect by level <= date '2020-01-10' - date '2020-01-01' + 1
7 )
8 -- outer join calendar with your table(s)
9 select c.datum, t.xref_security_id
10 from calendar c left join test t on t.missing_date = c.datum
11 order by c.datum;
DATUM XREF_S
---------- ------
01/01/2020
01/02/2020
01/03/2020 ABC999
01/04/2020
01/05/2020
01/06/2020
01/07/2020 ABC999
01/08/2020 ABC999
01/09/2020 ABC999
01/10/2020
10 rows selected.
SQL>
I can take a guess that the date_format might be the problem out here. Without actually knowing what is the data in your tables the only way to do is to guess.
select p.effective_date,x.xref_security_id,x.xref_type
from securitydbo.price p
inner join securitydbo.xreference x on x.security_alias = p.security_alias
where p.src_intfc_inst = 253
and p.effective_date between to_date('01-JAN-2020','DD-MON-YYY')
and to_date('10-JAN-2020','DD-MON-YYYY')
and x.xref_security_id = 'ABC999999999'

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;

Add N business days to a given date skipping holidays, exceptions and weekends in SQL DB2

I'm facing a challenging task here, spent a day on it and I was only able to solve it through a procedure but it is taking too long to run for all projects.
I would like to solve it in a single query if possible (no functions or procedures).
There is already some questions here doing it in programming languages OR sql functions/procedures (Wich I also solved min). So I'm asking if it is possible to solve it with just SQL
The background info is:
A project table
A phase table
A holiday table
A dayexception table which cancel a holiday or a weekend day (make that date as a working day) and it is associated with a project
A project may have 0-N phases
A phase have a start date, a duration and a draworder (needed by the system)
Working days is all days that is not weekend days and not a holiday (exception is if that date is in dayexception table)
Consider this following scenario:
project | phase(s) | Dayexception | Holiday
id | id pid start duration draworder | pid date | date
1 | 1 1 2014-01-20 10 0 | 1 2014-01-25 | 2014-01-25
| 2 1 2014-02-17 14 2 | |
The ENDDATE for the project id 1 and phase id 1 is actually 2014-01-31 see the generated data below:
The date on the below data (and now on) is formatted as dd/mm/yyyy (Brazil format) and the value N is null
proj pha start day weekday dayexcp holiday workday
1 1 20/01/2014 20/01/2014 2 N N 1
1 1 20/01/2014 21/01/2014 3 N N 1
1 1 20/01/2014 22/01/2014 4 N N 1
1 1 20/01/2014 23/01/2014 5 N N 1
1 1 20/01/2014 24/01/2014 6 N N 1
1 1 20/01/2014 25/01/2014 7 25/01/2014 25/01/2014 1
1 1 20/01/2014 26/01/2014 1 N N 0
1 1 20/01/2014 27/01/2014 2 N 27/01/2014 0
1 1 20/01/2014 28/01/2014 3 N N 1
1 1 20/01/2014 29/01/2014 4 N N 1
To generate the above data I created a view daysOfYear with all days from 2014 and 2015 (it can be bigger or smaller, created it with two years for the year turn cases) with a CTE query if you guys want to see it let me know and I will add it here. And the following select statement:
select ph.project_id proj,
ph.id phase_id pha,
ph.start,
dy.curday day,
dy.weekday, /*weekday here is a calling to the weekday function of db2*/
doe.exceptiondate dayexcp,
h.date holiday,
case when exceptiondate is not null or (weekday not in (1,7) and h.date is null)
then 1 else 0 end as workday
from phase ph
inner join daysofyear dy
on (year(ph.start) = dy.year)
left join dayexception doe
on (ph.project_id = doe.project_id
and dy.curday = truncate(doe.exceptiondate))
left join holiday h
on (dy.curday = truncate(h.date))
where ph.project_id = 1
and ph.id = 1
and dy.year in (year(ph.start),year(ph.start)+1)
and dy.curday>=ph.start
and dy.curday<=ph.start + ((duration - 1) days)
order by ph.project_id, start, dy.curday, draworder
To solve this scenario I created the following query:
select project_id,
min(start),
max(day) + sum(case when workday=0 then 1 else 0 end) days as enddate
from project_phase_days /*(view to the above select)*/
This will return correctly:
proj start enddate
1 20/01/2014 31/01/2014
The problem I couldn't solve is if the days I'm adding (non workdays sum(case when workday=0 then 1 else 0 end) days ) to the last enddate (max(day)) is weekend days or holidays or exceptions.
See the following scenario (The duration for the below phase is 7):
proj pha start day weekday dayexcp holiday workday
81 578 14/04/2014 14/04/2014 2 N N 1
81 578 14/04/2014 15/04/2014 3 N N 1
81 578 14/04/2014 16/04/2014 4 N N 1
81 578 14/04/2014 17/04/2014 5 N N 1
81 578 14/04/2014 18/04/2014 6 N 18/04/2014 0
81 578 14/04/2014 19/04/2014 7 N 0
81 578 14/04/2014 20/04/2014 1 N 20/04/2014 0
/*the below data I added to show the problem*/
81 578 14/04/2014 21/04/2014 2 N 21/04/2014 0
81 578 14/04/2014 22/04/2014 3 N 1
81 578 14/04/2014 23/04/2014 4 N 1
81 578 14/04/2014 24/04/2014 5 N 1
With the above data my query will return
proj start enddate
81 14/04/2014 23/04/2014
But the correct result would be the enddate as 24/04/2014 that's because my query doesn't take into account if the days after the last day is weekend days or holidays (or exceptions for that matter) as you can see in the dataset above the day 21/04/2014 which is outside my duration is also a Holiday.
I also tried to create a CTE on phase table to add a day for each iteration until the duration is over but I couldn't add the exceptions nor the holidays because the DB2 won't let me add a left join on the CTE recursion. Like this:
with CTE (projectid, start, enddate, duration, level) as (
select projectid, start, start as enddate, duration, 1
from phase
where project_id=1
and phase_id=1
UNION ALL
select projectid, start, enddate + (level days), duration,
case when isWorkDay(enddate + (level days)) then level+1 else level end as level
from CTE left join dayexception on ...
left join holiday on ...
where level < duration
) select * from CTE
PS: the above query doesn't work because of the DB2 limitations and isWorkDay is just as example (it would be a case on the dayexception and holiday table values).
If you have any doubts, please just ask in the comments.
Any help would be greatly appreciated. Thanks.
How to count business days forward and backwards.
Background last Century I worked at this company that used this technique. So this is a pseudo code answer. It worked great for their purposes.
What you need is a table that contains a date column and and id column that increments by one. Fill the table with only business dates... That's the tricky part because of the observing date on another date. Like 2017-01-02 was a holiday where I work but its not really a recognized holiday AFAIK.
How to get 200 business days in the future.
Select the min(id) where date >= to current date.
Select the date where id=id+200.
How to get 200 business days in the past.
Select the min(id) from table with a date >= to current date.
Select the date with id=id-200.
Business days between.
select count(*) from myBusinessDays where "date" between startdate and enddate
Good Luck as this is pseudo code.
So, using the idea of #danny117 answer I was able to create a query to solve my problem. Not exactly his idea but it gave me directions to solve it, so I will mark it as the correct answer and this answer is to share the actual code to solve it.
First let me share the view I created to the periods. As I said I created a view daysofyear with the data of 2014 and 2015 (in my final solution I added a considerable bigger interval without impacting in the end result). Ps: the date format here is in Brazil format dd/mm/yyyy
create or replace view daysofyear as
with CTE (curday, year, weekday) as (
select a1.firstday, year(a1.firstday), dayofweek(a1.firstday)
from (select to_date('01/01/1990', 'dd/mm/yyyy') firstday
from sysibm.sysdummy1) as a1
union all
select a.curday + 1 day as sumday,
year(a.curday + 1 day),
dayofweek(a.curday + 1 day)
from CTE a
where a.curday < to_date('31/12/2050', 'dd/mm/yyyy')
)
select * from cte;
With that View I then created another view with the query on my question adding an amount of days based on my historical data (bigger phase + a considerable margin) here it is:
create or replace view project_phase_days as
select ph.project_id proj,
ph.id phase_id pha,
ph.start,
dy.curday day,
dy.weekday, /*weekday here is a calling to the weekday function of db2*/
doe.exceptiondate dayexcp,
h.date holiday,
ph.duration,
case when exceptiondate is not null or (weekday not in (1,7) and h.date is null)
then 1 else 0 end as workday
from phase ph
inner join daysofyear dy
on (year(ph.start) = dy.year)
left join dayexception doe
on (ph.project_id = doe.project_id
and dy.curday = truncate(doe.exceptiondate))
left join holiday h
on (dy.curday = truncate(h.date))
where dy.year in (year(ph.start),year(ph.start)+1)
and dy.curday>=ph.start
and dy.curday<=ph.start + ((duration - 1) days) + 200 days
/*max duration in database is 110*/
After that I then created this query:
select p.id,
a.start,
a.curday as enddate
from project p left join
(
select p1.project_id,
p1.duration,
p1.start,
p1.curday,
row_number() over (partition by p1.project_id
order by p1.project_id, p1.start, p1.curday) rorder
from project_phase_days p1
where p1.validday=1
) as a
on (p.id = a.project_id
and a.rorder = a.duration)
order by p.id, a.start
What it does is select all workdays from my view (joined with my other days view) rownumber based on the project_id ordered by project_id, start date and current day (curday) I then join with the project table to get the trick part that solved the problem which is a.rorder = a.duration
If you guys need more explanation I will be glad to provide.