Resetting a date value if it falls on weekend - sql

I am writing a procedure and using dynamic cursor and SQL query which I am passing as string in V_SQL variable. Query is as following:
In the where condition, I'm passing date but the condition is that if it's month end and it falls on Friday, Saturday or sunday then reset it to Thursday. for instance, 30th June will be sunday so value passed to day_of_month in SQL query should be 27 i.e. day no from Thursday date.
Could you please help me whether writing separate function will be good and what code should I put for better performance and desired result.
V_SQL := 'SELECT B.FIN_ELEM, A.ORG_UNIT_ID, A.GL_ACCOUNT_ID, B.CMN_COA_ID, B.PROD1, B.PROD2, B.PROD3, '||
'A.AS_OF_DATE, SUM(CURRENT_BAL) AS CB_SUM, SUM(AVG_BAL) AS AB_SUM, B.FLAG1 FROM DAILYGL A, AL_LOOKUP B '||
'WHERE A.GL_ACCOUNT_ID = B.GL_ACCT ***AND DAY_OF_MONTH = '|| TO_DO_FUNCTION(V_RUN_DATE)***
' AND ROWNUM <=15 GROUP BY B.FIN_ELEM, A.ORG_UNIT_ID, A.GL_ACCOUNT_ID,B.CMN_COA_ID, B.PROD1, B.PROD2, B.PROD3';
DAY_OF_MONTH = '|| TO_DO_FUNCTION(V_RUN_DATE)
Desired result will be passed Thursday day number if last business day is in fri, sat or sunday.

You don't really need a function, you can achieve this with a case expression, something like:
AND DAY_OF_MONTH = case
when last_day(v_run_date) - v_run_date <= 3
and to_char(v_run_date, 'Dy', 'NLS_DATE_LANGUAGE=ENGLISH') in ('Fri', 'Sat', 'Sun')
and to_char(last_day(v_run_date), 'Dy', 'NLS_DATE_LANGUAGE=ENGLISH') in ('Fri', 'Sat', 'Sun')
then next_day(trunc(v_run_date, 'IW'), 'THURSDAY')
else v_run_date
end
That is, if v_run_date is within three days of the last day of the month, and v_run_date is a Friday, Saturday or Sunday, and the last day of the month is a Friday, Saturday or Sunday, then use the date of that week's Thursday.
Demo with dates generated for all of this year:
with cte (v_run_date) as (
select date '2018-12-31' + level
from dual
connect by level <= 365
)
select v_run_date,
to_char(v_run_date, 'Dy', 'NLS_DATE_LANGUAGE=ENGLISH') as dy,
case
when last_day(v_run_date) - v_run_date <= 3
and to_char(v_run_date, 'Dy', 'NLS_DATE_LANGUAGE=ENGLISH') in ('Fri', 'Sat', 'Sun')
and to_char(last_day(v_run_date), 'Dy', 'NLS_DATE_LANGUAGE=ENGLISH') in ('Fri', 'Sat', 'Sun')
then next_day(trunc(v_run_date, 'IW'), 'THURSDAY')
else v_run_date
end as adjusted_date
from cte;
V_RUN_DATE DY ADJUSTED_D
---------- ------------ ----------
2019-01-01 Tue 2019-01-01
2019-01-02 Wed 2019-01-02
...
2019-01-30 Wed 2019-01-30
2019-01-31 Thu 2019-01-31
2019-02-01 Fri 2019-02-01
2019-02-02 Sat 2019-02-02
...
2019-03-26 Tue 2019-03-26
2019-03-27 Wed 2019-03-27
2019-03-28 Thu 2019-03-28
2019-03-29 Fri 2019-03-28
2019-03-30 Sat 2019-03-28
2019-03-31 Sun 2019-03-28
2019-04-01 Mon 2019-04-01
...
2019-04-30 Tue 2019-04-30
2019-05-01 Wed 2019-05-01
...
2019-05-28 Tue 2019-05-28
2019-05-29 Wed 2019-05-29
2019-05-30 Thu 2019-05-30
2019-05-31 Fri 2019-05-30
2019-06-01 Sat 2019-06-01
2019-06-02 Sun 2019-06-02
...
2019-06-26 Wed 2019-06-26
2019-06-27 Thu 2019-06-27
2019-06-28 Fri 2019-06-27
2019-06-29 Sat 2019-06-27
2019-06-30 Sun 2019-06-27
2019-07-01 Mon 2019-07-01
...
2019-07-31 Wed 2019-07-31
2019-08-01 Thu 2019-08-01
...
2019-08-28 Wed 2019-08-28
2019-08-29 Thu 2019-08-29
2019-08-30 Fri 2019-08-29
2019-08-31 Sat 2019-08-29
2019-09-01 Sun 2019-09-01
...
2019-11-27 Wed 2019-11-27
2019-11-28 Thu 2019-11-28
2019-11-29 Fri 2019-11-28
2019-11-30 Sat 2019-11-28
2019-12-01 Sun 2019-12-01
...
db<>fiddle
As mentioned in a comment, your code doesn't appear to need to be dynamic; you can use static SQL for what you've shown, e.g.:
SELECT *
-- into ...
FROM (
SELECT B.FIN_ELEM, A.ORG_UNIT_ID, A.GL_ACCOUNT_ID,
B.CMN_COA_ID, B.PROD1, B.PROD2, B.PROD3, A.AS_OF_DATE,
SUM(CURRENT_BAL) AS CB_SUM, SUM(AVG_BAL) AS AB_SUM, B.FLAG1
FROM DAILYGL A
JOIN AL_LOOKUP B ON B.GL_ACCT = A.GL_ACCOUNT_ID
WHERE DAY_OF_MONTH = case
when last_day(v_run_date) - v_run_date <= 3
and to_char(v_run_date, 'Dy', 'NLS_DATE_LANGUAGE=ENGLISH') in ('Fri', 'Sat', 'Sun')
and to_char(last_day(v_run_date), 'Dy', 'NLS_DATE_LANGUAGE=ENGLISH') in ('Fri', 'Sat', 'Sun')
then next_day(trunc(v_run_date, 'IW'), 'THURSDAY')
else v_run_date
end
GROUP BY B.FIN_ELEM, A.ORG_UNIT_ID, A.GL_ACCOUNT_ID,B.CMN_COA_ID, B.PROD1, B.PROD2, B.PROD3
-- order by something...
)
WHERE ROWNUM <= 15;
I've also switched to ANSI joins but more importantly moved the main query into an inline view, and then applied the rownum filter to that; which will make no difference unless/until you add an order-by clause inside that inline view. Without it this, and your original, will get an indeterminate (not quite random, but similar effect) set of 15 rows back. If you order the inner query then you'll always get the first 15 rows according to your ordering criteria. From 12c you can use a row limiting clause instead of the subquery/rownum filter, but this approach will work in earlier versions.
Obviously you still need to either select that into variables or use it as a cursor query - whatever you had planned to do dynamically.

I hope this will solve your problem
V_RUN_DATE - (case when V_RUN_DATE > LAST_DAY (V_RUN_DATE)- 3 then V_RUN_DATE else
(case TO_CHAR(date V_RUN_DATE, 'DY') when 'FRI' then 1 when 'SAT' then 2 when 'SUN' then 3 else 0 end) end)

Related

Extract 5th Business Date from sysdate

I'm looking to extract the 5th Business Date data from database. Looking for pure 5th Business Date, no other business requirement like Holiday or New Year day.
Looking to extract 07/03/2022 from dual table using Oracle PL/SQL
Date
Day
Requirement
1/03/2022
Tuesday
1BD
2/03/2022
Wednesday
2BD
3/03/2022
Thursday
3BD
4/03/2022
Friday
4BD
5/03/2022
Saturday
Weekend
6/03/2022
Sunday
Weekend
7/03/2022
Monday
5BD
8/03/2022
Tuesday
6BD
9/03/2022
Wednesday
7BD
This is how I understood it.
Today is Thursday, 24.03.2022. It means that 5th business day looking backwards is Friday, 18.03.2022.
SQL> with test (datum, day) as
2 -- calendar
3 (select
4 trunc(sysdate) - &&par_number_of_days * 2 + level - 1,
5 to_char(trunc(sysdate) - &&par_number_of_days * 2 + level - 1, 'dy',
6 'nls_date_language = english')
7 from dual
8 connect by level <= (&&par_number_of_days * 2) + 1
9 ),
10 only_working_days as
11 -- remove weekends
12 (select datum,
13 day,
14 row_number() over (order by datum desc) rn
15 from test
16 where day not in ('sat', 'sun')
17 )
18 select datum, day, rn
19 from only_working_days
20 where rn = &&par_number_of_days;
Enter value for par_number_of_days: 5
DATUM DAY RN
---------- --- ----------
18.03.2022 fri 5
Or, 13th business day backwards is 08.03.2022:
SQL> undefine par_number_of_days
SQL> /
Enter value for par_number_of_days: 13
DATUM DAY RN
---------- --- ----------
08.03.2022 tue 13
SQL>
If it is, on the other hand, related to period since 1st of current, month, then
SQL> with test (datum, day) as
2 (select trunc(sysdate, 'mm') + level - 1,
3 to_char(trunc(sysdate, 'mm') + level - 1, 'dy', 'nls_date_language = english')
4 from dual
5 connect by level <= trunc(sysdate) - trunc(sysdate, 'mm') + 1
6 ),
7 only_working_days as
8 -- remove weekends
9 (select datum,
10 day,
11 row_number() over (order by datum) rn
12 from test
13 where day not in ('sat', 'sun')
14 )
15 select datum, day, rn
16 from only_working_days
17 where rn = &par_number_of_days;
Enter value for par_number_of_days: 5
DATUM DAY RN
---------- --- ----------
07.03.2022 mon 5
SQL> /
Enter value for par_number_of_days: 13
DATUM DAY RN
---------- --- ----------
17.03.2022 thu 13
SQL>
The 5th business day will always be 7 days ahead, since there will be 5 weekdays and 2 weekend days, so the simplest solution is:
SELECT TRUNC(SYSDATE) + INTERVAL '7' DAYS
FROM DUAL
More generally, if you want to add a number of business days to a date then you can calculate it using:
start_date
+ FLOOR(bd/5) * INTERVAL '7' DAY -- Full weeks
+ MOD(bd, 5) -- Part week
+ CASE
WHEN start_date - TRUNC(start_date, 'IW') + MOD(bd, 5) >= 5
THEN 2
WHEN start_date - TRUNC(start_date, 'IW') + MOD(bd, 5) < 0
THEN -2
ELSE 0
END -- Adjust for weekend
For example, given the sample data:
CREATE TABLE table_name (start_date, bd) AS
SELECT TRUNC(SYSDATE), LEVEL - 11 FROM DUAL CONNECT BY LEVEL <= 21
UNION ALL
SELECT DATE '2022-03-01', 5 FROM DUAL;
Then:
SELECT start_date,
bd,
start_date
+ FLOOR(bd/5) * INTERVAL '7' DAY -- Full weeks
+ MOD(bd, 5) -- Part week
+ CASE
WHEN start_date - TRUNC(start_date, 'IW') + MOD(bd, 5) >= 5
THEN 2
WHEN start_date - TRUNC(start_date, 'IW') + MOD(bd, 5) < 0
THEN -2
ELSE 0
END -- Adjust for weekend
AS adjusted_business_day
FROM table_name;
Outputs:
START_DATE
BD
ADJUSTED_BUSINESS_DAY
2022-03-24 00:00:00 (THU)
-10
2022-03-10 00:00:00 (THU)
2022-03-24 00:00:00 (THU)
-9
2022-03-04 00:00:00 (FRI)
2022-03-24 00:00:00 (THU)
-8
2022-03-07 00:00:00 (MON)
2022-03-24 00:00:00 (THU)
-7
2022-03-08 00:00:00 (TUE)
2022-03-24 00:00:00 (THU)
-6
2022-03-09 00:00:00 (WED)
2022-03-24 00:00:00 (THU)
-5
2022-03-17 00:00:00 (THU)
2022-03-24 00:00:00 (THU)
-4
2022-03-11 00:00:00 (FRI)
2022-03-24 00:00:00 (THU)
-3
2022-03-14 00:00:00 (MON)
2022-03-24 00:00:00 (THU)
-2
2022-03-15 00:00:00 (TUE)
2022-03-24 00:00:00 (THU)
-1
2022-03-16 00:00:00 (WED)
2022-03-24 00:00:00 (THU)
0
2022-03-24 00:00:00 (THU)
2022-03-24 00:00:00 (THU)
1
2022-03-25 00:00:00 (FRI)
2022-03-24 00:00:00 (THU)
2
2022-03-28 00:00:00 (MON)
2022-03-24 00:00:00 (THU)
3
2022-03-29 00:00:00 (TUE)
2022-03-24 00:00:00 (THU)
4
2022-03-30 00:00:00 (WED)
2022-03-24 00:00:00 (THU)
5
2022-03-31 00:00:00 (THU)
2022-03-24 00:00:00 (THU)
6
2022-04-01 00:00:00 (FRI)
2022-03-24 00:00:00 (THU)
7
2022-04-04 00:00:00 (MON)
2022-03-24 00:00:00 (THU)
8
2022-04-05 00:00:00 (TUE)
2022-03-24 00:00:00 (THU)
9
2022-04-06 00:00:00 (WED)
2022-03-24 00:00:00 (THU)
10
2022-04-07 00:00:00 (THU)
2022-03-01 00:00:00 (TUE)
5
2022-03-08 00:00:00 (TUE)
db<>fiddle here

Oracle get last weekday Mon-Fri

I would like to obtain the last weekday.
If it's Tues to Sat, it will be the previous day. If it's Sunday or Monday, it will be Friday.
So far, I've tried this, but I'm struggling to get the desired output.
SELECT
level AS dow,
trunc(sysdate, 'D') + level day,
to_char(trunc(sysdate, 'D') + level, 'Day') AS day_week,
CASE
WHEN to_char(trunc(sysdate, 'D') + level, 'Day') IN (
'Sunday',
'Monday'
) THEN
trunc(sysdate - 2, 'IW') + 4
ELSE
sysdate - 1
END calculation
FROM
dual
CONNECT BY
level <= 7;
This solution works independent of language and territory:
SELECT date_value,
date_value - CASE TRUNC(date_value) - TRUNC(date_value, 'IW')
WHEN 0 THEN 3 -- Monday
WHEN 6 THEN 2 -- Sunday
ELSE 1 -- Tuesday to Saturday
END AS previous_weekday
FROM table_name;
Which, for the sample data:
CREATE TABLE table_name (date_value) AS
SELECT TRUNC(sysdate - LEVEL + 1)
FROM DUAL
CONNECT BY LEVEL <= 7;
Outputs (with the date format YYYY-MM-DD HH24:MI:SS (DY)):
DATE_VALUE
PREVIOUS_WEEKDAY
2021-07-20 00:00:00 (TUE)
2021-07-19 00:00:00 (MON)
2021-07-19 00:00:00 (MON)
2021-07-16 00:00:00 (FRI)
2021-07-18 00:00:00 (SUN)
2021-07-16 00:00:00 (FRI)
2021-07-17 00:00:00 (SAT)
2021-07-16 00:00:00 (FRI)
2021-07-16 00:00:00 (FRI)
2021-07-15 00:00:00 (THU)
2021-07-15 00:00:00 (THU)
2021-07-14 00:00:00 (WED)
2021-07-14 00:00:00 (WED)
2021-07-13 00:00:00 (TUE)
db<>fiddle here

Week starting hour gives previous week date instead of current week

In our project we are maintaining a customers weekly spend. For that every week start we are resetting a customer limit.
Whenever customer doing a transaction we are updating his weekly spend by using the below query.
UPDATE SUMMARY
SET WEEKLIMIT = (
SELECT NVL (SUM (AMT / 100), 0)
FROM TRANSACTION
WHERE MOBILENO = :mobileNumber
AND TRUNC(TXNDT) BETWEEN (TRUNC (SYSTIMESTAMP, 'IW') - 1 ) AND TRUNC (SYSTIMESTAMP)
) WHERE MOBILENO = :mobileNumber
But the problem is customer who are doing transaction in sunday 00:00:01 to 01:00:00 hours, the above query updating the previous week limits
instead of current week.
SUMMARY
MOBILENUMBER AMT TXNDATE
0000000000 10000 26-12-2019 09:05:34
0000000000 10000 28-12-2019 11:05:34
0000000000 10000 29-12-2019 00:01:35
When I run this query in sunday first hour it returning
200
But from next hour
it returning only
100
Why the starting hour of week gives the previous week data.
Here is my NLS parameters
NLS_LANGUAGE ENGLISH
NLS_TERRITORY INDIA
NLS_CURRENCY Rs
NLS_ISO_CURRENCY INDIA
NLS_NUMERIC_CHARACTERS .,
NLS_CALENDAR GREGORIAN
NLS_DATE_FORMAT DD-MM-RR
NLS_DATE_LANGUAGE ENGLISH
NLS_SORT BINARY
NLS_TIME_FORMAT HH12:MI:SSXFF AM
NLS_TIMESTAMP_FORMAT DD-MM-RR HH12:MI:SSXFF AM
NLS_TIME_TZ_FORMAT HH12:MI:SSXFF AM TZR
NLS_TIMESTAMP_TZ_FORMAT DD-MM-RR HH12:MI:SSXFF AM TZR
NLS_DUAL_CURRENCY Rs
NLS_COMP BINARY
NLS_LENGTH_SEMANTICS BYTE
NLS_NCHAR_CONV_EXCP FALSE
Any help will be greatly appreciated!!!!
I believe your issue lies in your date manipulation. You are doing:
TRUNC(TXNDT) BETWEEN (TRUNC (SYSTIMESTAMP, 'IW') - 1 ) AND TRUNC (SYSTIMESTAMP)
and expecting that to make the start of the week a Sunday.
However, you haven't done this correctly - your logic says "take the Monday of the current week and subtract a day from it". If your day is a Sunday, that means it first gets the previous Monday and subtracts a day to make it the previous Sunday.
What you must first do is turn the Sunday into a Monday (by adding a day), truncate it to the first day of the week (Monday), and then subtract a day.
Which means your predicate should now look like:
TRUNC(TXNDT) BETWEEN (TRUNC (SYSDATE + 1, 'IW') - 1 ) AND TRUNC (SYSDATE)
Here's a test case demonstrating the difference:
WITH dts AS (SELECT TRUNC(SYSDATE, 'mm') - 1 + LEVEL dt
FROM dual
CONNECT BY LEVEL <= 13)
SELECT dt,
to_char(dt, 'Dy') day_of_week,
TRUNC(dt, 'iw') monday_start_week,
TRUNC(dt, 'iw') - 1 your_sunday_start_week,
TRUNC(dt + 1, 'iw') - 1 actual_sunday_start_week
FROM dts;
DT DAY_OF_WEEK MONDAY_START_WEEK YOUR_SUNDAY_START_WEEK ACTUAL_SUNDAY_START_WEEK
----------- ----------- ----------------- ---------------------- ------------------------
01/01/2020 Wed 30/12/2019 29/12/2019 29/12/2019
02/01/2020 Thu 30/12/2019 29/12/2019 29/12/2019
03/01/2020 Fri 30/12/2019 29/12/2019 29/12/2019
04/01/2020 Sat 30/12/2019 29/12/2019 29/12/2019
05/01/2020 Sun 30/12/2019 29/12/2019 05/01/2020 <-----
06/01/2020 Mon 06/01/2020 05/01/2020 05/01/2020
07/01/2020 Tue 06/01/2020 05/01/2020 05/01/2020
08/01/2020 Wed 06/01/2020 05/01/2020 05/01/2020
09/01/2020 Thu 06/01/2020 05/01/2020 05/01/2020
10/01/2020 Fri 06/01/2020 05/01/2020 05/01/2020
11/01/2020 Sat 06/01/2020 05/01/2020 05/01/2020
12/01/2020 Sun 06/01/2020 05/01/2020 12/01/2020 <-----
13/01/2020 Mon 13/01/2020 12/01/2020 12/01/2020
You can see that for the two Sunday rows, your version returns the previous Sunday, whereas the amended version returns the current Sunday.

Add target hours onto the next working day if job is logged after a certain time

Bit of a specific request / query but hope that I can explain it correctly, and that it makes sense.
The working day is 8am-5pm Monday-Friday
Each job has a target response time eg 1 hour, 2 hours, 4 hours
Some jobs it shows the target response time as outside of the working day eg a 4 hour job logged at 4:15pm will show a target response time of 8:15pm.
What I would like to do (and not even sure if it is possible) is:
If the priority_code is GC04 (1 hour job) and the time logged is after 4pm on a Monday-Fri take whatever time is before 5pm and add the remainder on to the next working day from 8am.
So an example would be 1 hour job logged at 4:15pm on Monday would show a target response time of 8:15am on Tuesday morning. (45 minutes used on Monday and 15 minutes carried over to Tuesday).
If the priority_code is GC05 (2 hour job) and the time logged is after 3pm on a Monday-Fri take whatever time is before 5pm and add the remainder on to the next working day from 8am.
So an example would be 2 hour job logged at 3:15pm on Monday would show a target response time of 8:15am on Tuesday morning. (1 hour 45 minutes used on Monday and 15 minutes carried over to Tuesday).
If the priority_code is GC06 (4 hour job) and the time logged is after 1pm on a Monday-Fri take whatever time is before 5pm and add the remainder on to the next working day from 8am.
So an example would be 4 hour job logged at 1:15pm on Monday would show a target response time of 8:15am on Tuesday morning. (3 hours 45 minutes used on Monday and 15 minutes carried over to Tuesday).
THANK YOU TO ALEX POOLE I'VE NOW GOT IT WORKING
Coding is below
select job_number, priority_code, job_entry_date, clock_start,
target_comp_date,
case
when to_char(target_time, 'Dy', 'NLS_DATE_LANGUAGE=ENGLISH') = 'Fri'
and floor((target_time - trunc(target_time)) * 24) >= 17
then target_time + 2 + 63/24
when floor((target_time - trunc(target_time)) * 24) >= 17
then target_time + 15/24
else target_time
end as target_time
from (
select job_number, priority_code, job_entry_date, clock_start,
TARGET_COMP_DATE,
CASE
WHEN PRIORITY_CODE IN ('GC01','GC02','GC03','GC04','GC05','GC06','GC07')
THEN
clock_start
+ case priority_code
when 'GC01' then 1
when 'GC02' then 2
when 'GC03' then 0.5
when 'GC04' then 1
when 'GC05' then 2
when 'GC06' then 4
when 'GC07' then 24
end
/ 24
ELSE
TARGET_COMP_DATE END as target_time
from (
select job_number, priority_code, job_entry_date, target_comp_date,
case
when to_char(job_entry_date, 'Dy', 'NLS_DATE_LANGUAGE=ENGLISH') = 'Fri'
and floor((job_entry_date - trunc(job_entry_date)) * 24) >= 17
then trunc(job_entry_date) + 80/24
when to_char(job_entry_date, 'Dy', 'NLS_DATE_LANGUAGE=ENGLISH') = 'Sat'
then trunc(job_entry_date) + 56/24
when to_char(job_entry_date, 'Dy', 'NLS_DATE_LANGUAGE=ENGLISH') =
'Sun'
or floor((job_entry_date - trunc(job_entry_date)) * 24) >= 17
then trunc(job_entry_date) + 32/24
when floor((job_entry_date - trunc(job_entry_date)) * 24) < 8
then trunc(job_entry_date) + 8/24
else job_entry_date
end as clock_start
from job
)
)
A slightly convoluted approach, which assumes your logged_time column is a timestamp (easy to adapt if it's a date), and that it can't be out-of-hours:
select id, priority_code, logged_time,
logged_time
+
-- response time
(
interval '1' hour
* case priority_code when 'GC04' then 1 when 'GC05' then 2 when 'GC06' then 4 end
)
+
-- actual time adjustment
(
-- possible time adjustment...
(
-- gap between 17:00 and 08:00
interval '15' hour
+
-- weekend days, only if Friday
(
interval '2' day
* case when to_char(logged_time, 'Dy', 'NLS_DATE_LANGUAGE=ENGLISH') = 'Fri'
then 1 else 0 end
)
)
*
-- ... but only if target exceeds 17:00
case when extract
(
hour from logged_time
+
-- response time
(
interval '1' hour
* case priority_code when 'GC04' then 1 when 'GC05' then 2 when 'GC06' then 4 end
)
) > 16 then 1 else 0 end
)
as target_time
from your_table;
which with some sample data like yours and just before your cut-offs, both on a Friday and Monday, gives:
ID PRIO LOGGED_TIME TARGET_TIME
---------- ---- --------------------- ---------------------
1 GC06 2019-05-26 12:59:59.0 2019-05-26 16:59:59.0
2 GC06 2019-05-26 13:15:00.0 2019-05-27 08:15:00.0
3 GC05 2019-05-26 14:59:59.0 2019-05-26 16:59:59.0
4 GC05 2019-05-26 15:15:00.0 2019-05-27 08:15:00.0
5 GC04 2019-05-26 15:59:59.0 2019-05-26 16:59:59.0
6 GC04 2019-05-26 16:15:00.0 2019-05-27 08:15:00.0
7 GC06 2019-05-31 12:59:59.0 2019-05-31 16:59:59.0
8 GC06 2019-05-31 13:15:00.0 2019-06-03 08:15:00.0
9 GC05 2019-05-31 14:59:59.0 2019-05-31 16:59:59.0
10 GC05 2019-05-31 15:15:00.0 2019-06-03 08:15:00.0
11 GC04 2019-05-31 15:59:59.0 2019-05-31 16:59:59.0
12 GC04 2019-05-31 16:15:00.0 2019-06-03 08:15:00.0
You can reduce some of the duplication with a CTE or inline view:
select id, priority_code, logged_time,
raw_target_time
+
-- actual time adjustment
(
-- possible time adjustment...
(
-- gap between 17:00 and 08:00
interval '15' hour
+
-- weekend days, only if Friday
(
interval '2' day
* case when to_char(logged_time, 'Dy', 'NLS_DATE_LANGUAGE=ENGLISH') = 'Fri'
then 1 else 0 end
)
)
*
-- ... but only if target exceeds 17:00
case when extract (hour from raw_target_time) > 16 then 1 else 0 end
)
as target_time
from (
select id, priority_code, logged_time,
logged_time
+
-- response time
(
interval '1' hour
* case priority_code when 'GC04' then 1 when 'GC05' then 2 when 'GC06' then 4 end
)
as raw_target_time
from your_table
);
and of course it doesn't need to be laid out like that, I was just trying to make the logic a bit clearer.
jobs can be logged at any time of the day using online web-forms, not just between 8am-5pm
This means that if a job is logged out-of-hours you need to treat it as if it was actually logged at the start of the next working day. (Note that I'm following your question in treating all Mon-Fri as working days - there is nothing in your question about public holidays for instance. Dealing with those would probably be a separate question.)
If you want to break it down you can first figure out when the clock starts on a given job, based on whether it was logged inside or outside the workday. There are a few ways to do this but since you have to deal with weekends I've chosen to do this as:
select id, priority_code, logged_time,
case
when to_char(logged_time, 'Dy', 'NLS_DATE_LANGUAGE=ENGLISH') = 'Fri'
and floor((logged_time - trunc(logged_time)) * 24) >= 17
then trunc(logged_time) + 56/24
when to_char(logged_time, 'Dy', 'NLS_DATE_LANGUAGE=ENGLISH') = 'Sat'
then trunc(logged_time) + 56/24
when to_char(logged_time, 'Dy', 'NLS_DATE_LANGUAGE=ENGLISH') = 'Sun'
or floor((logged_time - trunc(logged_time)) * 24) >= 17
then trunc(logged_time) + 32/24
when floor((logged_time - trunc(logged_time)) * 24) < 8
then trunc(logged_time) + 8/24
else logged_time
end as clock_start
from your_table;
The floor((logged_time - trunc(logged_time)) * 24) gives you the hour that a job was logged, so you can see if that was lower than 8 (i.e. 8am) or greater than or equal to 17 (i.e. 5pm). Jobs logged at or after 17:00 on Friday or any time at weekends have their clock-start time pushed to the following Monday; jobs logged at or after 17:00 on other days are pushed to the following morning. That's using date arithmetic - 8/24 is 8 hours, 32/24 is 1 day and 8 hours, 56/24 is 2 days and 8 hours etc.
You can then put that into an inline view or CTE to simplify further calculations:
with cte1 (id, priority_code, logged_time, clock_start) as (
...
)
select id, priority_code, logged_time, clock_start,
clock_start
+ case priority_code when 'GC04' then 1 when 'GC05' then 2 when 'GC06' then 4 end
/ 24 as target_time
from cte1;
which will give you the basic target time; and then you can adjust that using similar logic to my earlier answer about timestamps and working-day-only logging, but this time using more date manipulation with fractional days instead of with intervals:
with cte1 (id, priority_code, logged_time, clock_start) as (
select id, priority_code, logged_time,
case
when to_char(logged_time, 'Dy', 'NLS_DATE_LANGUAGE=ENGLISH') = 'Fri'
and floor((logged_time - trunc(logged_time)) * 24) >= 17
then trunc(logged_time) + 80/24
when to_char(logged_time, 'Dy', 'NLS_DATE_LANGUAGE=ENGLISH') = 'Sat'
then trunc(logged_time) + 56/24
when to_char(logged_time, 'Dy', 'NLS_DATE_LANGUAGE=ENGLISH') = 'Sun'
or floor((logged_time - trunc(logged_time)) * 24) >= 17
then trunc(logged_time) + 32/24
when floor((logged_time - trunc(logged_time)) * 24) < 8
then trunc(logged_time) + 8/24
else logged_time
end as clock_start
from your_table
),
cte2 (id, priority_code, logged_time, clock_start, target_time) as (
select id, priority_code, logged_time, clock_start,
clock_start
+ case priority_code when 'GC04' then 1 when 'GC05' then 2 when 'GC06' then 4 end
/ 24 as target_time
from cte1
)
select id, priority_code, logged_time, clock_start,
case
when to_char(target_time, 'Dy', 'NLS_DATE_LANGUAGE=ENGLISH') = 'Fri'
and floor((target_time - trunc(target_time)) * 24) >= 17
then target_time + 63/24
when floor((target_time - trunc(target_time)) * 24) >= 17
then target_time + 15/24
else target_time
end as target_time
from cte2;
which with some made-up data for the scenarios I've thought about gives, with the days added just for info to make things hopefully slightly clearer:
ID PRIO LOGGED_TIME CLOCK_START TARGET_TIME LOGGED_DAY CLOCK_DAY TARGET_DAY
--- ---- ------------------- ------------------- ------------------- ---------- --------- ----------
1 GC06 2019-05-27 12:59:59 2019-05-27 12:59:59 2019-05-27 16:59:59 Mon Mon Mon
2 GC06 2019-05-27 13:15:00 2019-05-27 13:15:00 2019-05-28 08:15:00 Mon Mon Tue
3 GC05 2019-05-27 14:59:59 2019-05-27 14:59:59 2019-05-27 16:59:59 Mon Mon Mon
4 GC05 2019-05-27 15:15:00 2019-05-27 15:15:00 2019-05-28 08:15:00 Mon Mon Tue
5 GC04 2019-05-27 15:59:59 2019-05-27 15:59:59 2019-05-27 16:59:59 Mon Mon Mon
6 GC04 2019-05-27 16:15:00 2019-05-27 16:15:00 2019-05-28 08:15:00 Mon Mon Tue
7 GC04 2019-05-27 16:59:59 2019-05-27 16:59:59 2019-05-28 08:59:59 Mon Mon Tue
8 GC04 2019-05-27 17:00:00 2019-05-28 08:00:00 2019-05-28 09:00:00 Mon Tue Tue
9 GC04 2019-05-28 07:59:59 2019-05-28 08:00:00 2019-05-28 09:00:00 Tue Tue Tue
10 GC04 2019-05-28 08:00:00 2019-05-28 08:00:00 2019-05-28 09:00:00 Tue Tue Tue
11 GC06 2019-05-31 12:59:59 2019-05-31 12:59:59 2019-05-31 16:59:59 Fri Fri Fri
12 GC06 2019-05-31 13:15:00 2019-05-31 13:15:00 2019-06-03 08:15:00 Fri Fri Mon
13 GC05 2019-05-31 14:59:59 2019-05-31 14:59:59 2019-05-31 16:59:59 Fri Fri Fri
14 GC05 2019-05-31 15:15:00 2019-05-31 15:15:00 2019-06-03 08:15:00 Fri Fri Mon
15 GC04 2019-05-31 15:59:59 2019-05-31 15:59:59 2019-05-31 16:59:59 Fri Fri Fri
16 GC04 2019-05-31 16:15:00 2019-05-31 16:15:00 2019-06-03 08:15:00 Fri Fri Mon
17 GC04 2019-05-31 16:59:59 2019-05-31 16:59:59 2019-06-03 08:59:59 Fri Fri Mon
18 GC04 2019-05-31 17:00:00 2019-06-03 08:00:00 2019-06-03 09:00:00 Fri Mon Mon
19 GC04 2019-06-01 12:00:00 2019-06-03 08:00:00 2019-06-03 09:00:00 Sat Mon Mon
20 GC04 2019-06-02 12:00:00 2019-06-03 08:00:00 2019-06-03 09:00:00 Sun Mon Mon
21 GC04 2019-06-03 07:59:59 2019-06-03 08:00:00 2019-06-03 09:00:00 Mon Mon Mon
22 GC04 2019-06-03 08:00:00 2019-06-03 08:00:00 2019-06-03 09:00:00 Mon Mon Mon
db<>fiddle
Note that the CTE construct is providing aliases for the column expression from the with (...) clause, and that those do not have table aliases - as those aliases are unrelated to the tables inside the CTE. So it's with cte1 (id, priority_code, ... and not with cte1 (your_table.id, your_table.priority_code, ....
Also note that the semicolon at the end is a statement separator, required or optional (or configurable) in some clients, but invalid in others - it can cause ORA-00933 or ORA-00911 errors, and possibly others, in dynamic SQL, JDBC etc.; so ODBC probably also doesn't expect to see that final semicolon character.
If ODBC (or your version) doesn't allow CTEs - suggested by the 'not a SELECT statement' error you mentioned in a comment - then you can use inline views instead:
select id, priority_code, logged_time, clock_start,
case
when to_char(target_time, 'Dy', 'NLS_DATE_LANGUAGE=ENGLISH') = 'Fri'
and floor((target_time - trunc(target_time)) * 24) >= 17
then target_time + 2 + 63/24
when floor((target_time - trunc(target_time)) * 24) >= 17
then target_time + 15/24
else target_time
end as target_time
from (
select id, priority_code, logged_time, clock_start,
clock_start
+ case priority_code when 'GC04' then 1 when 'GC05' then 2 when 'GC06' then 4 end
/ 24 as target_time
from (
select id, priority_code, logged_time,
case
when to_char(logged_time, 'Dy', 'NLS_DATE_LANGUAGE=ENGLISH') = 'Fri'
and floor((logged_time - trunc(logged_time)) * 24) >= 17
then trunc(logged_time) + 80/24
when to_char(logged_time, 'Dy', 'NLS_DATE_LANGUAGE=ENGLISH') = 'Sat'
then trunc(logged_time) + 56/24
when to_char(logged_time, 'Dy', 'NLS_DATE_LANGUAGE=ENGLISH') = 'Sun'
or floor((logged_time - trunc(logged_time)) * 24) >= 17
then trunc(logged_time) + 32/24
when floor((logged_time - trunc(logged_time)) * 24) < 8
then trunc(logged_time) + 8/24
else logged_time
end as clock_start
from your_table
)
);
Added to db<>fiddle.
It's a little unclear but to keep a pre-calculated target_comp_date for other priorities you could change the first inline view (based on cte2) to have nested case expressions:
...
from (
select id, priority_code, logged_time, clock_start,
case when priority_code in ('GC04', 'GC05', 'GC06') then
-- for these, calculate the target time based on clock-start as before
clock_start
+ case priority_code when 'GC04' then 1 when 'GC05' then 2 when 'GC06' then 4 end
/ 24
else
-- for any other priority use the original pre-calculated time
target_comp_date
end as target_time
from (
select id, priority_code, logged_time, target_comp_date,
...
The innermost inline view needs to include that extra column in its select list, so it's visible to that nested case expression.
The system will show the target_comp_date incorrect as 12am
You should probably be fixing that existing code then, rather than trying to fudge the result it gives you.

How to find first Thursday of a month using Oracle SQL?

How to find First Thursday of this month using oracle?
select trunc(sysdate, 'MM')firstday from dual;
above is getting firstday of this month
Try with the below SQL
SELECT NEXT_DAY(TRUNC(SYSDATE, 'MONTH'),'THURSDAY') FROM DUAL;
SQL Fiddle
If you just get the next Thursday after the first day of the month, the after part means that if the 1st is itself a Thursday you will acually get a date a week too late. For instance, 2018-02-01 was a Thursday, and putting that into next_day() gives you 2018-02-08.
You need to get the next Thursday after the last day of the previous month:
next_day(trunc(sysdate, 'MM') - 1, 'THU')
Demo of both values; look at February, March and November in particular:
with t (dt) as (
select add_months(date '2018-01-15' , level - 1) from dual connect by level <= 12
)
select next_day(trunc(dt, 'MM'), 'THU') as wrong,
next_day(trunc(dt, 'MM') - 1, 'THU') as ok
from t;
WRONG OK
---------- ----------
2018-01-04 2018-01-04
2018-02-08 2018-02-01
2018-03-08 2018-03-01
2018-04-05 2018-04-05
2018-05-03 2018-05-03
2018-06-07 2018-06-07
2018-07-05 2018-07-05
2018-08-02 2018-08-02
2018-09-06 2018-09-06
2018-10-04 2018-10-04
2018-11-08 2018-11-01
2018-12-06 2018-12-06