Now, to be perfectly transparent this is homework. The first part was to create a stored function to calculate the discount price of an item. It accepts one parameter for the Item ID and returns the value of the discount price. The function name is discount_price. This i have done and it works fine.
The next part is: "5. Write a script that creates and calls a stored function named item_total that calculates the total amount of an item in the Order_Items table (discount price multiplied by quantity). To do that, this function should accept one parameter for the item ID, it should use the discount_price function that you created in exercise 2, and it should return the value of the total for that item."
My question is, how do I pass the value of one function into another? I just need the basic syntax. My textbook contains no examples and I can't find a clear answer anywhere.
You can call a function within a function exactly in the same way you call it from a statement or a query; for example:
create function innerFunction(a number) return number is
begin
return a * 2;
end;
create function outerFunction(a number) return number is
begin
return innerFunction(a) * 3;
end;
create function calledFunction(a number) return number is
n number;
begin
n := outerFunction(a) * 5;
return n;
end;
SQL> select calledFunction(1) from dual;
CALLEDFUNCTION(1)
-----------------
30
SQL> select calledFunction(calledFunction(calledFunction(1))) from dual;
CALLEDFUNCTION(CALLEDFUNCTION(CALLEDFUNCTION(1)))
-------------------------------------------------
27000
SQL> declare
2 x number;
3 begin
4 x := calledFunction(1);
5 dbms_output.put_line(x);
6 end;
7 /
30
Believe this link has the examples you are looking for.
Basically you call it just like how you'd normally call it in sql-plus or sql-developer.
For example :
returl_val := SUBSTR(string_in,4,1);
Related
Can you call a PL/SQL procedure from inside a function?
I haven't come across with the practical example.So if anyone has come across with this please share.
Yes. You can call any pl/sql program from inside any other pl/sql program. A function can call a function, a procedure can call a procedure which calls a function, a function can invoke a TYPE BODY...do an INSERT..which causes a TRIGGER to fire.
A simple (not really practical) example.
Using the HR schema where you have an EMPLOYEES table which includes columns EMPLOYEE_ID, FIRST_NAME, and LAST_NAME.
I have a function that takes in an INTEGER which we use to look up an EMPLOYEE record. We take their first and last names, and concat them together, and return the value back in UPPERCASE text.
Before we do that however, we call a procedure which does nothing but take a 5 second nap using the DBMS_LOCK package.
The code:
create or replace procedure do_nothing_comments (x in integer, y in integer)
is
begin
null;
-- yeah, this is a dumb demo
dbms_lock.sleep(5);
end;
/
create or replace FUNCTION upper_name (
x IN INTEGER
) RETURN VARCHAR2 IS
upper_first_and_last VARCHAR2 (256);
BEGIN
SELECT upper (first_name)
|| ' '
|| upper (last_name)
INTO upper_first_and_last
FROM employees
WHERE employee_id = x;
do_nothing_comments (1, 2); -- here we are calling the procedure
RETURN upper_first_and_last;
END;
/
Now let's invoke the function.
DECLARE
X NUMBER;
v_Return VARCHAR2(200);
BEGIN
X := 101;
v_Return := UPPER_NAME(
X => X
);
:v_Return := v_Return;
END;
/
I'm going to do this in SQL Developer using the Execute feature with the function open:
I get the answer back...it just takes 5 seconds longer than it really needed to.
Here you go:
create or replace function demo
return varchar2
as
begin
dbms_output.put_line('Hello');
return 1;
end demo;
I am trying to create a simple function that takes in 3 parameters, 2 numbers and a string. I have written the function but am not getting the expected results from a simple select statement when using the LIKE comparison for the string.
The select from the function below returns no rows when executed with the string input value set to ebts, but if I run this as a standalone select state it returns 2 rows which what I would expect. Have used dbms output to determine if whitespace were being passed but all looks OK.
CREATE OR REPLACE FUNCTION OPC_OP.sitezone_exists
(in_site_id IN NUMBER, in_zone_id IN NUMBER, in_mod VARCHAR2)
RETURN NUMBER
IS
v_count_rec NUMBER;
v_return NUMBER;
v_mod VARCHAR2(4) := in_mod;
BEGIN
SELECT COUNT(*)
INTO v_count_rec
FROM AW_ACTIVE_ALARMS
WHERE AW_ACTIVE_ALARMS.site_id = in_site_id
AND AW_ACTIVE_ALARMS.zone_id = in_zone_id
AND AW_ACTIVE_ALARMS.module LIKE 'v_mod%';
IF v_count_rec > 0
THEN
DBMS_OUTPUT.PUT_LINE('count'||v_count_rec||'=========='||v_mod||'=============');
v_return:= 1;
RETURN (v_return);
ELSE
DBMS_OUTPUT.PUT_LINE('count'||v_count_rec||'=========='||v_mod||'=============');
v_return:= 0;
RETURN (v_return);
END IF;
END sitezone_exists;
When passing in values 12, 12, ebts the output displayed is:
count 0 ==========ebts=============
RetVal = 0
If I run the same select subtituting only passing in the above values the query returns 2 rows - I have removed the like clause of the function and it then returns 2 rows, any idea why the like part of clause is failing to match with rows.
You are trying to match a string literal with this:
AND AW_ACTIVE_ALARMS.module LIKE 'v_mod%';
Change it to:
AND AW_ACTIVE_ALARMS.module LIKE v_mod||'%';
MarioAna has the right answer, IMO, but as an aside (which is too long for a comment), your function can be better written.
You're duplicating the dbms_output code, plus it's considered best practice to have a single RETURN in a function (although I would argue that more might be ok - one in the body and one per exception in the exception block...), so you could rewrite it as:
CREATE OR REPLACE FUNCTION OPC_OP.sitezone_exists
(in_site_id IN NUMBER, in_zone_id IN NUMBER, in_mod VARCHAR2)
RETURN NUMBER
IS
v_count_rec NUMBER;
v_return NUMBER;
v_mod VARCHAR2(4) := in_mod;
BEGIN
SELECT COUNT(*)
INTO v_count_rec
FROM AW_ACTIVE_ALARMS aaa
WHERE aaa.site_id = in_site_id
AND aaa.zone_id = in_zone_id
AND aaa.module LIKE v_mod||'%';
DBMS_OUTPUT.PUT_LINE('count '||v_count_rec||'=========='||v_mod||'=============');
IF v_count_rec > 0 THEN
v_return := 1;
ELSE
v_return:= 0;
END IF;
RETURN (v_return);
END sitezone_exists;
/
I have tables(Customers,Pbasket and pp) with the following values inserted to the table
http://pastebin.com/eMUtLJn9
Basically I am tasked to create a function that finds all the product number(p#) purchased by the customer and I have to pass in the customer id(c#) as a input parameter;
This is my pl/sql script
http://pastebin.com/SqkY0P9N
I noticed that
there is no results returned for c#(100), which is correct.
but i noticed that for c#(101) and c#(102)
the result should return more than one p# but it only returns 1 result even though I had my while loop.
the output of my results
Please advice.
The function you wrote was supposed to return One row only. And also, you had a return in your LOOP. So after first iteration the control is returned back.(leaving the cursor opened for ever)
Create a TYPE of SQL Object
create type my_numbers as table of NUMBER;
/
FUNCTION returning a table!
CREATE OR REPLACE FUNCTION purchased(cId IN NUMBER)
RETURN my_numbers
IS
product VARCHAR2(45);
I NUMBER;
v_my_list my_numbers := my_numbers();
CURSOR CursorRow IS
SELECT p#
FROM customer c
LEFT OUTER JOIN pbasket pb
ON c.c# = pb.c#
LEFT OUTER JOIN pp pp
ON pb.whenfinalised = pp.whenfinalised
WHERE c.c# = cId;
CurrentPos CursorRow%ROWTYPE;
BEGIN
OPEN CursorRow;
I := 1;
FETCH CursorRow into CurrentPos;
LOOP
EXIT WHEN CursorRow%NOTFOUND
v_my_list.EXTEND;
v_my_list(I) := CurrentPos.p#;
I := I + 1;
END LOOP;
CLOSE CursorRow;
RETURN v_my_list;
END purchased;
/
Final SELECT Query:
select * FROM TABLE(CAST(purchased(100) as my_numbers));
We can also use Pipelined functions (Slight modification needed) for performance over Large resultsets!
I am trying to create a user defined avg function, e.g. to calculate an average, sum or count - are there any examples I can follow?
As #David Aldridge mentioned, the "official" way to build a user-defined aggregate function is to use Oracle Data Cartridge. But in my experience, it is simpler, faster, and less buggy to use the CAST(COLLECT method. The only downside is your SQL statement requires some extra syntax.
For example, to create a custom average function that ignores the number 1:
--Created nested table of numbers
create or replace type number_nt is table of number;
--Create a custom average function.
--For example, average everything except the number "1".
create or replace function my_avg(numbers number_nt) return number is
v_sum number := 0;
v_count number := 0;
begin
--Sum and count all the values, excluding nulls and "1".
for i in 1 .. numbers.count loop
if numbers(i) is not null and numbers(i) <> 1 then
v_sum := v_sum + numbers(i);
v_count := v_count + 1;
end if;
end loop;
if v_count = 0 then
return null;
else
return v_sum/v_count;
end if;
end;
/
Here's how to call the function:
--Regular average is 2, our custom average that excludes 1 should be 2.5.
select
avg(test_value) avg
,my_avg(cast(collect(test_value) as number_nt)) my_avg
from
(
select 1 test_value from dual union all
select 2 test_value from dual union all
select 3 test_value from dual
);
AVG MY_AVG
-- ------
2 2.5
What you're looking for is the User Defined Aggregates Function Interface, documented in the Data Cartridge Developers' Guide.
Some examples are documented here.
Thought I had followed creation pattern, but the body will not compile. What I am trying to accomplish is to develop a package to run a procedrure periodically to determine at what time and date more than 15 are in use.. Oracle 11g.
The only other data that needs to go into the table beingg inserted into the sysdate.
CREATE OR REPLACE
PACKAGE TAPES_USED AS
function TAPESCOUNT(count number) return number;
procedure INSERT_TAPES_COUNT(sysdate date, count NUMBER);
END TAPES_USED;
/
-----------------------------------------
CREATE OR REPLACE
PACKAGE body TAPES_USED AS
function TAPESCOUNT(count number) return number as count number;
begin
select count(*)
into
count
from DEV.TAPES_IN USE where count(*) > 15;
procedure INSERT_TAPES_COUNT(sysdate date, count NUMBER)as
begin
INSERT INTO DEV.TAPES_USED VALUES
(sysdate, count);
end INSERT_TAPES_COUNT;
END TAPES_USED;
/
Any help or suggestion anyone can offer will be appreciated.
CREATE OR REPLACE
PACKAGE BODY tapes_used AS
FUNCTION tapescount(in_ct NUMBER) RETURN NUMBER IS
ct NUMBER;
BEGIN
SELECT COUNT(*)
INTO ct
FROM dev.tapes_in_use;
IF ct > in_ct THEN
RETURN ct;
ELSE
RETURN NULL;
END IF;
END tapescount;
PROCEDURE insert_tapes_count(sysdt date, ct NUMBER) IS
BEGIN
INSERT INTO dev.tapes_used VALUES (sysdt, ct);
END insert_tapes_count;
END tapes_used;
/
You should refrain from using reserved words such as COUNT and SYSDATE for variable names (I don't know but that could be some of your compilation issues), so I've renamed them. Also, you forgot to END your function. I think you were missing an underscore in your table name in the FROM clause of the SELECT in your function, and you didn't have a RETURN statement in your function, which you must have.
Generally speaking, a function should accept one or more input parameters and return a single value. You're not making use of the input parameter in your function. I've implemented a suggested parameter.
As Egor notes, this isn't a realistic function, and I'm not certain about your intent here. What is the function supposed to do?
Maybe you want your function to return the Date/Time your count was exceeded? You could also combine everything into a single procedure:
PROCEDURE ck_tape_ct(min_tape_ct NUMBER) IS
ct NUMBER;
BEGIN
SELECT COUNT(*)
INTO ct
FROM dev.tapes_in_use;
IF ct > min_tape_ct THEN
INSERT INTO dev.tapes_used VALUES(SYSDATE, ct);
END IF;
END;