How can I write this procedure differently - sql

I want to write the following procedure differently so i can call it to return data as if it were a table by doing: SELECT * FROM table(package.get7DayCapacityDemandProv(1, sysdate))
Procesdure:
PROCEDURE get7DayCapacityDemandProv(p_H_id IN work_entity_data.H_id%TYPE
,p_date IN DATE
,p_capacity_day_1 OUT NUMBER
,p_demand_day_1 OUT NUMBER
,p_capacity_day_2 OUT NUMBER
,p_demand_day_2 OUT NUMBER
,p_capacity_day_3 OUT NUMBER
,p_demand_day_3 OUT NUMBER
,p_capacity_day_4 OUT NUMBER
,p_demand_day_4 OUT NUMBER
,p_capacity_day_5 OUT NUMBER
,p_demand_day_5 OUT NUMBER
,p_capacity_day_6 OUT NUMBER
,p_demand_day_6 OUT NUMBER
,p_capacity_day_7 OUT NUMBER
,p_demand_day_7 OUT NUMBER
)
IS
BEGIN
getCapacityDemandOnDayProvider(p_H_id
,p_date
,p_capacity_day_1
,p_demand_day_1
);
getCapacityDemandOnDayProvider(p_H_id
,p_date + 1
,p_capacity_day_2
,p_demand_day_2
);
getCapacityDemandOnDayProvider(p_H_id
,p_date + 2
,p_capacity_day_3
,p_demand_day_3
);
getCapacityDemandOnDayProvider(p_H_id
,p_date + 3
,p_capacity_day_4
,p_demand_day_4
);
getCapacityDemandOnDayProvider(p_H_id
,p_date + 4
,p_capacity_day_5
,p_demand_day_5
);
getCapacityDemandOnDayProvider(p_H_id
,p_date + 5
,p_capacity_day_6
,p_demand_day_6
);
getCapacityDemandOnDayProvider(p_H_id
,p_date + 6
,p_capacity_day_7
,p_demand_day_7
);
END get7DayCapacityDemandProv;

You want to (1) convert this to a function that returns a record, and then (2) convert that to a pipelined function.
Here's an example. I've left out the first parameter just so I could easily run it, but you can just add that back in.
create or replace package test
as
type theRecordType is record (
day date,
capacity number,
demand number
);
type theTableType is table of theRecordType;
function getData(p_date DATE) return theTableType pipelined;
end test;
/
create or replace package body test
as
function getData(p_date DATE) return theTableType pipelined
as
theRecord theRecordType;
begin
for i in 0..6 loop
theRecord.date := p_date + i;
theRecord.capacity := i;
theRecord.demand := i+1;
--
-- you would have a call to your procedure instead of the above two lines
-- getCapacityDemandOnDayProvider(p_H_id
-- ,theRecord.date
-- ,theRecord.capacity
-- ,theRecord.demand
-- );
--
pipe row (theRecord);
end loop;
return;
end getData;
end test;
/
You can now select from the function and get one row for each day.
select * from table(test.getData(SYSDATE));
I made it a package so the types could be declared within the package header. Alternatively, you could keep it a standalone function and declare the types within the schema using CREATE TYPE.

This is off the cuff, so it is not going to be 100% syntactically correct, but it will be conceptually correct.
create or replace package bingo IS
TYPE bingoCursor is REF CURSOR;
function get7Days(
bingoId IN bingoTable.bingoId%TYPE,
bingoDate IN date)
return bingoCursor;
end bingo;
create or replace package body bingo IS
function get7Days(
bingoId IN bingoTable.bingoId%TYPE,
bingoDate IN date)
return bingoCursor IS
sevenDaysContent bingoCursor;
begin
open sevenDaysContent for
select day 1 stuff;
union all
select day 2 stuff;
union all
... select and union all days 3 - day 7;
return sevenDaysContent;
end get7Days;
end bingo;

Sounds like you want a function or a view. A procedure's returned data can be caught and used within a SQL script but it requires dumping it into a temp or variable table first.
If your requirement is to be able to just do a SELECT * FROM something then you probably want a function or a view.

You could create a function that returns a sys_refcursor. An overly simple example:
create or replace function BLAH(somevar in varchar2) return sys_refcursor IS
v_result_cur sys_refcursor;
v_query varchar2(4000);
...
begin
...
v_query := 'select field1, field2 from blah';
...
open v_result_cur for v_query;
return v_result_cur;
...
exception
...
end;
But I will say this isn't typical. I would probably use a view (or materialized view), or turn those inner procedures into functions and simply select them, like:
select
FN_getCapacityDemandOnDayProvider(someVars) as Day1Val,
FN_getCapacityDemandOnDayProvider(someVars2) as Day2Val
from dual;

Related

Function using dateadd to return status pl sql

I have a table named Borrowing with columns id, borrow_date and duration. I want to create a function to check the status. If someone wants to borrow the book, they give 2 parameters (v_book and v_date). v_book is the book id they want to borrow and v_date is the date they want to borrow. This function checks whether the book can be borrowed or not.
Example, user input v_book=100, and v_date='5-Jan-2020'. But in the table, the book with id 100, the borrow_date is '4-Jan-2020' and the duration is 3 days. So January 4th plus 3 days is January 7th. So that means the book cannot be borrowed by January 5th.
This is my code so far and I still got an error in the dateadd. I need to write the function using Oracle PL/SQL. Any idea? Thanks!
CREATE OR REPLACE FUNCTION check_status (v_book INT, v_date DATE) RETURN VARCHAR2;
v_duration INT;
v_borrow date;
BEGIN
SELECT duration INTO v_duration FROM Borrowing WHERE id = v_book;
SELECT borrow_date INTO v_borrow FROM Borrowing WHERE id = v_book;
SELECT DATEADD(day, v_duration, v_borrow) AS DateAdd;
IF(v_date<DateAdd) THEN
RETURN 'False';
ELSE RETURN 'True';
END IF;
END;
/
DECLARE
m_book INT:=205;
m_date DATE:='5-JAN-2020';
BEGIN
if(check_status(m_book,m_date)='True') then
dbms_output.put_line('You may borrow the book');
else then dbms_output.put_line('book not available');
END;
/
dateadd isn't an Oracle function.
If you want to add days to a date in Oracle, you simply add the number of days to the date.
E.g. 2 days from now would be sysdate + 2.
N.B. if you are assigning a date to a DATE variable, please explicitly convert strings into dates first, e.g.
m_date DATE := to_date('05/01/2020', 'dd/mm/yyyy');
By forcing a string into a DATE variable, you're forcing an implicit conversion, which uses the NLS_DATE_FORMAT parameter of your session as the format of your string, e.g. Oracle will do the following behind the scenes:
m_date DATE := to_date('5-JAN-2020', <NLS_DATE_FORMAT>);
If your NLS_DATE_FORMAT doesn't match the string you've passed in, you'll get an error. By explicitly converting, you've made your code able to run on any session, regardless of the NLS_DATE_FORMAT setting.
Apart from the many syntax errors, your function will not work as a book can be borrowed many times and you need to check all the times that it has been borrowed to make sure none of them overlap with the instant you want to start borrowing.
You want to check something like this:
CREATE OR REPLACE FUNCTION check_status (
v_book BORROWING.ID%TYPE,
v_date DATE
) RETURN VARCHAR2
IS
v_is_borrowed NUMBER(1,0);
BEGIN
SELECT COUNT(*)
INTO v_is_borrowed
FROM Borrowing
WHERE id = v_book
AND borrow_date <= v_date
AND borrow_date + duration > v_date;
RETURN CASE v_is_borrowed WHEN 0 THEN 'True' ELSE 'False' END;
END;
/
Which, for the sample data:
CREATE TABLE borrowing( id, borrow_date, duration ) AS
SELECT 205, DATE '2020-01-01', 31 FROM DUAL UNION ALL
SELECT 205, DATE '2020-02-10', 10 FROM DUAL;
Then:
BEGIN
IF check_status( 205, DATE '2020-01-05' ) = 'True' THEN
dbms_output.put_line('You may borrow the book');
ELSE
dbms_output.put_line('book not available');
END IF;
END;
/
Outputs:
book not available
db<>fiddle here
You can simplify your function code as follows:
CREATE OR REPLACE FUNCTION check_status (v_book INT, v_date DATE) RETURN VARCHAR2
IS -- this was missing in your code
v_cnt number:= 0;
BEGIN
SELECT count(1)
INTO v_cnt
FROM Borrowing
WHERE id = v_book
AND v_date between borrow_date and borrow_date + duration ;
IF(cnt > 0) THEN
RETURN 'False';
ELSE RETURN 'True';
END IF;
END;
/
Also, you can call this function using the SELECT query as follows:
Select check_status(205, date'2020-01-05')
From dual;
Please note how dates are created in oracle.
Create the database table.
create table BORROWING (ID number(3), BORROW_DATE date, DURATION number(2));
Insert a sample row.
insert into BORROWING values (100, to_date('04-01-2021','DD-MM-YYYY'), 3);
Create the function. (I changed the names and types slightly.)
create or replace function CHECK_STATUS(P_BOOK BORROWING.ID%type,
P_DATE BORROWING.BORROW_DATE%type)
return boolean
is
L_DUMMY number(1);
begin
select 1
into L_DUMMY
from BORROWING
where ID = P_BOOK
and P_DATE between BORROW_DATE and (BORROW_DATE + DURATION);
return false;
exception
when NO_DATA_FOUND then
return true;
end;
/
If the desired borrow date for the desired book falls within a period where the book is already borrowed, then the function returns false.
Test the function. (Again changed the names and types.)
declare
L_BOOK BORROWING.ID%type;
L_DATE BORROWING.BORROW_DATE%type;
begin
L_BOOK := 100;
L_DATE := to_date('05-01-2021','DD-MM-YYYY');
if CHECK_STATUS(L_BOOK, L_DATE) then
DBMS_OUTPUT.PUT_LINE('You may borrow the book.');
else
DBMS_OUTPUT.PUT_LINE('Book not available.');
end if;
end;
/
Of-course the function only checks that the book is available on the intended borrow date. The function does not check whether the book can be borrowed for the intended duration. For that, you would need to check the intended duration also.
Refer to this db<>fiddle
You can put all statements into one SELECT Statement during the creation of the function
CREATE OR REPLACE FUNCTION check_status(
i_book Borrowing.Id%type,
i_date Borrowing.Borrow_Date%type
)
RETURN VARCHAR2 IS
o_borrowed VARCHAR2(5);
BEGIN
SELECT DECODE(SIGN(COUNT(*)),0,'True','False')
INTO v_borrowed
FROM Borrowing
WHERE id = i_book
AND i_date BETWEEN borrow_date AND borrow_date + duration - 1;
RETURN o_borrowed;
END;
/
and then revoke such as
DECLARE
v_book Borrowing.Id%type := 205;
v_date Borrowing.Borrow_Date%type := date'2020-01-05';
BEGIN
IF check_status(v_book, v_date) = 'True' THEN
dbms_output.put_line('You may borrow the book');
ELSE
dbms_output.put_line('book not available');
END IF;
END;
/
where there's no predefined function called DATEADD() in Oracle database.
Alternatively, you can create a PROCEDURE as
CREATE OR REPLACE PROCEDURE check_status(
i_book Borrowing.Id%type,
i_date Borrowing.Borrow_Date%type,
o_borrowed OUT VARCHAR2
) IS
BEGIN
SELECT DECODE(SIGN(COUNT(*)),0,'You may borrow the book','book not available')
INTO o_borrowed
FROM Borrowing
WHERE id = i_book
AND i_date BETWEEN borrow_date AND borrow_date + duration - 1;
END;
/
and print the description which you want out directly as
DECLARE
v_book Borrowing.Id%type := 205;
v_date Borrowing.Borrow_Date%type := date'2020-01-05';
v_borrowed VARCHAR2(50);
BEGIN
check_status(v_book, v_date,v_borrowed);
dbms_output.put_line(v_borrowed);
END;
/

Count function rather than count(*)

I am practicing for final exams coming up and in my database class we were thrown a question asking to count the rows of a table by creating a function and returning that number.
I know how to create functions but I am stuck on how to go about this.
Can you put Count(*) inside of the function with a Select statement and return it?
In this example, function accepts a parameter (table name) and returns number of rows it contains. DBMS_ASSERT is used to prevent possible SQL injection.
SQL> CREATE OR REPLACE FUNCTION f_cnt (par_table_name IN VARCHAR2)
2 RETURN NUMBER
3 IS
4 retval NUMBER;
5 BEGIN
6 EXECUTE IMMEDIATE
7 'select count(*) from ' || DBMS_ASSERT.sql_object_name (par_table_name)
8 INTO retval;
9
10 RETURN retval;
11 END;
12 /
Function created.
SQL> SELECT f_cnt ('dual') FROM DUAL;
F_CNT('DUAL')
-------------
1
SQL>
Try this.
CREATE OR REPLACE FUNCTION TotalRecords
RETURN number IS
total number;
BEGIN
SELECT count(*) into total
FROM Your_table;
RETURN total;
END;

PLS-00488: object "string" must be a type or subtype - but it is a type

I am trying to do build a sentence using a UDT, as follows:
CREATE OR REPLACE TYPE fv_group as object(
fv NUMBER,
group_number INTEGER
);
/
CREATE OR REPLACE TYPE fv_group_array IS VARRAY(100) OF fv_group;
CREATE OR REPLACE TYPE fv_grouping AS OBJECT (
fv_and_group fv_group_array,
MEMBER PROCEDURE insert_groupby(FV NUMBER),
MEMBER FUNCTION which_group(FV NUMBER) RETURN INTEGER
);
/
CREATE OR REPLACE TYPE BODY fv_grouping as
MEMBER PROCEDURE insert_groupby(FV NUMBER) IS
g fv_group;
BEGIN
IF fv < 15 THEN
g := fv_group(fv,1);
ELSE
g := fv_group(fv,2);
END IF;
fv_and_group.extend(1);
fv_and_group(fv_and_group.last) := g;
END;
MEMBER FUNCTION which_group(FV NUMBER) RETURN INTEGER IS
feature NUMBER;
BEGIN
FOR i IN 1..fv_and_group.count LOOP
feature := fv_and_group(i).fv;
IF fv_and_group(i).fv = fv THEN
RETURN fv_and_group(i).group_number;
END IF;
END LOOP;
RETURN 0;
END;
END;
/
The representation I need is:
DECLARE
obj fv_grouping;
BEGIN
SELECT :obj.which_group(gb.fv), count(*)
FROM (
SELECT :obj.insert_groupby(c.fv, 6, 3)
from cophir
) gb
GROUP BY :obj.which_group(gb.fv);
END;
/
The procedure insert_groupby inserts each value of the cophir table into a varray, which holds its values and the corresponding group.
After the varray is loaded with all values and their corresponding group, I want to group them. Is it possible, to do it in a query?
Thanks in advance!
grouping is a type. What is wrong?
GROUPING is an Oracle keyword. If you re-name your type to say FV_GROUPING you will solve the PLS-00488. Which will leave you free to address all the other syntax errors in your code:
The object type is not instantiated correctly in the query.
SELECT statements embedded in PL/SQL must select into a variable which matches the projection of the query.
Not sure what your code is trying to achieve, but this version of your code runs:
DECLARE
obj fv_grouping;
BEGIN
SELECT fv_grouping(cast(collect(fv_group(fv, 3)) as fv_group_array))
into obj
from cophir;
dbms_output.put_line(obj.which_group(2)) ;
END;
/
This uses COLLECT to gather the fv_group objects into an fv_group_array instance which can be used to instantiate fv_grouping and populate the obj variable.

How can execute dynamic sql if it is varchar2

I have PL/SQL function which is dynamically creating select statement an return this statement as varchar.Because I need this statement work dynamically(each time return different column count/name).For example it can return this select
'select id,name,currency,note from tabel t where t.id in(1,2,3,4,5,6);'
And I have another function must use this select statement result.
But the second select statement return that this string and cannot execute this select statement.
How can make first function return result as sql ?
Provided the caller knows the structure of the result:
CREATE OR REPLACE PROCEDURE execute_query(query IN VARCHAR2)
TYPE cur_typ IS REF CURSOR;
c cur_typ;
ID NUMBER;
Name VARCHAR2(20);
Currency VARCHAR2(20);
Note VARCHAR2(200);
BEGIN
OPEN c FOR query;
LOOP
FETCH c INTO ID, Name, Currency, Note;
EXIT WHEN c%NOTFOUND;
....
END LOOP;
CLOSE c;
END;
/
Use
EXECUTE IMMEDIATE Statement
as said in documentation:
https://docs.oracle.com/cd/B13789_01/appdev.101/b10807/13_elems017.htm

PLSQL SELECT INTO FROM parameter

I created a function that should return the max id from a table(parameter)
CREATE OR REPLACE FUNCTION getmaxid
(
P_TABLE IN VARCHAR2
)
RETURN NUMBER IS
v_maxId NUMBER(38);
BEGIN
SELECT MAX(id) INTO v_maxId FROM P_TABLE;
RETURN v_maxId;
END getmaxid
However, i keep getting the error message "ORA-00942: table or view does not exist" on this line:
SELECT MAX(id) INTO v_maxId FROM P_TABLE;
Like explained earlier, you need to use dynamic SQL to perform the operation. In this case, p_table is a variable. The solution to this is to build a string that will contain the SQL and dynamically execute it one you've build the query.
The example below uses, DUAL, but the table name is arbitrary.
Here is what you're looking for, take the function outside of the block, I left it like this so that you can test it..
DECLARE
FUNCTION getmaxid (p_table IN VARCHAR2)
RETURN NUMBER
IS
v_maxid NUMBER (38);
v_select VARCHAR2 (200);
cnt SYS_REFCURSOR;
BEGIN
v_select := 'SELECT COUNT(*) FROM ' || p_table;
DBMS_OUTPUT.put_line (v_select);
EXECUTE IMMEDIATE v_select INTO v_maxid;
RETURN v_maxid;
END getmaxid;
BEGIN
DBMS_OUTPUT.put_line (getmaxid ('DUAL'));
END;