Where clause from a subquery - oracle

I have a table with business days BUSINESS_DAYS which has all the dates
I have another table with payment information and DUE_DATES
I want to return in my query the next business day IF the DUE_DATE is not a business day
SELECT SQ1.DUE_DATE, SQ2.DATE FROM
(SELECT * FROM
PAYMENTS
ORDER BY
DUE_DATE) SQ1,
(SELECT MIN(DATE) DATE FROM BUSINESS_DAYS WHERE SQ1.DUE_DATE <= DATE GROUP BY DATE) SQ2
Anyone can shed some light?

The way I see it, code you posted doesn't do what you wanted anyway (otherwise, you won't be asking a question at all). Therefore, I'd suggest another approach:
Altering the session (you don't have to do it; my database speaks Croatian so I'm switching to English; also, setting date format to display day name):
SQL> alter session set nls_date_language = 'english';
Session altered.
SQL> alter session set nls_date_format = 'dd.mm.yyyy, dy';
Session altered.
Two CTEs contain
business_days: as commented, only this year's July, weekends excluded, there are no holidays)
payments: two rows, one whose due date is a working day and another whose isn't
Sample data end at line #15, query you might be interested in begins at line #16. Its CASE expression check whether due_date is one of weekend days; if not, due date to be returned is exactly what it is. Otherwise, another SELECT statement returns the first (MIN) business day larger than due_date.
SQL> with
2 business_days (datum) as
3 -- for simplicity, only all dates in this year's July,
4 -- weekends excluded (as they aren't business days), no holidays
5 (select date '2021-07-01' + level - 1
6 from dual
7 where to_char(date '2021-07-01' + level - 1, 'dy')
8 not in ('sat', 'sun')
9 connect by level <= 31
10 ),
11 payments (id, due_date) as
12 (select 1, date '2021-07-14' from dual -- Wednesday, business day
13 union all
14 select 2, date '2021-07-25' from dual -- Sunday, non-business day
15 )
16 select p.id,
17 p.due_date current_due_date,
18 --
19 case when to_char(p.due_date, 'dy') not in ('sat', 'sun') then
20 p.due_date
21 else (select min(b.datum)
22 from business_days b
23 where b.datum > p.due_date
24 )
25 end new_due_date
26 from payments p
27 order by id;
ID CURRENT_DUE_DAT NEW_DUE_DATE
---------- --------------- ---------------
1 14.07.2021, wed 14.07.2021, wed --> Wednesday remains "as is"
2 25.07.2021, sun 26.07.2021, mon --> Sunday switched to Monday
SQL>

Related

calculate number of saturdays+sunday total count in oracle

i have a query and I want to calculate the number of sat+sun total count in oracle, for example, I have a query pasted below there should be a total count of Saturday and Sunday, how can I achieve that please help, I really appreciate any help you can provide.
SELECT TO_DATE('01-12-2022','dd-mm-yyyy') start_date , TO_DATE(sysdate) end_date
FROM dual;
Don't use a row-generator to create a calendar (as it is very inefficient); just calculate the number by calculating the number of full weeks and then deal with the part weeks at the start and end of the range:
WITH range (start_date, end_date) AS (
SELECT DATE '2022-12-01', TRUNC(SYSDATE) FROM DUAL
)
SELECT -- Number of full weeks
(TRUNC(end_date, 'IW') - TRUNC(start_date, 'IW')) * 2/7
-- Number of weekend days in final week
+ GREATEST(end_date - TRUNC(end_date, 'IW') - 4, 0)
-- Number of weekend days in before first week
- GREATEST(start_date - TRUNC(start_date, 'IW') - 5, 0)
AS weekend_day_count
FROM range;
Which outputs:
WEEKEND_DAY_COUNT
8
fiddle
One option is to create a calendar between these two dates and then count number of Saturdays and Sundays:
SQL> with
2 test (start_date, end_date) as
3 -- period
4 (select date '2022-12-01', date '2022-12-29' from dual),
5 calendar as
6 -- calendar (all dates between START_DATE and END_DATE)
7 (select start_date + level - 1 as datum
8 from test
9 connect by level <= end_date - start_date + 1
10 )
11 -- number of Saturdays and Sundays
12 select count(*)
13 from calendar
14 where to_char(datum, 'dy', 'nls_date_language = english') in ('sat', 'sun');
COUNT(*)
----------
8
SQL>
You'd change dates at line #4.
P.S. If you look at code MT0 posted and their objection that row generator is inefficient, that's true. Although both queries return the same result, timing is different. For example:
Period 01.01.2022 - 31.12.2022 01.01.1900 - 31.12.2022 01.01.0001 - 31.12.2022
------ ----------------------- ----------------------- -----------------------
LF 00:00:00.00 00:00:00.17 00:00:03.72
MT0 00:00:00.02 00:00:00.02 00:00:00.05
It is obvious that my timing gets worse with period length. If you're looking at one year or a century, the difference is mostly irrelevant. For 2000 years, the difference is huge!
However, if you consider debugging, from my own point of view, my code is easier to read: "select number of rows from the calendar where date is either saturday or sunday" - plain English.
On the other hand, the other code isn't that straightforward; truncate date to week, subtract them, multiply by 2/7 (why "2/7" and not 4/9?), add result returned by the GREATEST function minus 4 (why 4? Why not 7?), subtract GREATEST of something minus 5 (why 5? Why not 2?) - as I said, that's NOT easy to read nor understand.
Therefore, it depends on what you actually need, timing vs. readability. Pick one :)

Training schedule till end of year

I have a schedule of my training, three times a week, for example -
MON, WED,FRI. I need to generate records for my schedule table with dates till the end of the current year when I have training.
The schedule table is:
CREATE TABLE trainingSchedule (
id NUMBER,
training_date DATE
);
If training date already exist - don’t insert a record.
Here's one option. Read comments within code.
SQL> CREATE TABLE trainingSchedule
2 (id NUMBER,
3 training_date DATE
4 );
Table created.
SQL> create sequence seq_tra;
Sequence created.
SQL> -- initial insert (just to show that MERGE will skip it
SQL> insert into trainingschedule values (seq_tra.nextval, date '2021-03-22');
1 row created.
MERGE will skip rows that are already inserted. I understood that you want to insert only dates that follow today's date; if that's not so, just remove the last condition.
SQL> merge into trainingschedule t
2 using (-- this is a calendar for current year
3 select trunc(sysdate, 'yyyy') + level - 1 datum
4 from dual
5 connect by level <= add_months(trunc(sysdate, 'yyyy'), 12) - trunc(sysdate, 'yyyy')
6 ) c
7 on (c.datum = t.training_date)
8 when not matched then insert (id, training_date) values (seq_tra.nextval, c.datum)
9 -- insert only Mondays, Wednesdays and Fridays
10 where to_char(c.datum, 'dy', 'nls_date_language = english') in ('mon', 'wed', 'fri')
11 -- insert only dates that follow today's date ("till the end of the current year")
12 and datum >= trunc(sysdate);
122 rows merged.
SQL>
What's in there?
SQL> select id,
2 to_char(training_date, 'dd.mm.yyyy, dy', 'nls_date_language = english') tr_date
3 from trainingschedule
4 order by training_date;
ID TR_DATE
---------- ------------------------
1 22.03.2021, mon --> see? No duplicates
311 24.03.2021, wed
309 26.03.2021, fri
207 29.03.2021, mon
354 31.03.2021, wed
321 02.04.2021, fri
<skip>

How to extract time from a column and subtract from a custom time in Oracle

I would like to compare two time values. The first time value is a custom time which reprsents the start time, for example the column name is Business_Start_time and set to 6:00:00 am. I would also like to extract the time only from a column in Oracle which is a date field that looks like '5/1/2019 12:57:19 PM' and is called 'Completed_Date_Time'. The purpose of this is to compare the businses start date to the time a file was completed. I've tried to convert the 'Completed_Date_Time' field to 'HH24:MI:SS' format which seems to change the datatype to a char(8) value which does not allow me to compare two timestamps.
CAST(TO_CHAR(Completed_Date_Time, 'HH:MI:SS AM') AS CHAR(8))
Convert the values to TIMESTAMP and then you can subtract the values from the values truncated to the start of the day to get an INTERVAL containing the time since midnight and to get the difference you can subtract.
Oracle Setup:
CREATE TABLE table_name ( Business_Start_time, Completed_Date_Time ) AS
SELECT '6:00:00 AM',
TO_DATE( '5/1/2019 12:57:19 PM', 'DD/MM/YYYY HH12:MI:SS AM' )
FROM DUAL
Query:
SELECT ( completed_time - TRUNC( completed_time ) ) -
( start_time - TRUNC( start_time ) ) AS time_difference
FROM (
SELECT TO_TIMESTAMP( business_start_time, 'HH12:MI:SS AM' ) AS start_time,
CAST( Completed_Date_Time AS TIMESTAMP ) AS completed_time
FROM table_name
)
Output:
| TIME_DIFFERENCE |
| :---------------------------- |
| +000000000 06:57:19.000000000 |
db<>fiddle here
Although you wrote both the question and a comment, I'm still not sure what you have and what you want to get. Sample case would help (create table & insert into).
Meanwhile, a few words about it: when subtracting two DATE datatype values, the result is number of days, which means that - if you want to display it in a format which is easier to read & understand - you have to do some calculations (a day has 24 hours; an hour has 60 mintues; and so forth).
Here's an example:
SQL> create table test
2 (business_Start_time date,
3 completed_date_Time date
4 );
Table created.
SQL> insert into test (business_start_time, completed_date_time) values
2 (to_date('05.01.2019 12:57:19', 'dd.mm.yyyy hh24:mi:ss'),
3 to_date('05.01.2019 18:58:20', 'dd.mm.yyyy hh24:mi:ss'));
1 row created.
Simply subtracted, you'd get
SQL> select completed_date_time - business_start_time result from test;
RESULT
----------
,250706019
SQL>
Here's a function which presents such a value in another format, dd:hh:mi (days:hours:minutes) (you can omit days by setting the second parameter to 0):
SQL> create or replace
2 function f_days2ddhhmi (par_broj_dana in number, par_cb_dd in number)
3 return varchar2
4 is
5 /* Converting number of days into dd:hh:mi format
6
7 Date from Date to Diff (days) Retval
8 -------------------- -------------------- -------------- ----------------------------------
9 20.11.2018. 07:00:00 - 20.11.2018. 13:45:00 0,28125 0:06:45 (6 hours 45 minutes)
10 23.10.2018. 07:00:00 - 25.10.2018. 22:12:00 2,63333 2:15:12 (2 daysa 15 hours 12 minutes)
11
12 PAR_BROJ_DANA: 0.28125
13 PAR_CB_DD : display number of days or not? 1 - yes --> 0:06:45
14 0 - no --> 06:45
15 */
16 l_broj_dana number := round (par_broj_dana, 15); -- to avoid 1.99999999999999 days = 1 day 24 hours
17 retval varchar2 (20);
18 begin
19 with podaci
20 as (select trunc (l_broj_dana) broj_dana,
21 round (mod (l_broj_dana * 24, 24), 2) broj_sati
22 from dual)
23 select decode (par_cb_dd,
24 1, lpad (p.broj_dana, 2, '0') || ':',
25 0, null)
26 || lpad (trunc (p.broj_sati), 2, '0')
27 || ':'
28 || lpad (round ( (p.broj_sati - trunc (p.broj_sati)) * 60),
29 2,
30 '0')
31 into retval
32 from podaci p;
33
34 return retval;
35 end f_days2ddhhmi;
36 /
Function created.
Applied to the test table, you'd get
SQL> select f_days2ddhhmi(completed_date_time - business_start_time, 0) result
2 from test;
RESULT
--------------------------------------------------------------------------------
06:01
which means that the difference is 6 hours and 1 minute.
If that's what you asked, see whether you can use it. Feel free to enhance it to seconds etc. if necessary.

Oracle Week Number From Date ISO Week But From December 1

Ok, I've had a query that has been working fine that calculates the week number from December 1 (the start of our Sales Fiscal Year).
Now the requirements have changed. I still need to calculate the week number based on the field (Invoice_Date). However, instead of starting to count from December 1 (Dec 1-7, Week 1, etc.) now I need to start counting on the nearest Monday to December 1st. As I understand it, the ISO week is kind of what I'm looking for but it starts January 1. How do I modify this to work from December 1?
Any help would be greatly appreciated.
select next_day(to_date('0112' || to_char(sysdate, 'YYYY'),'ddmmyyyy') - 1, 'MONDAY') dec_mon from dual; gives you first Monday of December current year
Number of week is just ceil((sysdate - dec_mon)/7).
If you want last Monday before 1st Dec you can get it by:
select next_day(to_date('2511' || to_char(sysdate, 'YYYY'),'ddmmyyyy') - 1, 'MONDAY')
from dual;
In this proposed solution, I build a "helper table" first, showing the Monday_from and Monday_to for each fiscal year (in the third CTE, named ranges). Then I build a few test dates - I was lazy, I should have used to_date() so I can include time-of-day component as well. The join condition in the actual solution (at the end of the code) is written so it works without modification for dates with non-zero "time-of-day" component.
I used the nice feature of Oracle 11.2 which allows us to give column aliases in the declaration of CTEs - otherwise the column aliases would need to be moved inside the respective SELECTs. Otherwise the solution should work at least for Oracle 9 and above (I think).
with
y ( dt ) as (
select add_months(date '2000-12-01', 12 * level )
from dual
connect by level <= 30
),
m ( dt ) as (
select trunc(dt, 'iw') + case when dt - trunc(dt, 'iw') <= 3 then 0 else 7 end
from y
),
ranges ( monday_from, monday_to ) as (
select dt, lead(dt) over (order by dt) - 1
from m
),
test_dates ( t_date ) as (
select date '2013-02-23' from dual union all
select date '2008-12-01' from dual union all
select date '2008-04-28' from dual union all
select date '2016-11-29' from dual
)
select t_date, monday_from, 1 + trunc((t_date - monday_from)/7) as week_no
from test_dates t inner join ranges r
on t.t_date >= r.monday_from and t.t_date < r.monday_to
;
T_DATE MONDAY_FROM WEEK_NO
------------------- ------------------- ----------
2008-04-28 00:00:00 2007-12-03 00:00:00 22
2008-12-01 00:00:00 2008-12-01 00:00:00 1
2013-02-23 00:00:00 2012-12-03 00:00:00 12
2016-11-29 00:00:00 2016-11-28 00:00:00 1
The nearest Monday to any any given date is returned with the following function:
NEXT_DAY(some_date-4,'Monday')
as shown by this query:
with dts(some_date) as (
select date '2006-12-1' from dual
union all
select add_months(some_date,12)
from dts
where some_date <= date '2014-12-1'
)
select some_date
, next_day(some_date-4,'monday') nearest
, some_date - next_day(some_date-4,'monday') dist
from dts;
SOME_DATE NEAREST DIST
----------- ----------- ----------
01-DEC-2006 04-DEC-2006 -3
01-DEC-2007 03-DEC-2007 -2
01-DEC-2008 01-DEC-2008 0
01-DEC-2009 30-NOV-2009 1
01-DEC-2010 29-NOV-2010 2
01-DEC-2011 28-NOV-2011 3
01-DEC-2012 03-DEC-2012 -2
01-DEC-2013 02-DEC-2013 -1
01-DEC-2014 01-DEC-2014 0
01-DEC-2015 30-NOV-2015 1
10 rows selected

Get Gap between time range

In WORK_TIME column in my database table (EMP_WORKS), i have records as below.
WORK_TIME
19:03:00
20:00:00
21:02:00
21:54:00
23:04:00
00:02:00
i want to create a database view using these data. for it i need to get Gap between these times as below.
WORK_TIME GAP
19:03:00 -
20:00:00 00:57:00 (Gap between 19:03:00 and 20:00:00)
21:02:00 01:02:00 (Gap between 20:00:00 and 21:02:00)
21:54:00 00:52:00 (Gap between 21:02:00 and 21:54:00)
23:04:00 01:10:00 (Gap between 21:54:00 and 23:04:00)
00:02:00 00:58:00 (Gap between 23:04:00 and 00:02:00)
How could i do this ?
This query will get you the differences in hours:
SELECT
work_time,
( work_time - LAG(work_time) OVER (ORDER BY work_time) ) * 24 AS gap
FROM emp_works
Example on SQL Fiddle returns this:
WORK_TIME GAP
November, 07 2012 19:03:00+0000 (null)
November, 07 2012 20:00:00+0000 0.95
November, 07 2012 21:02:00+0000 1.033333333333
November, 07 2012 21:54:00+0000 0.866666666667
November, 07 2012 23:04:00+0000 1.166666666667
November, 08 2012 00:02:00+0000 0.966666666667
First you will need to have a primary key in the table containing the DATE/TIME field.
I have set up this demo on SQL Fiddle .. Have a look
I have represented the gap as a factor of hours between the two times. You can manipulate the figure to represent minutes, or days, whatever.
SELECT
TO_CHAR(A.WORK_TIME,'HH24:MI:SS') WORK_FROM,
TO_CHAR(B.WORK_TIME,'HH24:MI:SS') WORK_TO,
ROUND(24*(B.WORK_TIME-A.WORK_TIME),2) GAP FROM
sample A,
SAMPLE B
WHERE A.ID+1 = B.ID(+)
If your primary key values have difference greater than 1 (gaps within the values of the primary key) then you will need to offset the value dynamically like this:
SELECT
TO_CHAR(A.WORK_TIME,'HH24:MI:SS') WORK_FROM,
TO_CHAR(B.WORK_TIME,'HH24:MI:SS') WORK_TO,
ROUND(24*(B.WORK_TIME-A.WORK_TIME),2) GAP FROM
sample A,
SAMPLE B
WHERE b.ID = (select min(C.ID) from sample c where c.id>A.ID)
According to your desired result, provided in the question, you want to see time interval. And also I suppose that the WORK_TIME column is of date datatype and there is a date part(otherwise there will be a negative result of subtraction (previous value of WORK_TIME from 00.02.00)).
SQL> create table Work_times(
2 work_time
3 ) as
4 (
5 select to_date('01.01.2012 19:03:00', 'dd.mm.yyyy hh24:mi:ss') from dual union all
6 select to_date('01.01.2012 20:00:00', 'dd.mm.yyyy hh24:mi:ss') from dual union all
7 select to_date('01.01.2012 21:02:00', 'dd.mm.yyyy hh24:mi:ss') from dual union all
8 select to_date('01.01.2012 21:54:00', 'dd.mm.yyyy hh24:mi:ss') from dual union all
9 select to_date('01.01.2012 23:04:00', 'dd.mm.yyyy hh24:mi:ss') from dual union all
10 select to_date('02.01.2012 00:02:00', 'dd.mm.yyyy hh24:mi:ss') from dual
11 )
12 /
Table created
SQL>
SQL> select to_char(t.work_time, 'hh24.mi.ss') work_time
2 , (t.work_time -
3 lag(t.work_time) over(order by WORK_TIME)) day(1) to second(0) Res
4 from work_times t
5 ;
WORK_TIME RES
--------- -------------------------------------------------------------------------------
19.03.00
20.00.00 +0 00:57:00
21.02.00 +0 01:02:00
21.54.00 +0 00:52:00
23.04.00 +0 01:10:00
00.02.00 +0 00:58:00
6 rows selected

Resources