How can this piece of PLSQL be made to compile? - oracle

I need to return the names of employees in string format for all those employees whose manager ID depends on the passed parameter. When I compile the function I get an error. Here is the function code:
create or replace function Employee(v_manid IN employees.manager_id%type)
return varchar2
AS
cursor cur_emp is select last_name from employees where manager_id = v_manid;
v_names varchar2(10);
begin
for emp_rec in cur_emp
loop
v_name = v_name || emp_rec.last_name ||', ';
end loop;
return v_name
end;
/
The error is:
Error(8,8): PLS-00103: Encountered the symbol "=" when expecting one
of the following: := . ( # % ; Error(8,44): PLS-00103:
Encountered the symbol ";" when expecting one of the following: )
, * & - + / at mod remainder rem and or ||
Could anyone help me with this?

As stated in the other answers the reason why your function won't compile is threefold.
You've declared the variable v_names and are referencing it as v_name.
The assignment operator in PL/SQL is :=, you're using the equality operator =.
You're missing a semi-colon in your return statement; it should be return v_name;
It won't stop the function from compiling but the variable v_names is declared as a varchar2(10). It's highly unlikely that when a manager with multiple subordinates all their last names will fit into this. You should probably declare this variable with the maximum size; just in case.
I would like to add that you're doing this a highly inefficient way. If you were to do the string aggregation in SQL as opposed to a PL/SQL loop it would be better. From 11g release 2 you have the listagg() function; if you're using a version prior to that there are plenty of other string aggregation techniques to achieve the same result.
create or replace function employee ( p_manid in employees.manager_id%type
) return varchar2 is
v_names varchar2(32767); -- Maximum size, just in case
begin
select listagg(lastname, ', ') within group ( order by lastname )
into v_names
from employees
where manager_id = p_manid;
return v_names;
exception when no_data_found then
return null;
end;
/
Please note a few other changes I've made:
Prepend a different letter onto the function parameter than the variable to make it clear which is which.
Add in some exception handling to deal with there being no data for that particular manager.
You would have returned , if you had no data I return NULL. If you want to return a comma instead simply put this inside the exception.
Rather than bother to create a cursor and loop through it etc I let Oracle do the heavy lifting.
It's rather curious that you would want to return a comma delimited list as there is little that you would be able to do with it in Oracle afterwards. It might be more normal to return something like an array or an open cursor containing all the surnames. I assume, in this answer, that you have a good reason for doing what you are.

There are a couple of things to be noted.
Declared as v_names but used as v_name
Assignemnt should be like v_name := v_name || emp_rec.last_name ||
', ';
v_name is declared with size of 10, it would be too small and would
give an error when you execute, so you could declare as
v_name employees.last_name%TYPE;
You could create your function as
CREATE OR REPLACE FUNCTION employee (v_manid IN employees.manager_id%TYPE)
RETURN VARCHAR2
AS
v_name employees.last_name%TYPE;
CURSOR cur_emp
IS
SELECT last_name
FROM employees
WHERE manager_id = v_manid;
BEGIN
FOR emp_rec IN cur_emp
LOOP
v_name := v_name || emp_rec.last_name || ', ';
END LOOP;
RETURN v_name;
END;
/

I guess you should use := instead of =
like
v_name := v_name || emp_rec.last_name ||', ';
one more thing you also need to add semicolon ; at the end of return v_name like
return v_name;

Related

Input sanitization - Numeric values

I've been asked to do input validation in order to prevent sql injection. I've been using dbms assert package functions to do the sanitization. However, when I try to sanitize a number(I'm getting it in varchar2(12 byte)) error is thrown. It's the same case with alphanumeric characters starting with number.
I tried various functions of dbms assert. Nothing seems to work except noop. But, noop is of no use since it does not do any validation.
create or replace procedure employee
(
v_emp_id IN varchar2(12 byte)
)
AS
lv_query CLOB;
BEGIN
if v_emp_id is NOT NULL THEN
lv_query := 'select * from employee where emp_id=''' || dbms_assert.enquote_name(v_emp_id) || '''';
--I also tried below:
-- lv_query := 'select * from employee where emp_id=''' || dbms_assert.simple_sql_name(v_emp_id) || '''';
end if;
END
No source gives more detailed input on dbms_assert package. Please help me in
Whether dbms_assert package can be used to sanitize numeric values(stored in VARCHAR2 variables). If yes, how?
Other ways of sanitizing input. (other than using bind variables)
Thanks.
Oracle 12.2 and higher
If you are on Oracle 12.2 or higher, you can use the VALIDATE_CONVERSION function which would be the simplest solution. Your code could potentially look something like this:
CREATE OR REPLACE PROCEDURE employee (v_emp_id IN VARCHAR2)
AS
lv_query CLOB;
BEGIN
IF v_emp_id IS NOT NULL AND validate_conversion (v_emp_id AS NUMBER) = 1
THEN
lv_query := 'select * from employee where emp_id = ' || v_emp_id;
ELSE
--do something here with an invalid number
null;
END IF;
END;
/
Earlier than Oracle 12.2
If you are not on Oracle 12.2 or higher, you can write your own small function to validate that the value is a number. Using a method similar to what Belayer suggested, just attempt to convert the value to a number using the TO_NUMBER function and if it fails, then you know it's not a number. In my example, I have it as a small anonymous block within the code but you can also make it a standalone function if you wish.
CREATE OR REPLACE PROCEDURE employee (v_emp_id IN VARCHAR2)
AS
lv_query CLOB;
l_is_number BOOLEAN;
BEGIN
--Verify that the parameter is a number
DECLARE
l_test_num NUMBER;
BEGIN
l_test_num := TO_NUMBER (v_emp_id);
l_is_number := TRUE;
EXCEPTION
WHEN VALUE_ERROR
THEN
l_is_number := FALSE;
END;
--Finished verifying if the parameter is a number
IF v_emp_id IS NOT NULL AND l_is_number
THEN
lv_query := 'select * from employee where emp_id = ' || v_emp_id;
ELSE
--do something here with an invalid number
null;
END IF;
END;
/
Well if you cannot change the procedure it means you have no test as that procedure will not compile, so it cannot be executed. However that may be a moot point. You need to define exactly what you mean by "sanitize numeric values". Do you mean validate a string contains a numeric value. If so DBMS_ASSERT will not do that. (Note: The function chooses ENQUOTE_NAME will uppercase the string and put double quotes (") around it thus making it a valid object name.) Further your particular validation may require you define a valid numeric value, is it: an integer, a floating point, is scientific nation permitted, is there a required precision and scale that must be satisfied, etc. As a brute force validation you can simulate the assertion by just convert to number. The following will do that. Like dbms_assert if the assertion is successful it returns the input string. Unlike dbms_assert, however, when the assertion fails it just returns null instead of raising an exception. See fiddle.
create or replace
function assert_is_numeric(value_in varchar2)
return varchar2
is
not_numeric exception;
pragma exception_init (not_numeric,-06502);
l_numeric number;
begin
l_numeric := to_number(value_in);
return value_in;
exception
when not_numeric then
return null;
end assert_is_numeric;

PLS-00382: expression is of wrong type by executing function and try to put the returntype in a variable

When I execute the function I get that the expression is of the wrong type but I don't know why. The return type that I use in the function is the same as where I try to put it in after the function is executed.
Below you find the record and table type.
TYPE department_id_table_type IS TABLE of DEPARTMENTS.DEPARTMENT_ID%TYPE;
TYPE managers_rec_type IS RECORD (
employee_id employees.employee_id%TYPE,
first_name employees.first_name%TYPE,
last_name employees.last_name%TYPE,
department_id_table_type DEPARTMENTS.DEPARTMENT_ID%TYPE);
TYPE managers_table_type IS TABLE OF managers_rec_type INDEX BY BINARY_INTEGER;
Below you find the function
FUNCTION managers_multiple_departments RETURN managers_table_type
IS
cursor department_curs is SELECT DEPARTMENT_NAME,MANAGER_ID,DEPARTMENT_ID FROM DEPARTMENTS WHERE MANAGER_ID IN (SELECT MANAGER_ID FROM DEPARTMENTS dep GROUP BY (MANAGER_ID) HAVING COUNT(MANAGER_ID) >1);
department_name departments.department_name%TYPE;
department_id departments.department_id%TYPE;
managerid departments.manager_id%TYPE;
employeeid employees.employee_id%TYPE;
firstname employees.first_name%TYPE;
lastname employees.last_name%TYPE;
count NUMBER;
rec managers_rec_type;
managers_rec managers_rec_type;
teller NUMBER := 1;
managers_table managers_table_type;
BEGIN
OPEN department_curs;
LOOP
FETCH department_curs INTO department_name, managerid,department_id;
EXIT WHEN department_curs%NOTFOUND;
Select EMPLOYEE_ID,FIRST_NAME,LAST_NAME,department_id into managers_rec from EMPLOYEES where MANAGER_ID = managerid;
managers_table(managers_rec.employee_id) := managers_rec;
FOR i IN managers_table.FIRST .. managers_table.LAST LOOP
IF managers_table.EXISTS(i) THEN
DBMS_OUTPUT.PUT_LINE(managers_table(i).first_name);
END IF;
END LOOP;
IF teller = 1 THEN
DBMS_OUTPUT.PUT_LINE(managers_rec.first_name ||' '|| managers_rec.last_name || ' Lijst van departments:');
teller := 2;
END IF;
DBMS_OUTPUT.PUT_LINE(department_id ||' '|| department_name);
END LOOP;
FOR i IN managers_table.FIRST .. managers_table.LAST LOOP
IF managers_table.EXISTS(i) THEN
DBMS_OUTPUT.PUT_LINE(managers_table(i).first_name);
DBMS_OUTPUT.PUT_LINE('test');
END IF;
END LOOP;
return managers_table;
END managers_multiple_departments;`enter code here`
Below is where I execute the function but this is where it is giving me the error on: managers := hr_package.managers_multiple_departments;
DECLARE
TYPE managers_rec_type IS RECORD (
employee_id employees.employee_id%TYPE,
first_name employees.first_name%TYPE,
last_name employees.last_name%TYPE,
department_id_table_type DEPARTMENTS.DEPARTMENT_ID%TYPE);
TYPE managers_table_type IS TABLE OF managers_rec_type INDEX BY BINARY_INTEGER;
man_rec managers_rec_type;
managers managers_table_type;
twee NUMBER;
BEGIN
managers := hr_package.managers_multiple_departments;
END;
You are declaring the record type, collection/table type and function all within the same package.
When you call the function you have to use the same type, from that package.
The return type that I use in the function is the same as where I try to put it in after the function is executed.
But it isn't. It has the same structure - fields and datatypes - but is not the same as far as Oracle is concerned. Oracle needs to know that exactly the same type is being used, partly so that it can keep track of dependencies between objects.
Your anonymous block needs to refer to the package types, rather than declaring its own - similar but conflicting - type(s):
DECLARE
managers hr_package.managers_table_type;
BEGIN
managers := hr_package.managers_multiple_departments;
END;
As a bonus it involves much less typing, and means you don't have to manage duplicate types.
It does also mean, though, that the type declarations have to be in the package specification - which is the case for anything you want to be publicly visible, of course.

how can we return records from pl/sql stored procedure without taking out parameter

My Question is "How can we return multiple records from pl/sql stored procedure without taking OUT parameter".I got this doubt because if we are using cursors or refcursor in out parameter it may degrade performance.So what is the solution??
As OldProgrammer wrote, i think the performance of a cursor wouldn't be you problem. But here a Solution anyway:
You can return custom types like Table of number. If it's only a list of numbers you could return a table of numbers. If you Want to return rows from a table you could return table of 'tablename'%ROWTYPE. But i guess you want to create some custom types.
CREATE OR REPLACE TYPE PUWB_INT.MyOrderType AS OBJECT
(
OrderId NUMBER,
OrderName VARCHAR2 (255)
)
/
CREATE OR REPLACE TYPE PUWB_INT.MyOrderListType AS TABLE OF MYORDERtype
/
Now we can use them similar to a return myNumberVariable;
Let's build a function (procedures don't have return values):
CREATE OR REPLACE FUNCTION PUWB_INT.MyFunction (SomeInput VARCHAR2)
RETURN MyOrderListType
IS
myOrderList MyOrderListType := MyOrderListType ();
BEGIN
FOR o IN (SELECT 1 AS Id, 'One' AS Name FROM DUAL
UNION ALL
SELECT 2 AS Id, 'Two' AS Name FROM DUAL)
LOOP
myOrderList.EXTEND ();
myOrderList (myOrderList.COUNT) := MyOrderType (o.Id, o.Name || '(' || SomeInput || ')');
END LOOP;
RETURN myOrderList;
END MyFunction;
/
Now we can call the function and get a table of our custom-type:
DECLARE
myOrderList MyOrderListType;
myOrder MyOrderType;
BEGIN
myOrderList := MyFunction ('test');
FOR o IN myOrderList.FIRST .. myOrderList.LAST
LOOP
myOrder := myOrderList (o);
DBMS_OUTPUT.put_line ('Id: ' || myOrder.OrderId || ', Name: ' || myOrder.OrderName);
END LOOP;
END;
Be aware, that the calling schema, has to know the type.

Oracle Apex procedure using loop

I am trying to get the user_id and group_id for individual user.
I have used user's email in loop because so many users are there, but I need the loop should take one by one, currently its taking all mail ids like : abinnaya.moorthy#abc.com,abinnaya.moorthy#def.com.
Because of this the select query is not returning any value.
The select query should return the value one by one by taking the email id from loop.
code:
DECLARE
L_USERS varchar2(1000);
l_org_group_id varchar2(1000);
l_user_id varchar2(1000);
l_api_body varchar2(1000);
l_retry_after number;
l_status number;
L_NOT_PROVISIONED_USERS varchar2(1000);
l_success boolean;
l_user varchar2(1000);
BEGIN
FOR I IN
(Select REQUESTORS_NAME into L_USER
from Request
where Request_Status = 'Approved'
and Provisioning_Status is NULL )
LOOP
L_USER:= L_USER ||','||I.REQUESTORS_NAME;
select GROUP_ID INTO l_org_group_id
from WORKSPACE_GROUP
where LOWER(email)=(L_USER);
select USER_ID into l_user_id
from slackdatawarehouse.users
where lower(email) = lower(L_USER);
DBMS_OUTPUT.PUT_LINE(l_user_id);
if l_user_id is null then
l_not_provisioned_users := l_not_provisioned_users||','|| L_USER;
else
l_api_body := l_api_body || '{"value" :"'||l_user_id ||'"},';
l_users := l_users||','||l_user_id;
end if;
end loop;
end;
Help me to get the user email one by one and pass it in select query to get the groupid and user id.
Oh, boy. I presume that senior members / moderators won't be too happy with this "answer", but it is impossible to put everything into a 600-characters long comment. True, I could shorten the critic to "this is sh*t", but that won't help anyone. Though, "rules" would be obeyed. So, if it turns out that this message is deleted, sorry, everyone.
Here you go.
It is difficult to guess what you want to get as the result. You talk about Apex, but - what does this code have to do with it? DBMS_OUTPUT certainly won't work there.
Then, in DECLARE section, you use 3 variables that are never used - get rid of them.
Cursor FOR loop is the way to do that; however, remove INTO clause from SELECT, it doesn't belong here.
L_USER is concatenation of all REQUESTOR_NAMEs returned by cursor. It means that you shouldn't use it in SELECT GROUP_ID nor SELECT USER_ID statements as it certainly won't return anything (maybe something for the first loop iteration, but nothing for the rest of them). It looks as if you'd rather use I.REQUESTORS_NAME. Also, once you apply LOWER function to it, and then you don't - consider making it uniform.
Why do you select GROUP_ID at all? You never use it later.
L_NOT_PROVISIONED_USERS is a huge concatenation of duplicates, as you concatenate previous values with (yet another concatenation of) L_USER. Try to DBMS_OUTPUT it, you'll see.
You don't care about possible NO-DATA-FOUNDs which might well be raised by those SELECTs, as - as I've already said - they won't return anything in subsequent loop iterations.
Finally, even if that PL/SQL finishes successfully, it won't do anything. Nobody, including you, wont' benefit from it.
So, this is quite a mess ... try to follow what I've written, make a clear picture of what you want to get as a result, go step-by-step, test frequently and - hopefully - you'll get something useful.
Do it as below:
DECLARE
L_USERS VARCHAR2 (1000);
L_ORG_GROUP_ID VARCHAR2 (1000);
L_USER_ID VARCHAR2 (1000);
L_API_BODY VARCHAR2 (1000);
L_RETRY_AFTER NUMBER;
L_STATUS NUMBER;
L_NOT_PROVISIONED_USERS VARCHAR2 (1000);
L_SUCCESS BOOLEAN;
L_USER VARCHAR2 (1000);
CURSOR EMAIL_IDS
IS
SELECT REQUESTORS_NAME L_USER
FROM REQUEST
WHERE REQUEST_STATUS = 'Approved'
AND PROVISIONING_STATUS IS NULL;
BEGIN
FOR I IN EMAIL_IDS
LOOP
SELECT GROUP_ID
INTO L_ORG_GROUP_ID
FROM WORKSPACE_GROUP
WHERE LOWER (EMAIL) = LOWER (I.L_USER);
SELECT USER_ID
INTO L_USER_ID
FROM SLACKDATAWAREHOUSE.USERS
WHERE LOWER (EMAIL) = LOWER (I.L_USER);
DBMS_OUTPUT.PUT_LINE (L_USER_ID);
IF L_USER_ID IS NULL THEN
L_NOT_PROVISIONED_USERS :=
L_NOT_PROVISIONED_USERS || ',' || I.L_USER;
ELSE
L_API_BODY :=
L_API_BODY || '{"value" :"' || L_USER_ID || '"},';
L_USERS := L_USERS || ',' || L_USER_ID;
END IF;
END LOOP;
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE (SQLERRM);
END;

How to escape a round bracket '(' in SQL?

I am trying to use reference cursor for a sql query but I think I am missing >escape notation somewhere. I have already escaped " ' " but I am unsure about >the round brackets.
I am getting "international_flag : invalid identifier" error at open ref_cursor statement. I have tried a bunch of things to escape round brackets because I think that is why it is not picking the variable international_flag. Any leads will be much appreciated.
declare
international_flag varchar2(4) := 'Y';
term_code varchar(8) := '201709';
type stu_ref_cursor is ref cursor;
ref_cursor stu_ref_cursor;
ref_cursor_select_statement varchar2(1000);
begin
ref_cursor_select_statement :=
'Select
CONFID_MSG,
ETHNIC_CODE,
STUDENT_NAME,
POTSDAM_ID(STUDENT_PIDM),
CLASS,
LEVL_CODE,
AGE,
BIRTHDATE,
fp_get_coll_box(STUDENT_PIDM),
f_get_on_campus_email_addr(STUDENT_PIDM),
RESD_IND,
STUDENT_PIDM,
REG_HRS,
SGB_TERM_ADMIT,
GENDER
From SEM_REG_STUDENT_NONGPA
Where REG_TERM = term_code
And STATUS = ''AS''
And REG_TERM_STATUS = ''Y''
And
(
international_flag = ''N''
Or
(international_flag = ''Y'' And f_international_student_natn(STUDENT_PIDM) Is Not NULL)
Or
(international_flag = ''U'' and CITIZEN = ''Y'')
)
Order By STUDENT_NAME';
open ref_cursor for ref_cursor_select_statement;
end;
That isn't how you reference PL/SQL variables in dynamic SQL. You need to use placeholders prefixed by a colon, and supply the variable with the USING clause. That can mean repetition where, as in this case, you use the same variable several times. You'll need to put in three placeholders and pass in the same variable three times (ie USING international_flag,international_flag,international_flag)
DECLARE
TYPE EmpCurTyp IS REF CURSOR; -- define weak REF CURSOR type
emp_cv EmpCurTyp; -- declare cursor variable
my_ename VARCHAR2(15);
my_sal NUMBER := 1000;
BEGIN
OPEN emp_cv FOR -- open cursor variable
'SELECT ename, sal FROM emp WHERE sal > :s' USING my_sal;
...
END;
PS. It is better to prefix variables (often with a v_ but some people go fo l_ for local and g_ for global etc) to make it more obvious what is a column and what is a variable.
The beauty of dynamic sql is that you never get to know the error as it gives run time error only. The binding of variables should be check properly before constructing a dynamic sql. Here there are two variables like "TERM_CODE" and "INTERNTIONAL_FLAG". Hope the below snippet helps.
DECLARE
international_flag VARCHAR2(4) := 'Y';
TERM_CODE VARCHAR(8) := '201709';
ref_cursor sys_refcursor;
ref_cursor_select_statement VARCHAR2(1000);
BEGIN
ref_cursor_select_statement := 'Select
CONFID_MSG,
ETHNIC_CODE,
STUDENT_NAME,
POTSDAM_ID(STUDENT_PIDM),
CLASS,
LEVL_CODE,
AGE,
BIRTHDATE,
fp_get_coll_box(STUDENT_PIDM),
f_get_on_campus_email_addr(STUDENT_PIDM),
RESD_IND,
STUDENT_PIDM,
REG_HRS,
SGB_TERM_ADMIT,
GENDER
From SEM_REG_STUDENT_NONGPA
Where REG_TERM = '''||term_code||'''
And STATUS = ''AS''
And REG_TERM_STATUS = ''Y''
And
('''|| international_flag||''' = ''N''
Or
('''||international_flag||''' = ''Y'' And f_international_student_natn(STUDENT_PIDM) Is Not NULL)
Or
('||
international_flag||' = ''U'' and CITIZEN = ''Y'')
)
Order By STUDENT_NAME';
OPEN ref_cursor FOR ref_cursor_select_statement;
END;
/

Resources