Find the first missing date in a column (Oracle) - sql

I need to find the first missing date in a date column from plan_table table. which should not be in holiday_table or it should be belongs to any week end.
holiday_table stores all the holiday dates.
Plan_table contains dates. here we have to find the first missing date
Plan_id Date
1 10/2/2016
2 10/3/2016
3 10/6/2016
4 10/9/2016
5 10/10/2016
6 10/12/2016
7 10/13/2016
8 10/16/2016
Here the first missing date is 10/4/2016, but if this date is in holiday_table then we have to show 10/5/2016 or next first occurrence..
Please help me to write a query for the same.

you can use the LEAD analytic function like this
select d
from
(
select
date + 1 as d
from
(
select
date,
lead(date) over(order by date) as next_date
from
(
select date from plan_table
union
select date from holliday_table
)
order by date
)
where
trunc(date) + 1 < trunc(next_date)
order by d
)
where rownum = 1
;

Related

Get days of the week from a date range in Postgres

So I have the following table :
id end_date name number_of_days start_date
1 "2022-01-01" holiday1 1 "2022-01-01"
2 "2022-03-20" holiday2 1 "2022-03-20"
3 "2022-04-09" holiday3 1 "2022-04-09"
4 "2022-05-01" holiday4 1 "2022-05-01"
5 "2022-05-04" holiday5 3 "2022-05-02"
6 "2022-07-12" holiday6 9 "2022-07-20"
I want to check if a week falls in a holiday range.
So far I can select the holidays that overlap with my choosen week( week_start_date, week_end_date) , but i cant get the exact days in which the overlap happens.
this is the query i'm using, i want to add a mechanism to detect the DAYS OF THE WEEK IN WHICH THE OVERLAP HAPPENS
SELECT * FROM holidays
where daterange(CAST(start_date AS date), CAST(end_date as date), '[]') && daterange('2022-07-18', '2022-07-26','[]')
THE CURRENT QUERY RETURNS THE OVERLLAPPING HOLIDA, (id = 6), however i'm trying to get the exact DAYS OF THE WEEK in which the overlap happens ( in this case, it should be monday,tuesday , wednesday)
You can use the * operator with tsranges, generate a series of dates with the lower and upper dates and finally with to_char print the days of the week, e.g.
SELECT
id, name, start_date, end_date, array_agg(dow) AS days
FROM (
SELECT *,
trim(
to_char(
generate_series(lower(overlap), upper(overlap),'1 day'),
'Day')) AS dow
FROM holidays
CROSS JOIN LATERAL (SELECT tsrange(start_date,end_date) *
tsrange('2022-07-18', '2022-07-26')) t (overlap)
WHERE tsrange(start_date,end_date) && tsrange('2022-07-18', '2022-07-26')) j
GROUP BY id,name,start_date,end_date,number_of_days;
id | name | start_date | end_date | days
----+----------+------------+------------+----------------------------
6 | holiday6 | 2022-07-12 | 2022-07-20 | {Monday,Tuesday,Wednesday}
(1 row)
Demo: db<>fiddle

For each quarter between two dates, add rows quarter by quarter in SQL SERVER

I have a table, with types int, datetime, datetime:
id start date end date
-- ---------- ----------
1 2019-04-02 2020-09-17
2 2019-08-10 2020-08-10
Here is create/insert:
CREATE TABLE dbo.something
(
id int,
[start date] datetime,
[end date] datetime
);
INSERT dbo.something(id,[start date],[end date])
VALUES(1,'20190402','20200917'),(2,'20190810','20200810');
What is a SQL query that can produce these results:
id Year Quarter
-- ---- ----------
1 2019 2
1 2019 3
1 2019 4
1 2020 1
1 2020 2
1 2020 3
2 2019 3
2 2019 4
2 2020 1
2 2020 2
2 2020 3
Just use a recursive CTE. This version switches to counting quarters from year 0:
with cte as (
select id,
year(start_date) * 4 + datepart(quarter, start_date) - 1 as yyyyq,
year(end_date) * 4 + datepart(quarter, end_date) - 1 as end_yyyyq
from t
union all
select id, yyyyq + 1, end_yyyyq
from cte
where yyyyq < end_yyyyq
)
select id, yyyyq / 4 as year, (yyyyq % 4) + 1 as quarter
from cte;
Here is a db<>fiddle.
If you cannot make another reference table/etc, you can use DATEDIFF (and DATEPART) using quarters, and then some simple date arithmetic.
The logic below is simply to find, for each startdate, the first quarter and then the number of additional quarters to get to the maximum. Then do a SELECT where the additional quarters are added to the startdate, to get each quarter.
The hardest part of the query to understand imo is the WITH numberlist section - all this does is generate a series of integers between 0 and the maximum number of quarters difference. If you already have a numbers table, you can use that instead.
Key code part is below, and here's a full DB_Fiddle with some additional test data.
CREATE TABLE #yourtable (id int, startdate date, enddate date)
INSERT INTO #yourtable (id, startdate, enddate) VALUES
(1, '2019-04-02', '2020-09-17'),
(2, '2019-08-10', '2020-08-20')
; WITH number_list AS
-- list of ints from 0 to maximum number of quarters
(SELECT n
FROM (SELECT ones.n + 10*tens.n AS n
FROM (VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) ones(n),
(VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) tens(n)
) AS a
WHERE n <= (SELECT MAX(DATEDIFF(quarter,startdate,enddate)) FROM #yourtable)
)
SELECT id,
YEAR(DATEADD(quarter, number_list.n, startdate)) AS [Year],
DATEPART(quarter, DATEADD(quarter, number_list.n, startdate)) AS [Quarter]
FROM (SELECT id, startdate, DATEDIFF(quarter,startdate,enddate) AS num_additional_quarters FROM #yourtable) yt
CROSS JOIN number_list
WHERE number_list.n <= yt.num_additional_quarters
DROP TABLE #yourtable
First create a date dimension table which contains date, corresponding quarter and year. Then use below query to get the result. Tweak column and table name according to your schema.
with q_date as
(
select 1 as id, '2019-04-02' :: date as start_date, '2020-09-17' :: date as end_date
UNION ALL
select 2 as id, '2019-08-10' :: date as start_date, '2020-08-10' :: date as end_date
)
select qd.id, dd.calendar_year, dd.calendar_quarter_number
from dim_date dd, q_date qd
where dd.date_dmk between qd.start_date and qd.end_date
group by qd.id, dd.calendar_year, dd.calendar_quarter_number
order by qd.id, dd.calendar_year, dd.calendar_quarter_number;

Split single row into multiple rows based on week of the month in date

I have a table like this
Id Valid_From Valid_To
9744 24/06/2019 07/07/2019
9745 12/08/2019 31/12/9999
I would like to split this table into multiple rows based on the week like this by joining to the date table
Id Valid_from Valid_To Month Week
9744 24/06/2019 07/07/2019 June 4
9744 24/06/2019 07/07/2019 July 1
9744 24/06/2019 07/07/2019 July 2
9745 12/08/2019 31/12/9999 August 2
9745 12/08/2019 31/12/9999 August 3
9745 12/08/2019 31/12/9999 August 4
In this case there will be 3 rows as the valid from and valid two falls between these 3 weeks for ID - 9744
For ID - 9745 the Valid_to date is infinity so we need to just take all the week in the current month from the valid_from date
I then just need to append the output with Month and the Week number
Can someone help me to write a query to have this output?
Thanks
You mention a "date" table. If you have one then you can use a join like this:
select distinct t.id, valid_from, t.valid_to, d.month, d.week
from yourtable t join
date d
on d.date >= t.valid_from and
d.date <= t.valid_to;
If I understand your question right, you need to list all month names and week numbers of these months' existing between valid_from and valid_to dates. I did it by following query:
SELECT
Q.ID,
Q.VALID_FROM,
Q.VALID_TO,
Q.MONTH_NAME,
WEEK_NUMBER
FROM
(
SELECT
CEIL((Q.DATES_BETWEEN_INTERVAL - FIRST_DAY_OF_MONTH + 1) / 7) WEEK_NUMBER,
TO_CHAR(Q.DATES_BETWEEN_INTERVAL, 'MONTH', 'NLS_DATE_LANGUAGE = American') MONTH_NAME,
Q.*
FROM
(
SELECT
LEVEL + S.VALID_FROM DATES_BETWEEN_INTERVAL,
TRUNC(LEVEL + S.VALID_FROM, 'MONTH') FIRST_DAY_OF_MONTH,
S.* FROM
(
SELECT T.*,
(CASE WHEN EXTRACT(YEAR FROM T.VALID_TO) = 9999 THEN LAST_DAY(T.VALID_FROM) ELSE T.VALID_TO END) - T.VALID_FROM DAYS_COUNT
FROM AAA_TABLE T
) S
CONNECT BY LEVEL <= S.DAYS_COUNT
) Q
) Q
GROUP BY
Q.ID,
Q.VALID_FROM,
Q.VALID_TO,
Q.MONTH_NAME,
WEEK_NUMBER
ORDER BY
Q.ID,
Q.VALID_FROM,
Q.VALID_TO,
Q.MONTH_NAME,
WEEK_NUMBER;
But there must be 5th week if the date greater than 28th day of month. Hope this will help you.

sql Select Earliest Date Multiple Rows

I have the following data:
id from_date to_date empty
1 24/03/2016 01/04/2016 Y
1 01/04/2016 23/06/2016 Y
1 05/08/2016 01/04/2017 Y
1 01/04/2017 01/04/2018 Y
1 01/04/2018 01/04/2019 Y
The current date falls between 01/04/2018 and 01/04/2019 however, the earliest consecutive date is 05/08/2016. How can I write an sql script to pick up the earliest from date for the period that includes today.
Is this possible without creating a temporary table and updating the from date for each id? where the from_date = to_date for the previous row.
Hope that all makes sense.
Thanks
Iain
You seem to want to group the values together. Here is one method to get the periods of the continuous dates:
select id, min(from_date), max(to_date)
from (select t.*,
sum(case when prev_to_date = to_date then 1 else 0 end) over (partition by id) as grp
from (select t.*,
lag(to_date) over (partition by id order by from_date) as prev_to_date
from t
) t
) t
group by id, grp;
For filtering, you can add:
having current_date >= min(from_date) and current_date <= max(to_date)

Oracle SQL overlap between begin date and end date in 2 or more records

Database my_table:
id seq start_date end_date
1 1 01-01-2017 02-01-2017
1 2 07-01-2017 09-01-2017
1 3 11-01-2017 11-01-2017
2 1 20-01-2017 20-01-2017
3 1 01-02-2017 02-02-2017
3 2 03-02-2017 04-02-2017
3 3 08-01-2017 09-02-2017
3 4 09-01-2017 10-02-2017
3 5 10-01-2017 12-02-2017
My requirement is to get the first date (normally seq 1 start date) and end date (normally last seq end date) and the number of dates occurred during all seq for each unique ID.
Date occurred:
id 1 2 3
01-01-2017 20-01-2017 01-02-2017
02-01-2017 02-02-2017
07-01-2017 03-02-2017
08-01-2017 04-02-2017
09-01-2017 08-02-2017
11-01-2017 09-02-2017
10-02-2017
11-02-2017
12-02-2017
total 6 1 9
Here is the result I want:
id start_date end_date num_date
1 01-01-2017 11-01-2017 6
2 20-01-2017 20-01-2017 1
3 01-02-2017 12-02-2017 9
I have tried
SELECT id
, MIN(start_date)
, MAX(end_date)
, SUM(end_date - start_date + 1)
FROM my_table
GROUP BY id
and this SQL statement work fine in id 1 and 2 since there is no overlap date between begin date and end date. But for id 3, the result num_date is 11. Could you please suggest the SQL statement to solve this problem? Thank you.
One more question: The date in database is in datetime format. How do I convert it to date. I tried to use TRUNC function but it sometimes convert date to yesterday instead.
You need to count how many times an end_date equals the following start_date. For this you need to use the lag() or the lead() analytic function. You can use a case expression for the comparison, but alas you can't wrap the case expression within a COUNT or SUM in the same query; you need a subquery and an outer query.
Something like this; not tested, since you didn't provide CREATE TABLE and INSERT statements to recreate your sample data.
select id, min(start_date) as start_date, max(end_date) as end_date,
sum(end_date - start_date + 1 - flag) as num_days
from ( select id, start_date, end_date,
case when start_date = lag(end_date)
over (partition by id order by end_date) then 1
else 0 end as flag
from my_table
)
group by id;
SELECT id,
MIN( start_date ) AS start_date,
MAX( end_date ) AS end_date,
SUM( end_date - start_date + 1 ) AS num_days
FROM (
SELECT id,
GREATEST(
start_date,
COALESCE(
LAG( end_date ) OVER ( PARTITION BY id ORDER BY seq ) + 1,
start_date
)
) AS start_date,
end_date
FROM your_table
)
WHERE start_date <= end_date
GROUP BY id;