How to exclude holidays between two dates? - oracle

I have two dates and I have to find out the number of Sundays and holidays fall between those two dates. Can I do this using BETWEEN? If so, how?
SELECT date1, date2, trunc(deposit_date - transaction_date) TOTAL
FROM Table_Name FULL OUTER JOIN Holidays ON date2 = hdate
WHERE hdate IN (date1, date2)
Using this I can definitely check whether there is a holiday on either of the two days, i.e. date1 or date2 but what I am not able to find out that whether there lies a holiday or a Sunday between these two dates. Help!

The solution you've posted is horribly inefficient; you can do all of this in a single SQL statement:
Firstly generate all possible dates between the two you have:
select trunc(:min_date) + level - 1
from dual
connect by level <= trunc(:min_date) - trunc(:max_date)
Then use your HOLIDAY table to restrict to what you want:
with all_dates as (
select trunc(:min_date) + level - 1 as the_date
from dual
connect by level <= trunc(:min_date) - trunc(:max_date)
)
select count(*)
from all_dates a
left outer join holiday b
on a.the_date = b.hdate
where b.hdate is null
and to_char(a.the_date, 'DY') <> 'SUN'

If you want to check if hdate is between the two dates you can query using
where hdate between date1 and date2
If you want to check if hdate is on the same day as date1 or date two you can query like this
where trunc(hdate) in (trunc(date1) ,trunc(date2))
The trunc function removed the time.

You should create a table with the holidays and maintain it on your own.
CREATE TABLE holidays
(
holiday VARCHAR2(100)
, d_date DATE
);
INSERT INTO holidays VALUES ('National Developer Day', DATE'2013-06-01');
SELECT *
FROM holidays;
-- National Developer Day 2013-06-01 00:00:00
The rest is just a matter of a SQL statment
Scenario 1: EXISTS
SELECT COUNT
(
CASE
WHEN TRIM(TO_CHAR(d.start_date_level, 'DAY')) = 'SUNDAY'
OR CASE
WHEN EXISTS (SELECT 1 FROM holidays h WHERE d.start_date_level = h.d_date)
THEN 1
ELSE NULL
END = 1
THEN 1
ELSE NULL
END
) AS holiday_check
FROM
(
SELECT start_date + (LEVEL - 1) AS start_date_level
FROM
(
SELECT start_date, end_date, end_date - start_date AS diff_date
FROM
(
SELECT TRUNC(ADD_MONTHS(SYSDATE, -2)) AS start_date
, TRUNC(SYSDATE) AS end_date
FROM DUAL
)
)
CONNECT BY
LEVEL <= (diff_date + 1)
) d
Scenario 2: LEFT JOIN
SELECT COUNT
(
CASE
WHEN TRIM(TO_CHAR(d.start_date_level, 'DAY')) = 'SUNDAY'
OR h.d_date IS NOT NULL
THEN 1
ELSE NULL
END
) AS holiday_check
FROM
(
SELECT start_date + (LEVEL - 1) AS start_date_level
FROM
(
SELECT start_date, end_date, end_date - start_date AS diff_date
FROM
(
SELECT TRUNC(ADD_MONTHS(SYSDATE, -2)) AS start_date
, TRUNC(SYSDATE) AS end_date
FROM DUAL
)
)
CONNECT BY
LEVEL <= (diff_date + 1)
) d
LEFT JOIN holidays h
ON d.start_date_level = h.d_date
9 Sundays + 1 "National Developer Day" = 10

CREATE OR REPLACE FUNCTION workdays (dt1 DATE, dt2 DATE) RETURN NUMBER IS
weekday_count NUMBER := 0;
date1 DATE := dt1;
date2 DATE := dt2;
cur_dt date;
holiday_count number;
begin
if date1 = date2 then
return 0;
end if;
cur_dt := transaction_date;
while cur_dt <= date2 loop
if cur_dt = date2 then
null;
else
SELECT count(*) INTO holiday_count
FROM holiday
WHERE hdate = cur_dt;
IF holiday_count = 0 THEN
IF to_char(cur_dt,'DY') NOT IN ('SUN') THEN
weekday_count := weekday_count + 1;
END IF;
END IF;
END IF;
cur_dt := cur_dt +1;
END LOOP;
RETURN weekday_count;
END;
And then I queried my database and got the right results. Do post if you have an optimal solution for this.

Here is an even better and efficient solution to the problem,
SELECT A.ID,
COUNT(A.ID) AS COUNTED
FROM tableA A
LEFT JOIN TableB B
ON A.tableB_id=B.id
LEFT JOIN holiday C
ON TRUNC(C.hdate) BETWEEN (TRUNC(a.date1) +1) AND TRUNC(B.date2)
WHERE c.hdate IS NOT NULL
GROUP BY A.ID;
where TableA contains date1 and tableB contains date2. Holiday contains the list of holidays and Sundays. And this query excludes 'date1' from the count.
RESULT LOGIC
trunc(date2) - trunc(date1) = x
x - result of the query

Make a table T$HOLIDAYS with your holidays (HDATE column). These dates will be excluded from calculation of working days within given period (sdate is start date and edate end date of period). Here is the function that calculates working days within given period excluding holidays, saturdays and sundays:
CREATE OR REPLACE FUNCTION WorkingDays(sdate IN DATE,edate IN DATE) RETURN NUMBER IS
days NUMBER;
BEGIN
WITH dates AS (SELECT sdate+LEVEL-1 AS d FROM DUAL CONNECT BY LEVEL<=edate-sdate+1)
SELECT COUNT(*) INTO days
FROM dates
WHERE d NOT IN (SELECT hdate FROM t$holidays) --exclude holidays
AND TO_CHAR(d,'D') NOT IN (6,7); --exclude saturdays + sundays
RETURN days;
END WorkingDays;
/

select sum(qq) from (
select case when to_number(to_char((trunc(sysdate-10) + level - 1),'D'))<=5 then 1 else 0 end as qq
from dual
connect by level <= trunc(sysdate) - trunc(sysdate-10))

Related

(Oracle 11g DB) Calculate Number of buisiness days between current time and a date while excluding holidays in a view

So I have this working SQL script that take a date and returns the age from current time to the given date excluding dates defined in a table called exclude dates
SELECT
COUNT(*)
FROM
(
SELECT
ROWNUM rnum
FROM
all_objects
WHERE
ROWNUM <= CAST(current_timestamp AS DATE) - to_date('&2') + 1
)
WHERE
to_char(to_date('&2') + rnum - 1, 'DY') NOT IN ( 'SAT', 'SUN' )
AND NOT EXISTS (
SELECT
NULL
FROM
exclude_dates
WHERE
no_work = trunc(to_date('&2') + rnum - 1)
);
I have a table called
TICKETS
that contains columns named
ID, UPDATED_AT
I want to create a view that uses the above script to return
ID, AGE
where age is the output of the script above.
You code has a few weaknesses.
There is no need for CAST(current_timestamp AS DATE).
If you need the current DATE then simply use TRUNC(SYSDATE)
You don't need to select from all_objects. Better use hierarchical query
SELECT LEVEL as rnum FROM dual CONNECT BY LEVEL <= ...
Using to_date('&2') without a format is usually bad. Either your input value is a string, then you should include the format, e.g. to_date('&2', 'YYYY-MM-DD') or your input value is a DATE, then simply use &2 - never use TO_DATE() on a value which is already a DATE!
Final query could be this one - assuming input parameter is a DATE value:
WITH t AS (
SELECT LEVEL as d
FROM dual
CONNECT BY LEVEL <= TRUNC(SYSDATE) - the_day)
SELECT COUNT(*) AS buisiness_days
FROM t
WHERE TO_CHAR(the_day + d - 1, 'DY', 'NLS_DATE_LANGUAGE = american') NOT IN ('SAT', 'SUN')
AND NOT EXISTS (
SELECT 'x'
FROM exclude_dates
WHERE no_work = TRUNC(the_day + d - 1)
)
However, for me it is not clear how you want to provide this as a view! You would need to create a separate view for each input date, or at least create a new view every day.
I would suggest to create a function:
CREATE OR REPLACE FUNCTION buisiness_days(the_date IN DATE) RETURN INTEGER AS
ret INTEGER;
BEGIN
WITH t AS (
SELECT LEVEL as d
FROM dual
CONNECT BY LEVEL <= TRUNC(SYSDATE) - the_date)
SELECT COUNT(*) AS buisiness_days
INTO ret
FROM t
WHERE TO_CHAR(the_date + d - 1, 'DY', 'NLS_DATE_LANGUAGE = american') NOT IN ('SAT', 'SUN')
AND NOT EXISTS (
SELECT 'x'
FROM exclude_dates
WHERE no_work = TRUNC(the_date + d - 1)
);
RETURN ret;
END;
The function will return a list of dates between the date range you provide so the dates don't have to be stored in a table.
CREATE OR REPLACE TYPE nt_date IS TABLE OF DATE;
/
CREATE OR REPLACE FUNCTION generate_dates_pipelined(
p_from IN DATE,
p_to IN DATE
)
RETURN nt_date PIPELINED DETERMINISTIC
IS
v_start DATE := TRUNC(LEAST(p_from, p_to));
v_end DATE := TRUNC(GREATEST(p_from, p_to));
BEGIN
LOOP
PIPE ROW (v_start);
EXIT WHEN v_start >= v_end;
v_start := v_start + INTERVAL '1' DAY;
END LOOP;
RETURN;
END generate_dates_pipelined;
/
To exclude holidays you need to know what dates they fall on so there needs to be a holiday table.
create table holidays(
holiday_date DATE not null,
holiday_name VARCHAR2(20),
constraint holidays_pk primary key (holiday_date),
constraint is_midnight check ( holiday_date = trunc ( holiday_date ) )
);
INSERT into holidays (HOLIDAY_DATE,HOLIDAY_NAME)
WITH dts as (
select to_date('25-NOV-2021 00:00:00','DD-MON-YYYY HH24:MI:SS'), 'Thanksgiving 2021' from dual union all
select to_date('29-NOV-2021 00:00:00','DD-MON-YYYY HH24:MI:SS'), 'Hanukkah 2021' from dual
)
SELECT * from dts;
This query will provide the count of days between the range, number of working days and number of holidays in the range.
SELECT COUNT (*) AS total_days
, COUNT ( CASE
WHEN h.holiday_date IS NULL
AND d.column_value - TRUNC (d.column_value, 'IW') < 5
THEN 'Business Day'
END
) AS business_days
, COUNT (h.holiday_date) AS holidays
FROM generate_dates_pipelined (DATE '2021-11-01', DATE '2021-11-30') d
LEFT JOIN holidays h ON h.holiday_date = d.column_value;
This query will provide a list of dates excluding sat, sun and holidays that fall between the range.
SELECT
COLUMN_VALUE
FROM
TABLE(generate_dates_pipelined(DATE '2021-11-01',
DATE '2021-11-30')) c
where
to_char(COLUMN_VALUE, 'DY') NOT IN ('SAT', 'SUN')
AND NOT EXISTS (
SELECT 1
FROM holidays h
WHERE c.COLUMN_VALUE = h.holiday_date
);
You don't need a function or to use a row generator function and can calculate the number of business days:
CREATE VIEW business_day_ages (ID, AGE) AS
SELECT id,
( TRUNC( SYSDATE, 'IW' ) - TRUNC( updated_at, 'IW' ) ) * 5 / 7
-- Number of full weeks.
+ LEAST( SYSDATE - TRUNC( SYSDATE, 'IW' ), 5 )
-- Add part weeks at the end.
- LEAST( updated_at - TRUNC( updated_at, 'IW' ), 5 )
-- Subtract part weeks at the start.
- COALESCE(
( SELECT SUM(
LEAST(no_work + INTERVAL '1' DAY, SYSDATE)
- GREATEST(no_work, updated_at)
)
FROM exclude_dates
WHERE no_work BETWEEN TRUNC(updated_at) AND SYSDATE
),
0
)
-- Subtract the holiday days.
FROM tickets;
Or, if you are not calculating using part days then you can simplify it to:
CREATE OR REPLACE VIEW business_day_ages (ID, AGE) AS
SELECT id,
( TRUNC( SYSDATE, 'IW' ) - TRUNC( updated_at, 'IW' ) ) * 5 / 7
-- Number of full weeks.
+ LEAST( TRUNC(SYSDATE) - TRUNC( SYSDATE, 'IW' ), 5 )
-- Add part weeks at the end.
- LEAST( updated_at - TRUNC( updated_at, 'IW' ), 5 )
-- Subtract part weeks at the start.
- COALESCE(
( SELECT 1
FROM exclude_dates
WHERE no_work BETWEEN TRUNC(updated_at) AND TRUNC(SYSDATE)
),
0
)
-- Subtract the holiday days.
FROM tickets;
db<>fiddle here

How to get last workday before holiday in Oracle [duplicate]

This question already has answers here:
How to get the previous working day from Oracle?
(4 answers)
Closed 1 year ago.
need help for some oracle stuff ..
I need to get Day-1 from sysdate, holiday and weekend will be excluded .
And for holiday, we need to get the range to get the last workday before holiday.
The start date and end date will coming from my holiday table.
ex :
Holiday Table
HolidayName
Start_date
End_Date
holiday1
5th Aug'21
6th Aug'21
condition :
this query run on 9th Aug 2021
expected result :
4th Aug'21
I've tried some query and function but I just can't get what I need.
Thanks a lot for help!
Here's one way to do it.
select max(d) as last_workday
from (select trunc(sysdate)-level as d from dual connect by level < 30) prior_month
where to_char(d, 'DY') not in ('SAT','SUN')
and not exists (select holidayname from holiday_table
where prior_month.d between start_date and end_date)
;
Without seeing your Holiday table, it's hard to say how many days back you would need to look to find the last workday. If you have a holiday that lasts for more than 30 days, you'll need to change the 30 to a larger number.
You can use a simple case expression to determine what day of the week the start of your holiday is, then subtract a number of days based on that.
WITH
holiday (holidayname, start_date, end_date)
AS
(SELECT 'holiday1', DATE '2021-8-5', DATE '2021-8-6' FROM DUAL
UNION ALL
SELECT 'Christmas', DATE '2021-12-25', DATE '2021-12-26' FROM DUAL
UNION ALL
SELECT 'July 4th', DATE '2021-7-4', DATE '2021-7-5' FROM DUAL)
SELECT holidayname,
start_date,
end_date,
start_date - CASE TO_CHAR (start_date, 'Dy') WHEN 'Mon' THEN 3 WHEN 'Sun' THEN 2 ELSE 1 END AS prior_business_day
FROM holiday;
HOLIDAYNAME START_DATE END_DATE PRIOR_BUSINESS_DAY
______________ _____________ ____________ _____________________
holiday1 05-AUG-21 06-AUG-21 04-AUG-21
Christmas 25-DEC-21 26-DEC-21 24-DEC-21
July 4th 04-JUL-21 05-JUL-21 02-JUL-21
You can use a recursive sub-query factoring clause from this answer:
WITH start_date (dt) AS (
SELECT DATE '2021-05-02' FROM DUAL
),
days ( dt, day, found ) AS (
SELECT dt,
TRUNC(dt) - TRUNC(dt, 'IW'),
0
FROM start_date
UNION ALL
SELECT dt - CASE day WHEN 0 THEN 3 WHEN 6 THEN 2 ELSE 1 END,
CASE WHEN day IN (0, 6, 5) THEN 4 ELSE day - 1 END,
CASE WHEN h.start_date IS NULL THEN 1 ELSE 0 END
FROM days d
LEFT OUTER JOIN holidays h
ON ( dt - CASE day WHEN 0 THEN 3 WHEN 6 THEN 2 ELSE 1 END
BETWEEN h.start_date AND h.end_date )
WHERE found = 0
)
SELECT dt
FROM days
WHERE found = 1;
Which, for the sample data:
CREATE TABLE holidays (HolidayName, Start_date, End_Date) AS
SELECT 'holiday1', DATE '2021-08-05', DATE '2021-08-06' FROM DUAL;
Outputs:
DT
2021-08-04 00:00:00
db<>fiddle here
Don't know if it's very efficient. Did it just for fun
create table holidays (
holiday_name varchar2(100) primary key,
start_date date not null,
end_date date not null
)
/
Table created
insert into holidays (holiday_name, start_date, end_date)
values ('holiday1', date '2021-08-05', date '2021-08-06');
1 row inserted
with days_before(day, wrk_day) as
(select trunc(sysdate - 1) d,
case
when h.holiday_name is not null then 0
when to_char(trunc(sysdate - 1), 'D') in ('6', '7') then 0
else 1
end work_day
from dual
left join holidays h
on trunc(sysdate - 1) between h.start_date and h.end_date
union all
select db.day - 1,
case
when h.holiday_name is not null then 0
when to_char(db.day - 1, 'D') in ('6', '7') then 0
else 1
end work_day
from days_before db
left join holidays h
on db.day - 1 between h.start_date and h.end_date
where db.wrk_day = 0) search depth first by day set order_no
select day from days_before where wrk_day = 1;
DAY
-----------
04.08.2021

count Sunday in plsql between two dates :date1 and :date2

I know there is lot of work for this on forums but i tried many things but get error please I have two parameters in oracle reports :date1 and :date2 I want to check sunday and then return gives me how many Sundays in these two dates
function SUNDAY_CFormula return NUMBER is
start_date DATE := :DATE1;
end_date DATE := :DATE2;
A NUMBER;
begin
SELECT Count(*)
FROM (SELECT To_char(start_date + ( LEVEL - 1 ), 'fmday')INTO A
FROM DUAL;
CONNECT BY LEVEL <= end_date - start_date + 1)
WHERE A IN ( 'sunday' );
RETURN A;
end;
You could rewrite your function like below. It is safer to add 'nls_date_language = english' clause in your to_char function in order to make your function independent from your default environnment settings.
create or replace
function SUNDAY_CFormula (DATE1 date, DATE2 date) return NUMBER is
start_date DATE := DATE1;
end_date DATE := DATE2;
A NUMBER;
begin
SELECT Count(*) INTO A
FROM (
SELECT To_char(start_date + ( LEVEL - 1 ), 'fmday', 'nls_date_language = english') A
FROM DUAL
CONNECT BY LEVEL <= end_date - start_date + 1
) t
WHERE t.A IN ( 'sunday' );
RETURN A;
end;
/
You could even use below version to make your function more flexible about the two dates it takes as parameters, no matter if date1 is greater or less than date2.
create or replace
function SUNDAY_CFormula (DATE1 date, DATE2 date) return NUMBER is
start_date DATE := DATE1;
end_date DATE := DATE2;
A NUMBER;
begin
SELECT Count(*) INTO A
FROM (SELECT To_char(start_date + ( LEVEL - 1 ), 'fmday', 'nls_date_language = english') A
FROM DUAL
CONNECT BY LEVEL <= greatest(end_date, start_date) - least(end_date, start_date) + 1
) t
WHERE t.A IN ( 'sunday' );
RETURN A;
end;
/
As an alternative. I always try creating formulas for date range process rather than "iterating", I just do not like generating data to just throw it away. Yes, it is sometimes necessary, but not in this case. The following will accomplish what you want:
create or replace
function sunday_calc ( date1_in date
, date2_in date
, sun_in varchar2 default 'sun'
)
return number
is
sun_count integer;
begin
with date_range( start_date, end_date) as
( select trunc(least(date1_in,date2_in))
, trunc(greatest(date1_in,date2_in))
from dual
)
select floor((trunc(end_date) - trunc(next_day(start_date-1,sun_in))/7)) + 1
into sun_count
from date_range;
return sun_count;
end sunday_calc;
Note: Unfortunately the next_day function does not accept a NLS_DATE_LANGUAGE parameter, so I created a substitute. Sun_in parameter: include the name in target language corresponding to English day 'Sunday'
Curious about this compared to the function by #MDO I ran some tests on each. And they produced the same result; Except in some instances where the start date was greater that the end date there was a difference of 1. Comparing to actual calendar the formula was correct (see fiddle). But why, MDO's logic seems completely sound. At that point I just had to know why. Took awhile but there is a slight bug in her/his code. Turns out when the start date is greater then the end date their routine actually began looking at dates for the greatest date and moved forward. Thus changing the period look at the the greater of the dates to that date plus number of days. This is corrected by applying the least function to the "Select to_char(start_date...", results in:
create or replace
function sunday_cformula_r (date1 date, date2 date) return number is
start_date date := date1;
end_date date := date2;
a number;
begin
select count(*) into a
from (select to_char(least(end_date, start_date) + ( level - 1 ), 'fmday', 'nls_date_language = english') a
from dual
connect by level <= greatest(end_date, start_date) - least(end_date, start_date) + 1
) t
where t.a in ( 'sunday' );
return a;
end;

Inserting incrementing date into separate Select query

I have inherited the following query :-
SELECT
d.dt --date
,fuel --vchar
,SUM(revenue) revenue --number
FROM
(
SELECT
ADD_MONTHS(TRUNC(SYSDATE,'mm'),-ROWNUM + 1)-1 dt
FROM dual
) d
<<SNIP>>
WHERE rn = 1
GROUP BY
d.dt
,fuel;
This returns a summed amount for the previous month.
Is it possible to somehow loop this so that it returns results for specific historical days. A static example would be :-
SELECT
d.dt --date
,fuel --vchar
,SUM(revenue) revenue --number
FROM
(
SELECT to_date('31-08-2017', 'dd-MM-yyyy') dt
FROM dual
) d
<<SNIP>>
WHERE rn = 1
GROUP BY
d.dt
,fuel;
I've been able to write the code to generate the dates I'm interested in :-
DECLARE
startdate DATE := TO_DATE('31/08/2016','dd/mm/yyyy');
enddate DATE := trunc(SYSDATE,'MM') - 1;
usedate DATE := enddate;
BEGIN
LOOP
usedate := add_months(usedate,-1);
dbms_output.put_line(usedate);
EXIT WHEN usedate <= startdate;
END LOOP;
END;
...with usedate being the date in question, but I'm unclear how to use these dates in the original query.
You can try as below. Read comments inline.
DECLARE
startdate DATE := TO_DATE('31/08/2016','dd/mm/yyyy');
enddate DATE := TRUNC(SYSDATE,'MM') - 1;
usedate DATE := enddate;
--Record to hold resultset from the Select query.
type UserInfo IS record
(
dt DATE,
fuel VARCHAR2(100),
revnue NUMBER );
TYPE v_UserInfo
IS
TABLE OF USerinfo INDEX BY pls_integer;
UserRecord UserInfo;
BEGIN
LOOP
usedate := add_months(usedate,-1);
dbms_output.put_line(usedate);
EXIT WHEN usedate <= startdate;
--Using usedate in select statement
SELECT *
BULK COLLECT INTO UserRecord
from (
SELECT d.dt ,
fuel, --- This column is coming from another table
SUM(revenue) --- This column is coming from another table
FROM
( SELECT ADD_MONTHS(TRUNC(usedate,'mm'),-ROWNUM + 1)-1 dt FROM dual
) d); --Make join with the table having columns in select query
FOR i IN 1..UserRecord.count
loop
--Displaying records of select staement
dbms_ouput.put_line(UserRecord(i).dt ||UserRecord(i).fuel || UserRecord(i).revnue);
end loop;
END LOOP;
END;
PS: Not tested.
You missing the number of months between your start date and use date. Not sure how you were able to generate your dates with your code as usedate := add_months(usedate,-1); will always give you the static value = 28-FEB-18.
In my example I use a bit different start date, which I suggest for your testing:
SELECT start_date
, end_date
, add_months(use_date,-LEVEL) stop_date
, MONTHS_BETWEEN(use_date, start_date) months_diff -- increment number
FROM
(
SELECT TO_DATE('31/08/2017','dd/mm/yyyy') start_date
, trunc(SYSDATE,'MM')-1 end_date
, add_months(trunc(SYSDATE,'MM')-1,-1) use_date
FROM dual
)
CONNECT BY LEVEL <= MONTHS_BETWEEN(use_date, start_date) -- will run 6 times = months_diff --
/
START_DATE END_DATE STOP_DATE MONTHS_DIFF
--------------------------------------------------
31-AUG-17 31-MAR-18 31-JAN-18 6
31-AUG-17 31-MAR-18 31-DEC-17 6
31-AUG-17 31-MAR-18 30-NOV-17 6
31-AUG-17 31-MAR-18 31-OCT-17 6
31-AUG-17 31-MAR-18 30-SEP-17 6
31-AUG-17 31-MAR-18 31-AUG-17 6 <<-- stopped here as stop_date equals to start date

ORACLE Query Where condition based on system time

I have an Oracle query below:
select ltrim(ROUND((1-n/c)*100) || '%') as TOTAL
from (select count(*) c
from WA_SEW_TBL_EMP_INFO
WHERE SHIFT = 'Morning')
, (SELECT COUNT(*) n
FROM (SELECT S.BADGEID_FK
FROM WA_SEW_TBL_EMP_INFO S
, WA_GA_TBL_EMPLOYEES E
WHERE S.BADGEID_FK = E.BADGEID
AND S.STATUS = 'Attend'
AND S.SHIFT = 'Morning'
AND S.BADGEID_FK NOT IN ( SELECT EMPID
FROM WA_SEW_TBL_RESULTS
WHERE SYSTEM_DATE between to_date ('2017-08-31 07:00', 'YYYY-MM-DD HH24:MI')
and to_date ('2017-08-31 19:29', 'YYYY-MM-DD HH24:MI')
)
)
)
Shift have 2 type:
Night
Morning
Now I want oracle query detected if system time from 08:00 until 19:29 then set shift to be Morning, else Night.
means I want WHERE SHIFT = 'system time condition from08:00until19:29then set shift to be Morning, else Night'
Is it possible to do that?
This query uses the 'sssss' date mask which is the number of seconds past midnight. Then you can tell whether the time element is within your desired range.
SELECT EMPID,
case when to_number(to_char(system_date, 'sssss'))
between 28800 -- 08:00:00
and 70140 -- 19:29:00
then 'Morning'
else 'Night' as SHIFT
FROM WA_SEW_TBL_RESULTS
where trunc(system_date) = date '2017-08-31'
Inject this sub-query into your main query as you need.
Incidentally I have closed the date range with a bound of seconds for 19:29:00 as you specified. Maybe that should be 70199, to take the day shift up to 19:29:59 - it depends how precisely you track time.
I think you can work like this:
WITH TEST_DATETIME AS(
SELECT TO_DATE('2017/08/31 10:15:24','YYYY/MM/DD HH24:MI:SS') DT FROM DUAL
UNION ALL
SELECT TO_DATE('2017/08/31 19:40:24','YYYY/MM/DD HH24:MI:SS') FROM DUAL
UNION ALL
SELECT SYSDATE FROM DUAL
)
SELECT CASE WHEN to_char(DT, 'hh24:mi:ss') > '08:00:00' AND to_char(DT, 'hh24:mi:ss') < '19:29:00' THEN 'Morning' ELSE 'Night' END AS NOON
FROM TEST_DATETIME
Easiest way is to write function returning day/night shift and include it in your query
create or replace function find_shift(dat in date) return varchar2
is
dat2 date;
treshhold1 date;
treshhold2 date;
ret varchar2(10);
begin
dat2:=to_date(to_char(dat, 'HH24:MI'),'HH24:MI');
treshhold1 := to_date('08:00','HH24:MI');
treshhold2 := to_date('19:29','HH24:MI');
if dat2>=treshhold1 and dat2<= treshhold2 then
ret := 'Morning';
else
ret := 'Night';
end if;
return ret
end;

Resources