I apologize ahead of time for the probably basic question. I am student and it is crunch time!
I am using Oracle 10g Express.
I created a function:
create or replace FUNCTION test_glaccounts_description
(
account_description_param VARCHAR2
)
RETURN NUMBER
AS
description_dup_var NUMBER;
BEGIN
SELECT 1
INTO description_dup_var
FROM general_ledger_accounts
WHERE account_description = account_description_param;
RETURN description_dup_var;
EXCEPTION
WHEN NO_DATA_FOUND THEN
RETURN 0;
END;
And would like to use that function in a procedure.
I've tried:
PROCEDURE insert_gla_with_test
(
account_number_param NUMBER,
account_description_param VARCHAR2
)
AS
BEGIN
IF test_glaccounts_description = 1 THEN
INSERT INTO general_ledger_accounts
VALUES (account_number_param, account_description_param);
ELSE raise_application_error (-20001, 'Duplicate account description');
END IF;
END;
But it doesn't like the "test_gla_accounts" line.....what am I doing wrong?
To my understanding, the function returns a value of 1 or 0, in the procedure, if the function returned a 1, I would like the param fields added to the table. If the function returned a 0, I would like the procedure to raise the error.
test_glaccounts_description takes a parameter (account_description_param) and returns a NUMBER. In order to call the function, therefore, you need to pass in a parameter. Assuming that you want to pass in the account_description_param that is passed in to the insert_gla_with_test procedure
CREATE OR REPLACE PROCEDURE insert_gla_with_test
(
account_number_param NUMBER,
account_description_param VARCHAR2
)
AS
BEGIN
IF test_glaccounts_description( account_description_param ) = 1 THEN
INSERT INTO general_ledger_accounts
VALUES (account_number_param, account_description_param);
ELSE
raise_application_error (-20001, 'Duplicate account description');
END IF;
END;
Related
I am fairly new to PL/SQL and one of the doubts I have is to create and declare functions.
Specifically this one:
Create a function that creates a new user:
Use a sequence togive the new User a new ID
Pass the name, address, etc... as IN arguments
Return as OUT arguments the ID created and a O_ERROR_MESSAGE
Function returns TRUE if the user is added, otherwise returns FALSE
Handle exceptions
Create a PL/SQL block and test the created function
This is my code so far:
CREATE OR REPLACE FUNCTION DSB_ADD_NEW_USERS (I_NAME IN VARCHAR2,
I_ADDRESS IN VARCHAR2,
I_BIRTHDATE IN DATE,
I_COUNTRY IN VARCHAR2)
RETURN NUMBER IS
O_ERROR_MESSAGE EXCEPTION;
CURRENT_USER NUMBER;
BEGIN
DSB_NB_SEQ_USER_ID.NEXTVAL;
SELECT COUNT(USER_ID) INTO CURRENT_USER
FROM DSB_NB_USERS;
WHILE CURRENT_USER != 0
LOOP
DSB_NB_SEQ_USER_ID.NEXTVAL;
SELECT COUNT(USER_ID) INTO CURRENT_USER
FROM DSB_NB_USERS;
END LOOP;
INSERT INTO DSB_NB_USERS (USER_ID, NAME, ADDRESS, BIRTHDATE, COUNTRY_ID_FK) VALUES (CURRENT_USER, I_NAME, I_ADDRESS, TO_DATE('I_BIRTHDATE', 'DD/MM/YYYY'), I_COUNTRY);
RETURN CURRET_USER;
EXCEPTION
WHEN O_ERROR_MESSAGE THEN
RETURN NULL;
WHEN OTHERS THEN
RETURN NULL;
END;
DECLARE
I_NEW_USER NUMBER;
BEGIN
I_NEW_USER := DSB_ADD_NEW_USERS(I_NAME => 'Arnaldo Amaral',
I_ADDRESS => 'Rua da Agra',
I_BIRTHDATE => '03/05/1959',
I_COUNTRY => 'PT');
END;
Am i too far from the truth?
I know there's still a lot to add.
Thank you for the help!!!
It seems to me you are asking for a lifeline, that needs more than just a code response. First off look at the answer by
#Tejash. His solution is a far cry from what you have, and is correct for your function definition (although that is itself incorrect given your listed requirements.) Also, #EdStevens is correct that is is a very poor use of a function. However a function is your assignment's requirement.
So lets dissect your code and see how it satisfies each of your requirements. (I cannot stand all caps, so I'm lower casing it.)
Fails. While you have a sequence it is used improperly. You can not
just code a standalone name. It must be used in either a SQL
statement or an assignment statement.
Passes.
Fails. Your function passes the IN parameters correctly. However
there are no OUT parameters.
Your function returns Number not the required True/False which
requires either Boolean or varchar2 for literal.
Code section for requirements 3,4.
create or replace function dsb_add_new_users (i_name in varchar2,
i_address in varchar2,
i_birthdate in date,
i_country in varchar2) <<< where are the 2 required out parameters
return number is <<< Should be True or False so either Boolean or varchar2
o_error_message exception; <<< Should be an Out parameter not an exception
Yes you have the Exception Section, which is required for handling
exceptions. However you do not handle exceptions so much as suppress
them, indicating to Oracle, and to the calling routine, that they didn't actually happen.
exception
when o_error_message then <<< as coded this is a developer defined exception, yet is not raised so it cannot happen.
return null; <<< even if it were raised you have thrown it away, the calling routine will never it happened
when others then <<< One of the worst pieces of code that can be written. I tend to remember seeing a discussion in
return null; <<< Oracle that would make this a compile time error. To bad the supporters lost the argument.
Passed, mostly. Correct for posted code, but insufficient per requirements.
So all-in-all not highly successful when viewed against the requirements.
A couple other items not specific the requirements, but disaster to successful implementation.
While technically you might get away with the following it is very bad practice. Current_User is an Oracle reserved word.
current_user number; <<< NO, NO bad verifiable name.
The following completely decimates your function. It creates a never ending loop. Well not quite -- it runs 1 time, but never afterward.
I believe you intended for the current_user variable to contain the ID assigned to the user. But it actually contains the number of rows in the table. Further what would the number of rows (count) have to do with Inserting a row? SO what does the code actually Do?
dsb_nb_seq_user_id.nextval; <<< systax error.
select count(user_id) into current_user <<< count the number of rows in the table
from dsb_nb_users;
while current_user != 0 <<< if there are any rows in the table
loop
dsb_nb_seq_user_id.nextval; <<< syntax error
select count(user_id) into current_user <<< try counting then again until there are none.
from dsb_nb_users;
end loop;
So correcting for the above what do we wind up with. I hesitate here in fear you will just copy the below and submit it. Please don't do that but study and understand what it's doing, then rewrite you routine. But after tearing you initial effort up, I guess I owe a corrected solution.
create or replace function dsb_add_new_users
( i_name in varchar2
, i_address in varchar2
, i_birthdate in date
, i_country in varchar2
, o_new_user_id out number
, o_error_message out varchar2
return boolean
is
country_id_l number;
begin
-- initialize out variables
o_new_user_id := null;
o_error_message := null;
-- get country id from input parameter
select country_id
into country_id_l
from country
where name = i_country;
-- create user and get the assigned user_id
insert into dsb_nb_users (user_id, name, address, birthdate, country_id_fk)
values dsb_nb_seq_user_id.nextval
, i_name
, i_address
, i_birthdate
, country_id_l
)
returning user_id
into o_new_user_id;
return true;
exception
when no_date_found then
o_output_message = 'ERROR: Specified country name ''' || country_id_l || ''' Not Found.';
return false;
when others then
o_output_message := 'ERROR: ' || sqlerrm;
return false
end dsb_add_new_users ;
--------------- Test Driver ----------------
declare
i_new_user number;
new_user_created boolean;
error_message varchar2(255);
begin
new_user_created := dsb_add_new_users( i_name => 'Arnaldo Amaral',
, i_address => 'Rua da Agra'
, i_birthdate => to_date('03/05/1959','mm/dd/yyyy') -- or is it 'dd/mm/yyyy'
, i_country => 'PT'
, o_new_user_id => i_new_user
, o_error_message => error_message);
if not new_user_created
then
dbms_output.put_line (error_messag);
end if ;
end;
Disclaimer: As you did not post table DDL nor test data the routine has not been tested nor even compiled. Any syntax error(s) is for you to resolve. Further, I follow Tejash in the assumption you actually have a countries table and there is a FK to it. That may be an erroneous assumption.
Why don't you directly use the sequence value to insert new user as follows:
CREATE OR REPLACE FUNCTION DSB_ADD_NEW_USERS (
I_NAME IN VARCHAR2,
I_ADDRESS IN VARCHAR2,
I_BIRTHDATE IN DATE,
I_COUNTRY IN VARCHAR2
) RETURN NUMBER IS
CURRENT_USER_ID NUMBER;
BEGIN
CURRENT_USER_ID := DSB_NB_SEQ_USER_ID.NEXTVAL; -- returns new and unique number
INSERT INTO DSB_NB_USERS (
USER_ID,
NAME,
ADDRESS,
BIRTHDATE,
COUNTRY_ID_FK
) VALUES (
CURRENT_USER_ID,
I_NAME,
I_ADDRESS,
I_BIRTHDATE,
(
SELECT
COUNTRY_ID
FROM
COUNTRIES
WHERE
NAME = I_COUNTRY
) -- it is FK so need to use SELECT sub-query to find COUNTRY_ID
);
RETURN CURRENT_USER_ID; -- returning the new user ID
EXCEPTION
WHEN OTHERS THEN
RETURN -1; -- returning -1 in case there is any error
END;
-1 will be returned when there is an error while adding the new user.
Came up with this much simpler answer after asking to a few people for help.
create or replace FUNCTION DSB_ADD_NEW_USERS (I_NAME IN VARCHAR2,
I_ADDRESS IN VARCHAR2,
I_BIRTHDATE IN DATE,
I_COUNTRY IN VARCHAR2)
RETURN NUMBER IS
CURRENT_USER NUMBER;
BEGIN
CURRENT_USER := DSB_NB_SEQ_USER_ID.NEXTVAL;
DBMS_OUTPUT.PUT_LINE('Hello World');
INSERT INTO DSB_NB_USERS (USER_ID, NAME, ADDRESS, BIRTHDATE, COUNTRY_ID_FK) VALUES (CURRENT_USER, I_NAME, I_ADDRESS, TO_DATE(I_BIRTHDATE, 'DD/MM/YYYY'), I_COUNTRY);
RETURN CURRENT_USER;
EXCEPTION
WHEN OTHERS THEN
RETURN -1;
END;
SET SERVEROUT ON
DECLARE
I_NEW_USER NUMBER;
BEGIN
I_NEW_USER := DSB_ADD_NEW_USERS(I_NAME => 'Arnaldo Amaral',
I_ADDRESS => 'Rua da Agra',
I_BIRTHDATE => '03/MAY/1959',
I_COUNTRY => 'PT');
commit;
END;
/
Thank you all!!!
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 have this function and I need to compare number with varchar.
CREATE OR REPLACE FUNCTION getOdds(i_odd in varchar2, i_id in number) return number as
begin
declare odd integer;
declare i_perecentage=0;
begin
if i_odd ='SP'
then
return (0);
end if;
odd:=round(to_number((1-i_perecentage/100)*i_odd),2);
if odd<1
then
return(i_odd);
else
return(round(odd,2));
end if;
end;
end;
/
PS: I edited function and i resolve problem with comparing , now i have another situation that i dont like..
This function returns calculated percentage of i_odd. The problem is that if i pass 0 in i_percentage in results i get result with no decimal places(for example: i_odd = 3.10 and i_percentage = 0 i get odd = 3 but if I pass i_odd = 3.10 and i_percentage = 1 i get odd = 3.10 ).
Why is on i_percentage = 0 i dont get decimal places ??
If you want to validate a varchar2 field as a number in PL/SQL, typically you'd just try converting it to a number and catch the exception.
CREATE OR REPLACE FUNCTION getOdds(i_odd in varchar2, i_id in number) return number as
odd number;
BEGIN
-- if i_odd = 'SP' (or another non-number), this will throw an ORA-01722
-- exception which will be caught in the exception block, below
odd := to_number(i_odd); -- you might want a format mask here
--... now you can use "odd" as a number
EXCEPTION WHEN INVALID_NUMBER THEN
return 0;
END;
/
You can also nest a begin..end block in the middle of your code just to catch exceptions, if that works better for you:
CREATE OR REPLACE FUNCTION getOdds(i_odd in varchar2, i_id in number) return number as
odd number;
begin
begin
odd := to_number(i_odd); -- you might want a format mask here
exception when INVALID_NUMBER then
odd := 0;
end;
--... now you can use "odd" as a number
end;
/
The reason why you can't catch the invalid_number exception is because you are declaring the input parameter as a number. When you call your function, Oracle tries to convert the string to a number first (and it fails of course, before entering your code at all).
If you change the input parameter to varchar2, then the conversions to number (implicit in this case) is done inside the function, and invalid numbers can be caught and handled as you want (here I'm just returning a different string to denote the issue):
create or replace function is_odd_even(i_num in varchar2)
return varchar2
is
begin
-- conversion to number is done here
if (mod(i_num, 2) = 0) then
return 'EVEN';
else
return 'ODD';
end if;
exception
when INVALID_NUMBER or VALUE_ERROR then
-- do something meaningful
return 'INV';
end;
Usage example:
with x as (
select '1' as val from dual
union all
select 'SP' as val from dual
union all
select '2' as val from dual
)
select x.val, is_odd_even(x.val)
from x;
Output:
1 ODD
SP INV
2 EVEN
SOLUTION:
CREATE OR REPLACE FUNCTION getOdds(i_odd in varchar2, i_id in number) return varchar2 as
odd varchar2(10);
ret_value number(4);
begin
if (i_odd ='SP') or i_odd is null then
return 'SP';
else
odd :=ROUND( TO_NUMBER( ( 1 - (play_beting.get_odds_percentage(i_id) / 100 ) ) * TO_NUMBER(i_odd) ), 2);
IF(odd < 1) THEN
ret_value := TO_NUMBER(i_odd);
ELSE
ret_value := to_char(odd,'9999.00');
END IF;
END IF;
RETURN to_char(ret_value,'9999.00');
END getOdds;
I am having trouble creating a function that returns multiple rows. I am trying to use a loop but I still get the "oracle exact fetch returns more than requested" error. Any help pointing me in the correct direction would be greatly appreciated.
CREATE OR REPLACE FUNCTION sections2
(
id_param NUMBER
)
RETURN VARCHAR
AS
sections_param VARCHAR(40);
BEGIN
LOOP
SELECT sections
INTO sections_param
FROM table2
WHERE id = id_param;
RETURN sections_param;
end loop;
END;
/
It sounds like you want a function that returns a collection. I'll show an example of returning a nested table
-- Use whatever length is appropriate here.
CREATE TYPE sections_tbl
IS TABLE OF VARCHAR2(40);
CREATE OR REPLACE FUNCTION sections2 (
id_param NUMBER
)
RETURN sections_tbl
AS
l_sections sections_tbl;
BEGIN
SELECT sections
BULK COLLECT INTO l_sections
FROM table2
WHERE id = id_param;
RETURN l_sections;
END;
In the caller, you'd do something like
DECLARE
l_sections sections_tbl := sections2( <<id>> );
BEGIN
FOR i IN 1 .. l_sections.count
LOOP
dbms_output.put_line( l_sections(i) );
END LOOP;
END;
I'm trying to learn how to create a user defined aggregate function. So far, I've been able to create one that compiles fine, but calling it gives an unexpected result. The function is a very simple test function that looks through a number of rows that are either set to 'Y' or 'N' and returns 'Y' if all are set to 'Y' and otherwise returns 'N'. I'm running it on a single row and getting back a blank varchar 2 instead.
I'm not sure what is the procedure to go through with debugging this. I've tried using DBMS_OUTPUT.PUT_LINE(), but I cannot see anything on the database output. The largest problem is that it is creating the function fine, and most of the code is in an object type. Thus, if I were to try to debug the select statement, it is calling code on the database that has already been compiled.
Below is the code for the function, but I don't want to know why this isn't working as much as I want to know how to debug so I can solve these issues myself, especially when more complex aggregate functions are involved.
CREATE OR REPLACE TYPE MYSCHEMA.ALL_TRUE_T AS OBJECT
(
TRUE_SO_FAR VARCHAR2(1),
STATIC FUNCTION ODCIAggregateInitialize(sctx IN OUT ALL_TRUE_T) RETURN NUMBER,
MEMBER FUNCTION ODCIAggregateIterate(self IN OUT ALL_TRUE_T, value IN VARCHAR2) RETURN NUMBER,
MEMBER FUNCTION ODCIAggregateTerminate(self IN ALL_TRUE_T, returnValue OUT VARCHAR2, flags IN NUMBER) RETURN NUMBER,
MEMBER FUNCTION ODCIAggregateMerge(self IN OUT ALL_TRUE_T, ctx2 IN ALL_TRUE_T) RETURN NUMBER
);
CREATE OR REPLACE TYPE BODY MYSCHEMA.ALL_TRUE_T IS
STATIC FUNCTION ODCIAggregateInitialize(sctx IN OUT ALL_TRUE_T)
RETURN NUMBER IS
BEGIN
sctx := ALL_TRUE_T('Y');
return ODCIConst.Success;
END;
MEMBER FUNCTION ODCIAggregateIterate(self IN OUT ALL_TRUE_T, value IN VARCHAR2)
RETURN NUMBER IS
BEGIN
IF value <> 'Y' OR self.TRUE_SO_FAR <> 'Y' THEN
self.TRUE_SO_FAR := 'N';
END IF;
RETURN ODCIConst.Success;
END;
MEMBER FUNCTION ODCIAggregateTerminate(self IN ALL_TRUE_T, returnValue OUT VARCHAR2, flags IN NUMBER)
RETURN NUMBER IS
BEGIN
returnValue := self.TRUE_SO_FAR;
RETURN ODCIConst.Success;
END;
MEMBER FUNCTION ODCIAggregateMerge(self IN OUT ALL_TRUE_T, ctx2 IN ALL_TRUE_T)
RETURN NUMBER IS
BEGIN
IF ctx2.TRUE_SO_FAR = 'N' THEN
self.TRUE_SO_FAR := 'N';
END IF;
RETURN ODCIConst.Success;
END;
END;
CREATE OR REPLACE PACKAGE MYSCHEMA.ALL_TRUE_PKG IS
FUNCTION ALL_TRUE (input VARCHAR2) RETURN VARCHAR2;
END;
CREATE OR REPLACE PACKAGE BODY MYSCHEMA.ALL_TRUE_PKG IS
FUNCTION ALL_TRUE (input VARCHAR2) RETURN VARCHAR2
AGGREGATE USING ALL_TRUE_T;
END;
And here is how I call it. YN_TEST_TABLE currently has a single row with an 'N' in it.
SELECT
MYSCHEMA.ALL_TRUE_PKG.ALL_TRUE(YN)
FROM
MYSCHEMA.YN_TEST_TABLE
Finally, I'm not sure if this is relevant, but I'm using Toad 11.6.
Edit:
So I've tried inserting into a temp log table and that didn't work either.
I added the following
MEMBER FUNCTION ODCIAggregateIterate(self IN OUT ALL_TRUE_T, value IN VARCHAR2)
RETURN NUMBER IS
BEGIN
BEGIN
INSERT INTO MYSCHEMA.LAWTONFOGLES_TEMP_LOG
(
ID,
Message,
Time
)
VALUES
(
'all_true',
'test1',
systimestamp
);
END;
IF value <> 'Y' OR self.TRUE_SO_FAR <> 'Y' THEN
self.TRUE_SO_FAR := 'N';
END IF;
RETURN ODCIConst.Success;
END;
There was nothing in the temp log, but also no error message. It is as if none of the 4 aggregate function parts are even being called.
EDIT2:
So, to make things more interesting, this works when it is not in a package.
I did the following
CREATE OR REPLACE FUNCTION MYSCHEMA.LAWTONFOGLES_ALL_TRUE (input VARCHAR2) RETURN VARCHAR2
AGGREGATE USING ALL_TRUE_T;
and then ran this
SELECT
MYSCHEMA.LAWTONFOGLES_ALL_TRUE(YN)
FROM
MYSCHEMA.YN_TEST_TABLE
and got the results I expected. It seems that the code itself isn't a problem, but putting it in a package causes it to break. Thursday my Oracle DBA will be opening a ticket up with oracle, so I'll be sure to update with why does putting this in a package break it but leaving it as just a function doesn't when they get back with us. Until then I may just have to keep this outside of a package.
Also, I tried to add a put_line on it when it was working and still did not get an output. I think that the way user defined aggregate functions work prevent put_line from working.
If you're using TOAD, be sure to turn on DBMS_OUTPUT recording before you run your proc so you can see your outputs. It should be on the bottom DBMS tab (if you have it open). Typically you'll see a red circle since it's defaulted as off. Click the circle so that it's green.
See this link as an example: http://geekbrigade.wordpress.com/2009/04/09/how-to-set-and-view-dbms-output-of-oralce-in-toad/