Training schedule till end of year - oracle

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>

Related

Where clause from a subquery

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>

No hard coding in where clause in oracle

[I have created a table as shown in figure
CREATE TABLE TABLE1 (CAL_YEAR VARCHAR2(4), NAME VARCHAR2(1))
INSERT INTO TABLE1 VALUES(‘2020’, ’A’)
INSERT INTO TABLE1 VALUES(‘2020’, ’B’)
INSERT INTO TABLE1 VALUES(‘2020’, ’C’)
INSERT INTO TABLE1 VALUES(‘2020’, ’D’)
INSERT INTO TABLE1 VALUES(‘2021’, ’E’)
INSERT INTO TABLE1 VALUES(‘2021’, ’F’)
Let us assume I am querying the statement in this year like
SELECT * FROM TABLE1 WHERE CAL_YEAR= TO_CHAR((SYSDATE),'YYYY');
so I get present year values.
What I need is if the present year data not present in the table then I need to use previous year data i.e. 2020 data...How can I do this in the where clause itself]1
You can simply get the maximum year that is less than or equal the year you query for in a subquery.
SELECT *
FROM table1
WHERE cal_year = (SELECT max(cal_year)
FROM table1
WHERE cal_year <= 2020);
You want to show the previous year, if no row for the current year exists? Simply select all years until now, order by year, take the last one.
SELECT *
FROM table1
WHERE cal_year <= EXTRACT(YEAR FROM SYSDATE)
ORDER BY cal_year DESC
FETCH FIRST ROW WITH TIES;
Optionally (if you're on a database version which doesn't support FETCH FIRST ROW WITH TIES Thorsten suggested):
with year 2021 in the table:
SQL> select * from table1
2 where cal_year = case when (select max(1) from table1
3 where exists (select null from table1
4 where cal_year = to_char(sysdate, 'yyyy')
5 )
6 ) = 1 then to_char(sysdate, 'yyyy')
7 else to_char(add_months(sysdate, -12), 'yyyy')
8 end;
CAL_ N
---- -
2021 E
2021 F
Without this year:
SQL> delete from table1 where cal_year = 2021;
2 rows deleted.
SQL>
SQL> select * from table1
2 where cal_year = case when (select max(1) from table1
3 where exists (select null from table1
4 where cal_year = to_char(sysdate, 'yyyy')
5 )
6 ) = 1 then to_char(sysdate, 'yyyy')
7 else to_char(add_months(sysdate, -12), 'yyyy')
8 end;
CAL_ N
---- -
2020 A
2020 B
2020 C
2020 D
SQL>
What does it do?
lines #2 - 5: SELECT* (within CASE) returns 1 if EXISTS says that there's at least one row whose CAL_YEAR = this year
if that's so (i.e. 1 has been returned), then CAL_YEAR is compared to this year (to_char(sysdate, 'yyyy'))
otherwise, if SELECT returns something else (most probably NULL), CAL_YEAR is compared to previous year (that's what to_char(add_months(sysdate, -12), 'yyyy') does - subtracts 12 months from today's date and extracts year from it)

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.

Calculate total time in oracle

i have same problem like here
how to calculate sum time with data type char in oracle
but little different. i have 2 table like this :
table employee
emp_id emp_name emp_birth_date
123456 sacha 18/07/1980
Using this query
create table employee (emp_id char(10), emp_name char(10), emp_birth_date date);
insert into employee values ('123456', 'sacha', (TO_DATE('18/07/1980', 'dd/mm/yyyy')));
and table dept
table dept
emp_id emp_reg_date emp_time_in emp_time_off
123456 25/12/2011 10:00:00 19:00:00
using this query
create table dept (emp_id char(10), emp_reg_date date, emp_time_in char(10), emp_time_off char(10));
insert into dept values ('123456', (TO_DATE('25/12/2011', 'dd/mm/yyyy')), '10:00:00', '19:00');
all data type is char except birth_date and reg_date
i can display emp_id, emp_name, emp_reg_date, emp_time_in, emp_time_off using this query select employee.emp_id, employee.emp_name, dept.emp_date_reg, dept.emp_time_in, dept.emp_time_off from employee, dept where employee.emp_id = dept.emp_id;
but how to calculate total time in table dept for emp_time_in and emp_time_off for a day and a month?
What an awful design; what made you create EMP_TIME_IN and EMP_TIME_OFF VARCHAR2 columns? Those should have been DATE ones. I suggest you change that.
Meanwhile, you'll have to concatenate EMP_REG_DATE and those IN and OFF columns in order to get DATE value; then, by subtracting two dates, you'd get number of DAYS and - using a little bit of mathematics - get hours, minutes, or whatever you want.
For example:
SQL> create table test
2 (empno number,
3 emp_reg_date date,
4 emp_time_in varchar2(10),
5 emp_time_off varchar2(10));
Table created.
SQL> insert into test values (1, date '2018-03-20', '10:00', '19:00');
1 row created.
SQL> insert into test values (1, date '2018-03-21', '11:30', '12:30');
1 row created.
SQL> insert into test values (2, date '2018-03-25', '13:00', '16:20');
1 row created.
Employee 1 worked 9 hours + 1 hour = 10 hours in total.
Employee 2 worked 3 hours 20 minutes.
SQL> with dates as
2 (select
3 empno,
4 to_date(to_char(emp_reg_date, 'dd.mm.yyyy') || emp_time_in , 'dd.mm.yyyy hh24:mi') date_in,
5 to_date(to_char(emp_reg_date, 'dd.mm.yyyy') || emp_time_off, 'dd.mm.yyyy hh24:mi') date_off
6 from test
7 ),
8 summary as
9 (select empno,
10 sum(date_off - date_in) diff_days
11 from dates
12 group by empno
13 )
14 select empno,
15 trunc(diff_days * 24) hours,
16 round((diff_days * 24 - trunc(diff_days * 24)) * 60) minutes
17 from summary;
EMPNO HOURS MINUTES
---------- ---------- ----------
1 10 0
2 3 20
SQL>
Note that there's practically no control over what you enter into VARCHAR2 columns TIME_IN and TIME_OUT; what prevents you from entering AX:FM or 99:45 or A-b-e_XF in there? All those are valid strings, but invalid times.
Once again: fix data model.

Can't figure out cursor for loop in a plsql procedure

I am trying to write a procedure in plsql that takes in two parameters, month and year. The procedure generates data for a table - loanreport. The procedure generates for all loan types in the loan type table. When the procedure is run it should populate the table with:
Month
Year
closed loan amount (sum of loan amounts of loans with status = 6) if no loans have status = 6 then closed loan amount is 0.
4.Average Closing Period
Here are the pertinent tables:
CREATE TABLE LOANDETAILS
(LOANNO VARCHAR2(11) primary key,
PROPERTYID VARCHAR2(10),
CUSTID CHAR(8),
LOANTYPE VARCHAR2(20),
LOANSTATUSCODE NUMBER(3,0),
LOANAMOUNT NUMBER(10,2),
RATE NUMBER(5,2),
LOANCREATIONDATE DATE,
LOANSTATUSDATE DATE,
constraint loandet_prop_fk foreign key(PROPERTYID) references PROPERTIES(propertyid),
constraint loandet_cust_fk foreign key(CUSTID) references customers(custid),
constraint loandet_lt_fk foreign key (LOANTYPE) references loantypes(loantype)
);
--insert
Insert into LOANDETAILS values ('L1000000001','P1000001','C1000001','Conventional',1,87975,9,to_date('26-JUL-2016','DD-MON-YY'),to_date('02-AUG-2016','DD-MON-YYYY'));
Insert into LOANDETAILS values ('L1000000009','P1000009','C1000009','FHA',6,160055,4.5,to_date('30-NOV-2016','DD-MON-YYYY'),to_date('07-DEC-2016','DD-MON-YYYY'));
Insert into LOANDETAILS values ('L1000000010','P1000010','C1000010','VA',2,217600,7.5,to_date('30-NOV-2016','DD-MON-YYYY'),to_date('07-DEC-2016','DD-MON-YYYY'));
CREATE TABLE LOANTYPES
(ltID char(5) constraint loantypes_pk primary key,
loantype VARCHAR2(20) constraint loantypes_lt_unique UNIQUE,
description VARCHAR2(100),
active char(1) constraint loantypes_active CHECK (active IN ('Y','N')) -- if loan type is currently being offered
);
Insert into loantypes values ('LT001', 'VA', 'Service members, veterans or eligible family','Y');
Insert into loantypes values ('LT002', 'FHA', 'Federal Housing Administration eligible loans', 'Y');
Insert into loantypes values ('LT003', 'Conventional', 'Standard loan','Y');
Insert into loantypes values ('LT004', 'Employee', 'Eligible employees of the organization','Y');
Insert into loantypes values ('LT005', 'Reconstruct', 'Relief work reconstruction','N');
CREATE TABLE LOANTYPEREPORT
(LOANTYPE VARCHAR2(20),
MONTH number(2,0),
YEAR NUMBER(4,0),
CLOSEDLOANSAMOUNT NUMBER(15,2),
AVERAGECLOSINGPERIOD NUMBER(5,2),
constraint loantr_pk PRIMARY KEY (LOANTYPE, RMONTH, RYEAR)
);
I am new to sql and I clearly have some knowledge gaps. I am attempting to create a procedure then would like to make a cursor and iterate over the cursor with a for loop to create the desired report. Here is my incomplete code:
CREATE OR REPLACE PROCEDURE loan_type_report_procedure (Month loantypereport.month%type, Year loantypereport.year%type) AS
CURSOR C1 IS
SELECT l.loantype,
loanamount,
loancreationdate,
loanstatusdate,
loanstatuscode,
to_char(LOANCREATIONDATE, 'mm') AS rMonth,
to_char(LOANCREATIONDATE, 'YYYY') AS rYEAR
FROM LOANTYPES l
JOIN LOANDETAILS d
ON l.loantype = d.loantype
WHERE Month = to_char(LOANCREATIONDATE, 'mm')
AND Year = to_char(LOANCREATIONDATE, 'YYYY')
BEGIN
FOR loan_rec in C1 LOOP
As far as my understanding goes for loop goes row by row in the cursor. If I want a final table loan type report that contains loantype, month, year, closed loan amount, and average closing period - how do I make that work? Closed loan amount and average closing period are both aggregated on the loan type grouping. Would I use a group by and having in the cursor select statement?
Thank you for your insight
If I understand your question correctly, you'd like to summarize the number of loans by their LOANTYPE, for each month, and for the loans that were closed (LOANSTATUSCODE = 6), you'd like to SUM their amount and record an AVERAGE of their loan time span.
From the look of LOANTYPEREPORT, it looks like you are planning for this average span to be in a number of days.
To accomplish this, you do not need to use PL/SQL. This can be done with traditional SQL. I'm only guessing at what criteria go into deciding a loan's duration, so I'll outline an example below with a couple variations.
In this example, I needed to modify your tables a little, since they reference other tables not included in your post (I dropped LOANDET_PROP_FK and LOANDET_CUST_FK).
After creating your example tables, create the LOANTYPEREPORT table (modified slightly from your example to compile):
CREATE TABLE LOANTYPEREPORT
(LOANTYPE VARCHAR2(20),
MONTH NUMBER(2,0),
YEAR NUMBER(4,0),
CLOSEDLOANSAMOUNT NUMBER(15,2),
AVERAGECLOSINGPERIOD NUMBER(5,2),
CONSTRAINT LOANTR_PK PRIMARY KEY (LOANTYPE, MONTH, YEAR)
);
Also, I added a little extra data for some FHA and Conventional loans, to help differentiate the SUMs and durations in the examples below.
Insert into LOANDETAILS values ('L1000000001','P1000001','C1000001','Conventional',1,87975,9,to_date('26-JUL-2016','DD-MON-YY'),to_date('02-AUG-2016','DD-MON-YYYY'));
Insert into LOANDETAILS values ('L1000000009','P1000009','C1000009','FHA',6,160055,4.5,to_date('30-NOV-2016','DD-MON-YYYY'),to_date('07-DEC-2016','DD-MON-YYYY'));
Insert into LOANDETAILS values ('L1000000010','P1000010','C1000010','VA',2,217600,7.5,to_date('30-NOV-2016','DD-MON-YYYY'),to_date('07-DEC-2016','DD-MON-YYYY'));
Insert into LOANDETAILS values ('L1000000011','P1000010','C1000010','VA',6,217600,7.5,to_date('30-NOV-2016','DD-MON-YYYY'),to_date('07-DEC-2016','DD-MON-YYYY'));
Insert into LOANDETAILS values ('L1000000012','P1000010','C1000010','VA',6,111111,7.5,to_date('30-NOV-2016','DD-MON-YYYY'),to_date('17-DEC-2016','DD-MON-YYYY'));
Insert into LOANDETAILS values ('L1000000013','P1000010','C1000010','VA',2,222222,7.5,to_date('30-NOV-2016','DD-MON-YYYY'),to_date('27-DEC-2016','DD-MON-YYYY'));
Insert into LOANDETAILS values ('L1000000014','P1000010','C1000010','Conventional',6,333333,7.5,to_date('30-NOV-2016','DD-MON-YYYY'),to_date('27-JAN-2017','DD-MON-YYYY'));
Insert into LOANDETAILS values ('L1000000015','P1000010','C1000010','Conventional',5,333333,7.5,to_date('30-NOV-2016','DD-MON-YYYY'),to_date('27-FEB-2017','DD-MON-YYYY'));
Insert into LOANDETAILS values ('L1000000016','P1000010','C1000010','Conventional',4,333333,7.5,to_date('30-NOV-2016','DD-MON-YYYY'),to_date('27-MAR-2017','DD-MON-YYYY'));
Insert into LOANDETAILS values ('L1000000017','P1000010','C1000010','FHA',4,444444,7.5,to_date('30-NOV-2016','DD-MON-YYYY'),to_date('27-APR-2017','DD-MON-YYYY'));
Insert into LOANDETAILS values ('L1000000018','P1000010','C1000010','FHA',6,200000,7.5,to_date('30-NOV-2016','DD-MON-YYYY'),to_date('27-APR-2017','DD-MON-YYYY'));
Insert into LOANDETAILS values ('L1000000019','P1000010','C1000010','FHA',6,300000,7.5,to_date('30-NOV-2016','DD-MON-YYYY'),to_date('27-MAY-2017','DD-MON-YYYY'));
Insert into LOANDETAILS values ('L1000000020','P1000010','C1000010','FHA',6,300000,7.5,to_date('30-NOV-2016','DD-MON-YYYY'),to_date('22-MAY-2017','DD-MON-YYYY'));
Then, you can load the report table.
Example 1 This assumes that only closed loans should be included in the loan-duration average, that the loan-duration is between LOANCREATIONDATE and LOANSTATUSDATE, and that you only want data for months and loan-types where loans were actually closed. This means july 2016 will not be included at all, since no loans were closed in that month.
INSERT INTO LOANTYPEREPORT
SELECT
LOANDETAILS.LOANTYPE,
EXTRACT(MONTH FROM LOANDETAILS.LOANSTATUSDATE) AS MONTH,
EXTRACT(YEAR FROM LOANDETAILS.LOANSTATUSDATE) AS YEAR,
SUM(LOANDETAILS.LOANAMOUNT) AS CLOSED_LOAN_AMOUNT,
AVG(LOANDETAILS.LOANSTATUSDATE - LOANDETAILS.LOANCREATIONDATE) AS AVERAGE_LOAN_DURATION
FROM LOANDETAILS
WHERE LOANDETAILS.LOANSTATUSCODE = 6
GROUP BY
LOANDETAILS.LOANTYPE,
EXTRACT(YEAR FROM LOANDETAILS.LOANSTATUSDATE),
EXTRACT(MONTH FROM LOANDETAILS.LOANSTATUSDATE);
Then see what it did:
SELECT YEAR,MONTH,LOANTYPE,CLOSEDLOANSAMOUNT,AVERAGECLOSINGPERIOD
FROM LOANTYPEREPORT
ORDER BY YEAR, MONTH, LOANTYPE;
YEAR MONTH LOANTYPE CLOSEDLOANSAMOUNT AVERAGECLOSINGPERIOD
2016 12 FHA 160055 7
2016 12 VA 328711 12
2017 1 Conventional 333333 58
2017 4 FHA 200000 148
2017 5 FHA 600000 175.5
Example 2: But if you want to include data for months where no loans were closed for a given loan-type (I'm not sure but this might be the third item in your post), then you'll need to enumerate the months.
You can do this several ways, but I'll just include an extra query in this example that sets boundaries for the for 2016 - 2018.
Run the insert:
INSERT INTO LOANTYPEREPORT
WITH YEAR_MONTH AS(
SELECT THE_MONTH.MONTH_NUMBER,
THE_YEAR.YEAR_NUMBER
FROM
(SELECT LEVEL AS MONTH_NUMBER FROM DUAL CONNECT BY LEVEL < 13) THE_MONTH
CROSS JOIN
(SELECT YEAR_NUMBER FROM
(SELECT LEVEL AS YEAR_NUMBER FROM DUAL CONNECT BY LEVEL < 2019)
WHERE YEAR_NUMBER BETWEEN 2016 AND 2018) THE_YEAR
)
SELECT
LOANTYPES.LOANTYPE,
YEAR_MONTH.MONTH_NUMBER,
YEAR_MONTH.YEAR_NUMBER,
SUM(COALESCE(CLOSED_LOAN.LOANAMOUNT,0)) AS CLOSED_LOAN_AMOUNT,
AVG(CLOSED_LOAN.LOAN_DURATION) AS AVERAGE_LOAN_DURATION
FROM
YEAR_MONTH
CROSS JOIN LOANTYPES
LEFT OUTER JOIN (SELECT EXTRACT(MONTH FROM LOANDETAILS.LOANSTATUSDATE) AS MONTH,
EXTRACT(YEAR FROM LOANDETAILS.LOANSTATUSDATE) AS YEAR,
LOANDETAILS.LOANTYPE,
LOANDETAILS.LOANAMOUNT,
LOANDETAILS.LOANSTATUSDATE - LOANDETAILS.LOANCREATIONDATE AS LOAN_DURATION
FROM LOANDETAILS
WHERE LOANDETAILS.LOANSTATUSCODE = 6) CLOSED_LOAN
ON YEAR_MONTH.YEAR_NUMBER = CLOSED_LOAN.YEAR
AND YEAR_MONTH.MONTH_NUMBER = CLOSED_LOAN.MONTH
AND LOANTYPES.LOANTYPE = CLOSED_LOAN.LOANTYPE
GROUP BY YEAR_NUMBER, MONTH_NUMBER, LOANTYPES.LOANTYPE
ORDER BY YEAR_NUMBER, MONTH_NUMBER, LOANTYPES.LOANTYPE;
And see what data is generated:
YEAR MONTH LOANTYPE CLOSEDLOANSAMOUNT AVERAGECLOSINGPERIOD
2016 1 Conventional 0
2016 1 Employee 0
2016 1 FHA 0
2016 1 Reconstruct 0
2016 1 VA 0
2016 2 Conventional 0
...
...
...
2016 12 Conventional 0
2016 12 Employee 0
2016 12 FHA 160055 7
2016 12 Reconstruct 0
2016 12 VA 328711 12
2017 1 Conventional 333333 58
2017 1 Employee 0
2017 1 FHA 0
2017 4 FHA 200000 148
2017 4 Reconstruct 0
2017 4 VA 0
2017 5 Conventional 0
2017 5 Employee 0
2017 5 FHA 600000 175.5
2017 5 Reconstruct 0
2017 5 VA 0
2017 6 Conventional 0
2017 6 Employee 0
2017 6 FHA 0
This way you get sum of closing amounts for loans closed each month of each type, or zero if none were closed.
EDIT with an example of returning from a function.
Just to reiterate, you do not need to use a function to do this kind of reporting.
But if you have a requirement to use a function, here is an example:
First, create your return type:
CREATE TYPE LOAN_TYPE_MONTH_REPORT IS OBJECT (
LOANTYPE VARCHAR2(20),
MONTH number(2,0),
YEAR NUMBER(4,0),
CLOSEDLOANSAMOUNT NUMBER(15,2),
AVERAGECLOSINGPERIOD NUMBER(5,2)
);
/
CREATE TYPE LOAN_TYPE_MONTH_REPORT_LIST IS TABLE OF LOAN_TYPE_MONTH_REPORT;
/
Then create your function:
CREATE FUNCTION GET_LOAN_TYPE_REPORT_FOR_MONTH(P_YEAR IN NUMBER, P_MONTH IN NUMBER)
RETURN LOAN_TYPE_MONTH_REPORT_LIST
IS
V_MONTH_REPORT LOAN_TYPE_MONTH_REPORT_LIST;
BEGIN
SELECT
LOAN_TYPE_MONTH_REPORT(
THE_MONTH_YEAR.LOANTYPE,
THE_MONTH_YEAR.THE_MONTH,
THE_MONTH_YEAR.THE_YEAR,
COALESCE(CLOSED_LOAN_SUMMARY.CLOSED_LOAN_AMOUNT,0),
CLOSED_LOAN_SUMMARY.AVERAGE_LOAN_DURATION)
BULK COLLECT INTO V_MONTH_REPORT
FROM
(SELECT P_YEAR AS THE_YEAR, P_MONTH AS THE_MONTH, LOANTYPES.LOANTYPE FROM LOANTYPES) THE_MONTH_YEAR
LEFT OUTER JOIN
(SELECT
LOANDETAILS.LOANTYPE,
EXTRACT(MONTH FROM LOANDETAILS.LOANSTATUSDATE) AS THE_MONTH,
EXTRACT(YEAR FROM LOANDETAILS.LOANSTATUSDATE) AS THE_YEAR,
SUM(LOANDETAILS.LOANAMOUNT) AS CLOSED_LOAN_AMOUNT,
AVG(LOANDETAILS.LOANSTATUSDATE - LOANDETAILS.LOANCREATIONDATE) AS AVERAGE_LOAN_DURATION
FROM LOANDETAILS
WHERE LOANDETAILS.LOANSTATUSCODE = 6
GROUP BY
LOANDETAILS.LOANTYPE,
EXTRACT(YEAR FROM LOANDETAILS.LOANSTATUSDATE),
EXTRACT(MONTH FROM LOANDETAILS.LOANSTATUSDATE)) CLOSED_LOAN_SUMMARY
ON THE_MONTH_YEAR.THE_YEAR = CLOSED_LOAN_SUMMARY.THE_YEAR
AND THE_MONTH_YEAR.THE_MONTH = CLOSED_LOAN_SUMMARY.THE_MONTH
AND THE_MONTH_YEAR.LOANTYPE = CLOSED_LOAN_SUMMARY.LOANTYPE;
RETURN V_MONTH_REPORT;
END;
/
Then test it:
Here's a month with two loan-types with closings:
SELECT * FROM TABLE(GET_LOAN_TYPE_REPORT_FOR_MONTH(2016,12));
LOANTYPE MONTH YEAR CLOSEDLOANSAMOUNT AVERAGECLOSINGPERIOD
Employee 12 2016 0
VA 12 2016 328711 12
Reconstruct 12 2016 0
FHA 12 2016 160055 7
Conventional 12 2016 0
Or a month with just one loan-type with a closing:
SELECT * FROM TABLE(GET_LOAN_TYPE_REPORT_FOR_MONTH(2017,01));
LOANTYPE MONTH YEAR CLOSEDLOANSAMOUNT AVERAGECLOSINGPERIOD
Employee 1 2017 0
Reconstruct 1 2017 0
FHA 1 2017 0
VA 1 2017 0
Conventional 1 2017 333333 58

Resources