Identify double seat bookings via sql - sql

I have to make a report to identify double seat bookings . One can book a seat for a date range or a single date. Like the columns date_from to date_to can be a single day or a range( like from 16th Jan till 16th Jan or from 10th Jan to 30th Jan)
The problem is that the system allows double booking in case when there is an overlapping date range like if someone wants to book seat no 7 from 10th Jan to 16th Jan and someone books the same seat from 12thJan to 13th Jan. But it should not, that is what I have to flag about
I have tried writing the below query but my query does not identify anything in date ranges.. it only works for single dates. I would need to first break these date ranges in single dates and then run my query to work -
;with duplicate_seat(desk_id,date_from,date_to,name) as
(
select da.desk_id, da.date_from,da.date_to, hr.name as name
FROM [human_resources].[dbo].[desks_temporary_allocations] da
JOIN[human_resources].[dbo].hrms_mirror hr ON hr.sage_id = da.sage_id
)
select ds.desk_id,ds.date_from,ds.date_to,count(ds.desk_id)as occurences,min(ds.name)as Name1,max(ds.name) as Name2
from duplicate_seat ds
where ds.name like ('priyanka%')
group by ds.desk_id,ds.date_from,ds.date_to
having count(ds.desk_id)>1
This will give result like-
enter image description here
as you can see it is not picking up any date ranges.. only for a single date..But there were double bookings in case date ranges which this query is not showing. Can anyone please help me with this?

As others have suggested, you should remove the email part of your question and post that separately once this is resolved.
For simplicity, I've used temp tables to demonstrate this but it should be easy to convert to a CTE is you wish.
The key to the is having a Date table. If you don't have one, there are plenty of examples of how to generate one quickly. In this case my date table is called [Config].[DatesTable]
CREATE TABLE #t (desk_id int, date_from date, date_to date, EmpName varchar(10));
insert into #t VALUES
(1, '2022-12-25', '2023-01-01', 'Dave'),
(2, '2023-01-15', '2023-01-15', 'Jane'),
(2, '2023-01-12', '2023-01-20', 'Bob'),
(2, '2023-01-15', '2023-01-17', 'Mary');
-- desks and the dates they are over booked on
SELECT desk_id, TheDate
INTO #OverBookedDeskByDate
FROM (SELECT t.* , dt.TheDate
FROM #t t
JOIN Config.DatesTable dt on dt.TheDate between t.date_from and t.date_to
) a
GROUP BY desk_id, TheDate
HAVING Count(*) >1
-- find the bookings that overlap these desks/dates
SELECT t.*, o.TheDate FROM #OverBookedDeskByDate o
JOIN #t t on o.TheDate between t.date_from and t.date_to
ORDER by EmpName, desk_id, TheDate
I've created 3 bookings with some overlapping dates for desk 2.
Here are the results

Related

Sum over N days excluding Weekends and Holidays

I have below table
AccountID
Date
Amount
123
07/02/2021
2000
123
07/09/2021
9000
123
07/15/2021
500
123
07/20/2021
500
123
07/28/2021
500
I am trying to create a test script to test data for just one month(July). I want to sum the amount over 5 days where 5 days does not count weekends and holidays. Since it is month of July the holiday falls on July 5th 2021(07/05/2021).
The output should look something like below
AccountID
Date
Amount
123
07/02/2021
11000
123
07/09/2021
9500
123
07/15/2021
1000
123
07/20/2021
500
123
07/28/2021
500
Below is the table create and data insert statements for reference :-
create table TRANSACTIONS (
AccountID int,
Date date,
Amount int
)
insert into TRANSACTIONS values (123, '07/02/2021', 2000)
insert into TRANSACTIONS values (123, '07/09/2021', 9000)
insert into TRANSACTIONS values (123, '07/15/2021', 500)
insert into TRANSACTIONS values (123, '07/20/2021', 500)
insert into TRANSACTIONS values (123, '07/28/2021', 500)
I was able to create script that could sum over 5 days with skipping weekends(Saturday and Sunday). I am not able to think how can I skip the holiday on July 5th, 2021. I am fine with hardcoding it since this is just for testing purposes. The code 'DATEPART(WEEKDAY, h2.Date) not in (1, 7)' skips Weekend and 'DATEADD(d, 6, h1.Date)' here I am adding 6 and not 5 even the sum should be for over 5 days because after reading some articles I figured that in skipping weekends the last day is not inclusive so used 6 instead of 5. This code adds perfectly over 5 days skipping weekends
SELECT AccountId, Date,
(
SELECT SUM(Amount)
FROM TRANSACTIONS h2
WHERE
h1.AccountID = h2.AccountID and
DATEPART(WEEKDAY, h2.Date) not in (1, 7) and
h2.Date between h1.Date AND DATEADD(d, 6, h1.Date)
) as SumAmount
FROM TRANSACTIONS h1
The only sane way to tackle this is to have a calendar table to represent holidays. The easiest approach is to store every date for the date range you're likely to need (eg 1970-2030) with the type of the date, perhaps and enum of WORKDAY, WEEKEND, HOLIDAY or whatever works, eg
CREATE TABLE CALENDAR (
Date DATE,
Day_type varchar(16)
);
-- insert rows for dates you care about
Depending on where you live, you may need to include a region column too (typically the country and/or state).
With such a table, you join to it:
SELECT
AccountId,
DATEADD(DAY, (DATEDIFF(DAY, 0, t.Date)/7)*7 + 7, 0) as Date,
SUM(Amount)
FROM TRANSACTIONS t
JOIN CALENDAR c on t.Date = c.Date
AND c.day_type = 'WORKDAY'
WHERE t.Date BETWEEN <your date range>
GROUP BY AccountId, DATEADD(DAY, (DATEDIFF(DAY, 0, t.Date)/7)*7 + 7, 0)

Data value on a given date

This time I have a table on a PostgreSQL database that contains the employee name, the date that he started working and the date that he leaves the company, in the cases of the employee still remains in the company, this field has null value.
Knowing this, I would like to know how many people was working on a predetermined date, ex:
I would like to know how many people works on the company in January 2021.
I don't know where to start, in some attempts I got the number of hires and layoffs per month, but I need to show this accumulated value per month, in another column.
I hope I made myself understood, I'll leave the last SQL I got here.
select reference, sum(hires) from
(
select
date_trunc('month', date_hires) as reference,
count(*) as hires
from
ponto_mais_relatorio_colaboradores
group by
date_hires
union all
select
date_trunc('month', date_layoff) as reference,
count(*)*-1 as layoffs
from
ponto_mais_relatorio_colaboradores
group by
date_layoff
) as reference
join calendar_aux on calendar_aux.ano_mes = reference
group by reference
order by reference
Break the requirement down. The question: how many are employed on any given date? That would include all hired before that date and do not have a layoff date plus all hired before with a layoff date later then the date your interested period. I.e you are interested in Jan so you still want to count an employee with a layoff date in Feb. With that in place convert into SQL. The preceding is available from select comparing dates. other issue is that Jan is not a date, it is a range of dates, so you need each date. You can use generate series to create each day in Jan. Then Join the generated dates with and selection from your table. Resulting query:
with jan_dates( jdate ) as
( select generate_series( date '2021-01-01'
, date '2021-01-31'
, interval '1' day
)::date
)
select jdate "Date", count(*) "Employees"
from jan_dates j
join employees e
on ( e.date_hires <= j.jdate
and ( e.date_layoff is null
or e.date_layoff > j.jdate
)
)
group by j.jdate
order by j.jdate;
Note: Not tested.

Count Records Prior to Date for Whole Year

I have a historical database with about 9000 records with unique UserID and date they created an account CreatedDate that looks like this:
UserID CreatedDate
1 5/12/2019
2 1/1/2018
3 4/2/2015
4 8/9/2016
. ..
I would like to know how many accounts were created UP TO a certain date, but for multiple months.
For example, how many accounts were there in Jan 2020, Feb 2020, Mar 2020, so on and so forth.
The manual way would be to do this for each month but it would be tedious:
select count(*)
from SCHEMA
--KEEP REPLACING THE MONTH TO GET COUNTS
where CreatedDate <= '2020-01-31'
Just wondering if there is a more efficient way? A group by wouldn't work because it just totals for each month, but I'm trying to get a historical count. Thanks!
You seem to need running total for each month. If so, you need group by to compute total counts per month and then you have to sum them using analytical sum function.
This is how you would do it in Postgres (db fiddle). Other vendors may differ in the way how month is extracted but the principle is same.
with schema(UserID, CreatedDate) as (values
(1, date '2019-12-05'),
(2, date '2018-01-01'),
(3, date '2015-01-04'),
(4, date '2016-09-08')
)
select month, sum(cnt) over (order by month) from (
select date_trunc('month', CreatedDate)::date as month, count(*) as cnt
from schema
group by date_trunc('month', CreatedDate)::date
) x
Note if data has gaps in month sequence and you want continuous sequence (for example all months between 2015-01 and 2019-12), you have to pregenerate calendar (relation with all months) and left join table schema to it. (It is not in my example yet because of YAGNI.)

Group Data by Year, Oracle SQL

I am trying to create a query that counts records that existed within a year. The table looks like this:
Title_ID ISSUE_DATE EXPIRY_DATE CLIENT_NUMBER
123 '26-JUN-19' '17-AUG-20' 8529
124 '04-APR-19' '17-SEP-22' 8529
125 '09-MAY-15' '11-SEP-19' 3654
126 '31-DEC-19' '25-NOV-22' 9852
127 '27-OCT-18' '26-FEB-21' 2254
128 '05-OCT-11' '01-JAN-19' 9852
Specifically, I want to count the number of distinct CLIENT_NUMBERS of the records that existed in a given calendar year.
The record (title) exists from the ISSUE_DATE until the EXPIRY_DATE. If the record existed at any point within a year (Let's say 2019), then we are interested in including it in our client count.
So, if the record was issued in 2019 or if the record expired in 2019 or if the record was issued before 2019 and expired after 2019, then we are interested in including it in the client count for the year it existed.
I have built the following query that does this, but only for one specific year (2019). I'd like to build the query further so it look at each calendar year and counts the distinct client numbers when the client has an active title:
SELECT *
-- count(distinct client_number)
FROM
TITLE
WHERE
issue_date between '01-Jan-19' and '31-Dec-19'
or expiry_date between '01-Jan-19' and '31-Dec-19'
or (issue_date < '01-Jan-19' and expiry_date > '31-Dec-19')
Where I am having trouble is, my data is much larger than the subset I have provided. I would like to recursively get counts of distinct client numbers by year using the same kind of logic to include a record within a calendar year as I have outlined above. So, I'd like to have a table like this:
YEAR COUNT_OF_CLIENT_NUMBERS
2020 5469
2019 5587
2018 4852
2017 4501
2016 3265
etc
I think I've stretched by current SQL abilities at this point, so I thought Id ask to see if there are any suggestions to make this happen?
Thanks.
EDIT: to clarify, the issue date and the expiry date apply to the title, not the client. So, the title is issued on the issue date and expires on the expiry date. A client can own one or more title(s).
So, I am looking to get a count of how many distinct clients own active titles within a give year if one or more of their titles is active within that year. So the key is, a title is considered active if it was issued in that year OR it expired within that year OR it was issued before that year and expired after that year. A title CAN be active in multiple years (i.e. Issued on Feb. 4, 2014 and expires on Apr.7 2017, I want to include the client count for each year that titles exists....2014, 2015, 2016 and 2017).
So, I created a table to join to (thanks #GMB for the suggestion):
with calendar_year (y) as
(
select 2010 from dual
union all select y + 1 from calendar_year where y < 2020
)
select * from calendar_year
Which returns:
2010
2011
2012
2013
2014
etc
I want to join that to my titles table, but I am having issues recursively looking at the issue date and expiry date to join up the title to each year it existed in. Any help in that area, would be great!
You can use a recursive query to generate the years, then bring the table with a left join, and aggregate:
with dates (dt) as (
select date '2016-01-01' from dual
union all select add_months(dt, 1) from dates where dt < date '2020-01-01'
)
select d.dt, count(distinct t.client_number) count_of_client_numbers
from dates d
left join title t
on t.issue_date <= d.dt
and t.expiry_date > d.dt
group by d.dt
The upside of this approach is that you get results for each and every year, even those where no title started or ended.
You can get number of clients on any day by unpivoting the data, so there is one row per date. Then keep track of the "ins" and "outs".
You don't specify the database, but here is one approach:
select dte, sum(inc),
sum(sum(inc)) over (order by dte) as active_on_date
from ((select issue_date as dte, 1 as inc
from t
) union all
(select expiry_date as dte, -1 as inc
from t
)
) t
group by dte
order by dte;
EDIT:
Hmmm, the above may not do exactly what you want. If you want to count distinct client numbers rather than overall rows, then it might be simpler to just list the dates and join:
select d.dte, count(distinct t.client_id)
from (select date '2020-01-01' as dte from dual union all
select date '2019-01-01' as dte from dual union all
select date '2018-01-01' as dte from dual union all
. . .
) d left join
t
on d.dte between t.issue_dte and t.expiry_dte
group by d.dte
order by d.dte;

SQL Server Between Start and End dates

I'm creating an internal holiday booking system and I need to put business logic rules into place but I need to do a check on how many people are booked off on the dates between the Start and End date because for example 2 apprentices may only be booked off on 1 day but I have no way off grabbing the dates between.
Any help would be appreciated
Below is the job role table
You haven't posted the RDBMS, or the names of the tables, or what exactly the Job Role table is supposed to be doing ... but I'll take a shot at this anyway. I'm using a recursive CTE to generate a list of dates, but it would be far better for you to use a Date table, and I don't even know whether your RDBMS will support this. I've also posted the syntax for a table variable below that populates data mimicking your sample.
The final output, naturally, will need to be customized to do whatever you need to do. This does show you, however, a list of every date when more than one employee is on vacation. Add extra conditions to the second JOIN or to the WHERE clause to filter on other things (like JobRole) if necessary.
-- Code originally from http://smehrozalam.wordpress.com/2009/06/09/t-sql-using-common-table-expressions-cte-to-generate-sequences/
-- Define start and end limits
DECLARE #todate DATETIME, #fromdate DATETIME
SELECT #fromdate='2014-01-01', #todate=GETDATE()-1
DECLARE #TimeOff TABLE (StartDate DATETIME, EndDate DATETIME, EmployeeID INT)
INSERT INTO #TimeOff (StartDate, EndDate, EmployeeID)
SELECT '1/1/2014', '1/7/2014', 7 UNION
SELECT '2/1/2014', '2/7/2014', 7 UNION
SELECT '3/3/2014', '3/9/2014', 7 UNION
SELECT '2/5/2014', '2/6/2014', 8
;WITH DateSequence( Date ) AS -- this will list all dates. Use this if you don't have a date table
(
SELECT #fromdate as Date
UNION ALL
SELECT DATEADD(DAY, 1, Date)
FROM DateSequence
WHERE Date < #todate
)
--select result
SELECT DateSequence.Date, TimeOffA.StartDate, TimeOffB.EndDate, TimeOffA.EmployeeID
FROM
DateSequence -- a full list of all possible dates
INNER JOIN
#TimeOff TimeOffA ON -- all dates when an employee is on vacation -- replace this with your actual table's name
DateSequence.Date BETWEEN TimeOffA.StartDate AND TimeOffA.EndDate
INNER JOIN
#TimeOff TimeOffB ON -- all dates when an employee who is NOT employee A is on vacation -- replace this with your actual table's name
DateSequence.Date BETWEEN TimeOffB.StartDate AND TimeOffB.EndDate AND
TimeOffA.EmployeeID <> TimeOffB.EmployeeID
option (MaxRecursion 2000)