This points to the between condition, with some twists, but I was very unlucky using betweens in connections. It seems like they do some form of cross-connect at the back and end, and then filter out the actual join where-clause style. I know that this is not very technical, but I never made the condition for equality in the connection, which turned out well.
Thus, this may seem counterintuitive, but I think hacking all the features today might be your best bet. Not knowing how big your date ranges are, it's really hard to say.
In addition, I think that this will really satisfy both conditions in your question right away - by informing you of all work tasks that do not have corresponding payment rates.
Try this against your actual data and see how it works (and how long it takes).
with pay_dates as ( select emp_id, rate, generate_series (start_date, coalesce (end_date, current_date), interval '1 day') as pd from hourly_pay ), assignment_dates as ( select emp_id, start_date, generate_series (start_date, coalesce (end_date, current_date), interval '1 day') as wd from work_assignments ) select emp_id, min (wd)::date as from_date, max (wd)::date as thru_date from assignment_dates a where not exists ( select null from pay_dates p where p.emp_id = a.emp_id and a.wd = p.pd ) group by emp_id, start_date
The result should be all ranges of work orders without bids:
emp from thru 1 '2017-05-10' '2017-05-19' 2 '2017-05-08' '2017-11-14'
It is nice that this will also eliminate any overlap where the work task was partially covered.
- Edit 3/20/2018 -
At your request, here is a breakdown of what logic does.
with pay_dates as( select emp_id, rate, generate_series (start_date, coalesce (end_date, current_date), interval '1 day') as pd from hourly_pay )
This takes hourly_pay data and breaks it into a record for each employee for each day:
emp_id rate pay date 1 75 5/20/17 1 75 5/21/17 1 75 5/22/17 ... 1 75 6/30/17 1 80 6/01/17 1 80 6/02/17 ... 1 80 today
Further
[implied "with"] assignment_dates as ( select emp_id, start_date, generate_series (start_date, coalesce (end_date, current_date), interval '1 day') as wd from work_assignments )
Effectively does the same for the work assignment table, storing only the โstart date columnโ on each row.
Then the main request is as follows:
select emp_id, min (wd)::date as from_date, max (wd)::date as thru_date from assignment_dates a where not exists ( select null from pay_dates p where p.emp_id = a.emp_id and a.wd = p.pd ) group by emp_id, start_date
Which of the two above queries. The important part is the anti-compound:
not exists ( select null from pay_dates p where p.emp_id = a.emp_id and a.wd = p.pd )
This defines each work task if there is no corresponding entry for this employee for this day.
Thus, in essence, the query accepts data ranges from both tables, creates every possible combination of dates, and then performs anti-join to see where they do not match.
Although it seems illogical to take one record and blow it up into several records, two things need to be considered:
Dates are very limited creatures โ even over 10 years, the value of data, which is only 4,000 or so records, is of little concern to the database, even when it is multiplied by the employeeโs database. Your time frame looks much smaller.
I had very, VERY bad luck using unions other than =, for example between or > . It seems that in the background he makes Cartesians, and then filters the results. For comparison, takeoff ranges, at least, give you some control over how the data explosion occurs.
For smiles, I did this with your sample data above and came up with this that actually looks accurate:
1 '2017-05-10' '2017-05-19' 2 '2017-05-08' '2018-03-20'
Let me know if this is unclear.