Max date removal and uaing last buisness date in oracle sql - sql

Select max( date) from t1 t where a.calendar_ date= t.date
This query creating issue when month ends on weekend or on holiday. This query joins with another table so I need a query by using which I can get correct date.

"On holiday" is hard because they are regional and change from year to year. You should keep a table full of dates that are holidays so that you can exclude them with something like:
Select max(date)
from t1 t
LEFT JOIN holidays h on t.date = h.date
where
h.date IS NULL AND --not a holiday
(1 + TRUNC (t.date) - TRUNC (t.date, 'IW')) NOT IN (6,7) AND -- not a weekend, regardless of region
a.calendar_date= t.date
ps; I've no idea where your a alias came from

Related

Get list of dates that falls in End Of each Month?

I need to get end of each month for the past 3 yrs from the current date excluding statutory holidays and weekends using table1 and table2. Table1 has all the dates ranging from year 2025-2017. Table2 has all the statutory holidays for the years ranging from 2025-2017.
How to create SQL script for to attain this result? Any suggestions could help. Expected result would be list of date last 3yrs of endofmonth excluding statutory holidays and weekends.
Table 1 has 2 columns, DateId and FullDate column
DateID Fulldate
1010392 2019-12-1
1010393 2019-12-2
1010394 2019-12-3
1010395 2019-12-4
.
.
101086 2019-12-31
Table 2 has 2 columns, DateId and Statutory_Holidays
Date ID Stat_Holidays
101085 2019-12-25
101086 2019-12-26
And the returned results should look like
WeekDay_FullDate_Past3yrs
2019-12-31
2020-1-31
2020-2-28
2020-3-31
Tried the below:
select * from
( select a.Date from Table1 a where a.Date <=
'20221215' and a.Date >= DATEADD (YEAR, -3, getdate()) ) as t1
join
( select EOMONTH(a.Date) as Date from Table1 a where a.Date <= '20221215' and a.Date >= DATEADD (YEAR, -3, getdate()) ) as t2 on t1.Date = t2.Date
tried the solution from the below link it dosen't solve my issue. I'm looking to get list of last workday of a month(excluding weekends and holiday) for the past 3yrs
SQL Server - Get Last Business Data excluding holidays and Weekends
You can group by month and year and take the max date (excluding holidays and weekends):
SET DATEFIRST 1;
DECLARE #CurrentDate DATE = '20221215';
WITH cte
AS
(
SELECT MAX(Date ) as EOMDate
FROM Table1
WHERE DATEPART(weekday,Date) NOT IN (6,7)
AND Date NOT IN (SELECT Date FROM Table2)
GROUP BY YEAR(Date),MONTH(Date)
)
SELECT *
FROM cte
WHERE cte.EOMDate BETWEEN DATEADD(YEAR,-3,#CurrentDate) AND #CurrentDate;
This should work and give you the last working day for each month in your main table. Just filter by the desired time period:
SELECT TOP 1 WITH TIES FullDate
FROM Table1
WHERE FullDate NOT IN (SELECT Stat_Holidays FROM Table2) -- not holiday
AND DATEPART(weekday, FullDate) NOT IN (7, 1) -- not saturday and sunday
ORDER BY DENSE_RANK() OVER(PARTITION BY YEAR(FullDate), MONTH(FullDate) ORDER BY FullDate DESC)
Check this with your table name and column names.
select year(dates) _Year ,month(dates) _Month,EOMONTH(dates) endofMOnth from tabledate1 where DATENAME(DW, dates) not in ('Saturday','Sunday')
and EOMONTH(dates) not in (select holidaydate from tableholidays)
Group by year(dates),month(dates),EOMONTH(dates)
order by year(dates) ,month(dates)

Counting Biz days for each row of database

I have a table with each row containing a start and end date with timestamp format and need to filter them by the number of business days between the start and end date.
Based on some of the solutions posted here, I created a separate table with all days and marked them with a boolean field like this:
CREATE TABLE tbl_holiday (h_date TIMESTAMP, is_holiday BOOLEAN)
Is it possible to write a query that filters by count days between start_date and date_date that has _is_holiday as False?
My database is Impala.
You would typically join the original table with the holiday table with inequality conditions on the start and end date, aggregate, and finally filter in a having clause by the sum of business days against your target value:
select t.id, t.start_date, t.end_date
from mytable t
inner join tbl_holiday h on h.hdate between t.start_date and t.end_date
group by t.id, t.start_date, t.end_date
having sum(cast(is_holiday as int)) = :no_of_business_days

Oracle - Split a record into multiple records

I have a schedule table for each month schedule. And this table also has days off within that month. I need a result set that will tell working days and off days for that month.
Eg.
CREATE TABLE SCHEDULE(sch_yyyymm varchar2(6), sch varchar2(20), sch_start_date date, sch_end_date date);
INSERT INTO SCHEDULE VALUES('201703','Working Days', to_date('03/01/2017','mm/dd/yyyy'), to_date('03/31/2017','mm/dd/yyyy'));
INSERT INTO SCHEDULE VALUES('201703','Off Day', to_date('03/05/2017','mm/dd/yyyy'), to_date('03/07/2017','mm/dd/yyyy'));
INSERT INTO SCHEDULE VALUES('201703','off Days', to_date('03/08/2017','mm/dd/yyyy'), to_date('03/10/2017','mm/dd/yyyy'));
INSERT INTO SCHEDULE VALUES('201703','off Days', to_date('03/15/2017','mm/dd/yyyy'), to_date('03/15/2017','mm/dd/yyyy'));
Using SQL or PL/SQL I need to split the record with Working Days and Off Days.
From above records I need result set as:
201703 Working Days 03/01/2017 - 03/04/2017
201703 Off Days 03/05/2017 - 03/10/2017
201703 Working Days 03/11/2017 - 03/14/2017
201703 Off Days 03/15/2017 - 03/15/2017
201703 Working Days 03/16/2017 - 03/31/2017
Thank You for your help.
Edit: I've had a bit more of a think, and this approach works fine for your insert records above - however, it misses records where there are not continuous "off day" periods. I need to have a bit more of a think and will then make some changes
I've put together a test using the lead and lag functions and a self join.
The upshot is you self-join the "Off Days" onto the existing tables to find the overlaps. Then calculate the start/end dates on either side of each record. A bit of logic then lets us work out which date to use as the final start/end dates.
SQL fiddle here - I used Postgres as the Oracle function wasn't working but it should translate ok.
select sch,
/* Work out which date to use as this record's Start date */
case when prev_end_date is null then sch_start_date
else off_end_date + 1
end as final_start_date,
/* Work out which date to use as this record's end date */
case when next_start_date is null then sch_end_date
when next_start_date is not null and prev_end_date is not null then next_start_date - 1
else off_start_date - 1
end as final_end_date
from (
select a.*,
b.*,
/* Get the start/end dates for the records on either side of each working day record */
lead( b.off_start_date ) over( partition by a.sch_start_date order by b.off_start_date ) as next_start_date,
lag( b.off_end_date ) over( partition by a.sch_start_date order by b.off_start_date ) as prev_end_date
from (
/* Get all schedule records */
select sch,
sch_start_date,
sch_end_date
from schedule
) as a
left join
(
/* Get all non-working day schedule records */
select sch as off_sch,
sch_start_date as off_start_date,
sch_end_date as off_end_date
from schedule
where sch <> 'Working Days'
) as b
/* Join on "Off Days" that overlap "Working Days" */
on a.sch_start_date <= b.off_end_date
and a.sch_end_date >= b.off_start_date
and a.sch <> b.off_sch
) as c
order by final_start_date
If you had a dates table this would have been easier.
You can construct a dates table using a recursive cte and join on to it. Then use the difference of row number approach to classify rows with same schedules on consecutive dates into one group and then get the min and max of each group which would be the start and end dates for a given sch. I assume there are only 2 sch values Working Days and Off Day.
with dates(dt) as (select date '2017-03-01' from dual
union all
select dt+1 from dates where dt < date '2017-03-31')
,groups as (select sch_yyyymm,dt,sch,
row_number() over(partition by sch_yyyymm order by dt)
- row_number() over(partition by sch_yyyymm,sch order by dt) as grp
from (select s.sch_yyyymm,d.dt,
/*This condition is to avoid a given date with 2 sch values, as 03-01-2017 - 03-31-2017 are working days
on one row and there is an Off Day status for some of these days.
In such cases Off Day would be picked up as sch*/
case when count(*) over(partition by d.dt) > 1 then min(s.sch) over(partition by d.dt) else s.sch end as sch
from dates d
join schedule s on d.dt >= s.sch_start_date and d.dt <= s.sch_end_date
) t
)
select sch_yyyymm,sch,min(dt) as start_date,max(dt) as end_date
from groups
group by sch_yyyymm,sch,grp
I couldn't get the recursive cte running in Oracle. Here is a demo using SQL Server.
Sample Demo in SQL Server

How to count records for each day in a range (including days without records)

I'm trying to refine this question a little since I didn't really ask correctly last time. I am essentially doing this query:
Select count(orders)
From Orders_Table
Where Order_Open_Date<=To_Date('##/##/####','MM/DD/YYYY')
and Order_Close_Date>=To_Date('##/##/####','MM/DD/YYYY')
Where ##/##/#### is the same day. In essence this query is designed to find the number of 'open' orders on any given day. The only problem is I'm wanting to do this for each day of a year or more. I think if I knew how to define the ##/##/#### as a variable and then grouped the count by that variable then I could get this to work but I'm not sure how to do that-or there may be another way as well. I am currently using Oracle SQL on SQL developer. Thanks for any input.
You could use a "row generator" technique like this (edited for Hogan's comments):
Select RG.Day,
count(orders)
From Orders_Table,
(SELECT trunc(SYSDATE) - ROWNUM as Day
FROM (SELECT 1 dummy FROM dual)
CONNECT BY LEVEL <= 365
) RG
Where RG.Day <=To_Date('##/##/####','MM/DD/YYYY')
and RG.Day >=To_Date('##/##/####','MM/DD/YYYY')
and Order_Open_Date(+) <= RG.Day
and Order_Close_Date(+) >= RG.Day - 1
Group by RG.Day
Order by RG.Day
This should list each day of the previous year with the corresponding number of orders
Lets say you had a table datelist with a column adate
aDate
1/1/2012
1/2/2012
1/3/2012
Now you join that to your table
Select *
From Orders_Table
join datelist on Order_Open_Date<=adate and Order_Close_Date>=adate
This gives you a list of all the orders you care about, now you group by and count
Select aDate, count(*)
From Orders_Table
join datelist on Order_Open_Date<=adate and Order_Close_Date>=adate
group by adate
If you want to pass in a parameters then just generate the dates with a recursive cte
with datelist as
(
select #startdate as adate
UNION ALL
select adate + 1
from datelist
where (adate + 1) <= #lastdate
)
Select aDate, count(*)
From Orders_Table
join datelist on Order_Open_Date<=adate and Order_Close_Date>=adate
group by adate
NOTE: I don't have an Oracle DB to test on so I might have some syntax wrong for this platform, but you get the idea.
NOTE2: If you want all dates listed with 0 for those that have nothing use this as your select statement:
Select aDate, count(Order_Open_Date)
From Orders_Table
left join datelist on Order_Open_Date<=adate and Order_Close_Date>=adate
group by adate
If you want only one day you can query using TRUNC like this
select count(orders)
From orders_table
where trunc(order_open_date) = to_date('14/05/2012','dd/mm/yyyy')

in sql, calculating date parts versus date lookup table in group queries

many queries are by week, month or quarter when the base table date is either date or timestamp.
in general, in group by queries, does it matter whether using
- functions on the date
- a day table that has extraction pre-calculated
note: similar question as DATE lookup table (1990/01/01:2041/12/31)
for example, in postgresql
create table sale(
tran_id serial primary key,
tran_dt date not null default current_date,
sale_amt decimal(8,2) not null,
...
);
create table days(
day date primary key,
week date not null,
month date not null,
quarter date non null
);
-- week query 1: group using funcs
select
date_trunc('week',tran_dt)::date - 1 as week,
count(1) as sale_ct,
sum(sale_amt) as sale_amt
from sale
where date_trunc('week',tran_dt)::date - 1 between '2012-1-1' and '2011-12-31'
group by date_trunc('week',tran_dt)::date - 1
order by 1;
-- query 2: group using days
select
days.week,
count(1) as sale_ct,
sum(sale_amt) as sale_amt
from sale
join days on( days.day = sale.tran_dt )
where week between '2011-1-1'::date and '2011-12-31'::date
group by week
order by week;
to me, whereas the date_trunc() function seems more organic, the the days table is easier to use.
is there anything here more than a matter of taste?
-- query 3: group using instant "immediate" calendar table
WITH calender AS (
SELECT ser::date AS dd
, date_trunc('week', ser)::date AS wk
-- , date_trunc('month', ser)::date AS mon
-- , date_trunc('quarter', ser)::date AS qq
FROM generate_series( '2012-1-1' , '2012-12-31', '1 day'::interval) ser
)
SELECT
cal.wk
, count(1) as sale_ct
, sum(sa.sale_amt) as sale_amt
FROM sale sa
JOIN calender cal ON cal.dd = sa.tran_dt
-- WHERE week between '2012-1-1' and '2011-12-31'
GROUP BY cal.wk
ORDER BY cal.wk
;
Note: I fixed an apparent typo in the BETWEEN range.
UPDATE: I used Erwin's recursive CTE to squeeze out the duplicated date_trunc(). Nested CTE galore:
WITH calendar AS (
WITH RECURSIVE montag AS (
SELECT '2011-01-01'::date AS dd
UNION ALL
SELECT dd + 1 AS dd
FROM montag
WHERE dd < '2012-1-1'::date
)
SELECT mo.dd, date_trunc('week', mo.dd + 1)::date AS wk
FROM montag mo
)
SELECT
cal.wk
, count(1) as sale_ct
, sum(sa.sale_amt) as sale_amt
FROM sale sa
JOIN calendar cal ON cal.dd = sa.tran_dt
-- WHERE week between '2012-1-1' and '2011-12-31'
GROUP BY cal.wk
ORDER BY cal.wk
;
Yes, it is more than a matter of taste. The performance of the query depends on the method.
As a first approximation, the functions should be faster. They don't require joins, doing the read in a single table scan.
However, a good optimizer could make effective use of a lookup table. It would know the distribution of the target values. And, an in memory join could be quite fast.
As a database design, I think having a calendar table is very useful. Some information such as holidays just isn't going to work as a function. However, for most ad hoc queries the date functions are fine.
1. Your expression:
... between '2012-1-1' and '2011-12-31'
doesn't work. Basic BETWEEN requires the left argument to be less than or equal to the right argument. Would have to be:
... BETWEEN SYMMETRIC '2012-1-1' and '2011-12-31'
Or it's just a typo and you mean something like:
... BETWEEN '2011-1-1' and '2011-12-31'
It's unclear to me, what your queries are supposed to retrieve. I'll assume you want all weeks (Monday to Sunday) that start in 2011 for the rest of this answer. This expression generates exactly that in less than a microsecond on modern hardware (works for any year):
SELECT generate_series(
date_trunc('week','2010-12-31'::date) + interval '7d'
,date_trunc('week','2011-12-31'::date) + interval '6d'
, '1d')::date
*Note that the ISO 8601 definition of the "first week of a year is slightly different.
2. Your second query does not work at all. No GROUP BY?
3. The question you link to did not deal with PostgreSQL, which has outstanding date / timestamp support. And it has generate_series() which can obviate the need for a separate "days" table in most cases - as demonstrated above. Your query would look like this:
In the meantime #wildplasser provided an example query that was supposed to go here.
By popular* demand, a recursive CTE version - which is actually not that far from being a serious alternative!
* and by "popular" I mean #wildplasser's very serious request.
WITH RECURSIVE days AS (
SELECT '2011-01-01'::date AS dd
,date_trunc('week', '2011-01-01'::date )::date AS wk
UNION ALL
SELECT dd + 1
,date_trunc('week', dd + 1)::date AS wk
FROM days
WHERE dd < '2011-12-31'::date
)
SELECT d.wk
,count(*) AS sale_ct
,sum(s.sale_amt) AS sale_amt
FROM days d
JOIN sale s ON s.tran_dt = d.dd
-- WHERE d.wk between '2011-01-01' and '2011-12-31'
GROUP BY 1
ORDER BY 1;
Could also be written as (compare to #wildplasser's version):
WITH RECURSIVE d AS (
SELECT '2011-01-01'::date AS dd
UNION ALL
SELECT dd + 1 FROM d WHERE dd < '2011-12-31'::date
), days AS (
SELECT dd, date_trunc('week', dd + 1)::date AS wk
FROM d
)
SELECT ...
4. If performance is of the essence, just make sure, that you do not apply functions or calculations to the values of your table. This prohibits the use of indexes and is generally very slow, because every row has to be processed. That's why your first query is going to suck with big table. When ever possible, apply calculations to the values you filter with, instead.
Indexes on expressions are one way around this. If you had an index like
CREATE INDEX sale_tran_dt_week_idx ON sale (date_trunc('week', tran_dt)::date);
.. your first query could be very fast again - at some cost for write operations for index maintenance.