I want to loop thru all the records and concatenate them into one string.
Here is the code:
create or replace PROCEDURE P_GET_TRACKING_NOS
(
P_ORDERID NUMBER,
TRACKINGNOS OUT VARCHAR2
)
IS
CURSOR C1 IS
SELECT TRACKID
FROM MULTISHIPDTL
WHERE ORDERID = P_ORDERID;
BEGIN
TRACKINGNOS := '';
FOR TRACKID_REC IN C1
LOOP
TRACKINGNOS := TRACKINGNOS + ', ' + TRACKID_REC.TRACKID;
END LOOP;
END;
Depending on how long the result is, and if it is shorter than 4000 characters, a simpler option would be to use LISTAGG, e.g.
select listagg(m.trackid, ', ') within group (order by null) result
from multishipdtl m
where m.orderid = p_orderid;
Besides, why is it a procedure? A function seems to be a better option (you can use it in SQL; a procedure with an OUT parameter requires a(n anonymous) PL/SQL block, declaring a variable which accepts the result). For example:
create or replace function f_get_tracking_nos (p_orderid in number)
return varchar2
is
retval varchar2(4000);
begin
select listagg(m.trackid, ', ') within group (order by null)
into retval
from multishipdtl m
where m.orderid = p_orderid;
return retval;
end;
Related
I have a requirement to create a function in which I have to pass Query result as input to the output query concatenate by space . The below code is roughly written. Need help in modifying the function.
CREATE or replace FUNCTION GETPGM(Year IN Number, ID IN Number)
RETURN VARCHAR2 IS
result VARCHAR2(200);
cursor getterm is
select term_code from table_term
where proc_yr = Year;
BEGIN
loop
fetch cur into TERM;
exit when cur%NOTFOUND;
select f_getp (ID,:TERM1,Year)||' ' f_getp (ID,:TERM2,Year) from dual -- output Result set
end loop;
RETURN result;
END;
Let me know if any doubts.
If you want to apply the f_getp function to every row of the query result and concatenate the results into a space delimited string then you do not need to use a cursor and can use LISTAGG:
CREATE FUNCTION GETPGM(
i_year IN table_term.proc_yr%type,
i_id IN Number
) RETURN VARCHAR2
IS
result VARCHAR2(200);
BEGIN
SELECT LISTAGG(f_getp(i_id, term_code, i_year), ' ') WITHIN GROUP (ORDER BY term_code)
INTO result
FROM table_term
WHERE proc_yr = i_year;
RETURN result;
END;
/
It is unclear what TERM1 and TERM2 are (parameters? If so, you should pass them to the function), nor what is the result supposed to be.
Anyway, see if something like this helps:
CREATE OR REPLACE FUNCTION getpgm (par_year IN NUMBER,
par_id IN NUMBER,
par_term1 IN NUMBER,
par_term2 IN NUMBER)
RETURN VARCHAR2
IS
result VARCHAR2 (200);
BEGIN
FOR cur_r IN (SELECT term_code
FROM table_term
WHERE proc_yr = par_year)
LOOP
result :=
result
|| ' '
|| f_getp (par_id, par_term1, par_year)
|| ' '
|| f_getp (par_id, par_term2, par_year);
END LOOP;
RETURN result;
END;
result should be concatenated with its "previous" value (otherwise, you'd get the last cursor's value as the result, not everything)
use cursor FOR loop as Oracle does all the dirty job for you (you don't have to declare cursor variable, open the cursor, fetch from it, worry about exiting the loop, close the cursor - note that a lot of those things your code doesn't have, while it should)
pay attention to return value's datatype; will a string whose length is 200 characters enough? The result will be a space-separated list of some values. Wouldn't you rather return a ref cursor or a collection?
I am new to PL/sql and I am currently writing some PL/SQL code that has to extract data from two tables. The code that I have so far keeps getting error PLS-00103: Encountered the symbol "RETURN" when expecting one of
the following:
* & = - + ; < / > at in is mod remainder not rem
This is my code at the moment
CREATE OR REPLACE FUNCTION LISTNATION(N_NAME in VARCHAR2, R_NAME IN VARCHAR2, R_REGIONKEY IN NUMBER)
2 RETURN VARCHAR2 IS
3 R_REGIONKEY NUMBER(3);
4 R_NAME VARCHAR2(50);
5 N_NAME VARCHAR2(50);
6
7 BEGIN
8 select r_regionkey, r_name, n_name
9 from region
10 inner join nation
11 on r_regionkey = n_regionkey;
12
13 dbms_output.put_line = (R_REGIONKEY || ' ' || R_NAME || ':' || N_NAME || ',')
14
15 END;
16 /
The immediate cause of the error you're getting(in the original version of the question) is that you're missing missing a semicolon at the end of line 4.
But there are quite a few other issues:
you are using double-quotes " around string literals instead of single quotes '. Those are for identifiers, not strings.
your local variables are the same as the arguments, but you don't need all those arguments anyway.
you aren't selecting into anything; and your query will return multiple rows.
your query doesn't have a where clause so it will look for data in all regions.
you've mangled the dbms_output call; it's missing a closing parenthesis (again, in the original question) and should not have the =.
you're returning test but haven't declared that.
So, to have the function use dbms_output to display the results - which relies on the caller handling that output, which you shouldn't assume - you could maybe do:
CREATE OR REPLACE FUNCTION LISTNATION(P_R_REGIONKEY IN NUMBER)
RETURN VARCHAR2 IS
L_R_NAME REGION.R_NAME%TYPE;
L_N_NAME NATION.N_NAME%TYPE;
BEGIN
FOR l_row IN (
select r.r_name, n.n_name
from region r
inner join nation n
on r.r_regionkey = n.n_regionkey
where r.r_regionkey = p_r_regionkey
) LOOP
dbms_output.put_line (P_R_REGIONKEY || ' ' || l_row.R_NAME || ':' || l_row.N_NAME);
END LOOP;
RETURN 'test';
END;
/
That adds a filter to your query, turns it into an implicit cursor, and loops over the results.
db<>fiddle with some made-up data, with table and column names taken from your attempt.
It isn't clear what you actually want to return; you might want a comma-separated list of nation names, in which case look at the listagg() function. For example, you could do something like:
CREATE OR REPLACE FUNCTION LISTNATION(P_R_REGIONKEY IN NUMBER)
RETURN VARCHAR2 IS
L_RESULT VARCHAR2(4000);
BEGIN
select listagg(n.n_name, ',') within group (order by n.n_name)
into l_result
from region r
inner join nation n
on r.r_regionkey = n.n_regionkey
where r.r_regionkey = p_r_regionkey;
RETURN l_result;
END;
/
and you could then call that to get back a single list of values.
db<>fiddle
Although using a PL/SQL function wrapper around that query doesn't seem very useful. Presumably this is an exercise though...
is there anyway for this to display it such that it only shows the region key and region once while listing all nations?
You can change the second function to include the region info, concatenating that with the listagg result:
CREATE OR REPLACE FUNCTION LISTNATION(P_R_REGIONKEY IN NUMBER)
RETURN VARCHAR2 IS
L_RESULT VARCHAR2(4000);
BEGIN
select r.r_regionkey || ' ' || r.r_name || ': '
|| listagg(n.n_name, ',') within group (order by n.n_name)
into l_result
from region r
inner join nation n
on r.r_regionkey = n.n_regionkey
where r.r_regionkey = p_r_regionkey
group by r.r_regionkey, r.r_name;
RETURN l_result;
END;
/
then call that for each region key you're interested in.
db<>fiddle
The way you've phrased it makes it sound like you don't want to pass in a region key and instead want to see all regions at once; and want to use dbms_output, which isn't ideal; and don't really want to return anything. So you could use a procedure instead, change the cursor query to bring back the region name, and then concatenate in the put_line call inside the loop:
CREATE OR REPLACE PROCEDURE LISTNATION IS
BEGIN
FOR l_row IN (
select r.r_regionkey, r.r_name,
listagg(n.n_name, ',') within group (order by n.n_name) as names
from region r
inner join nation n
on r.r_regionkey = n.n_regionkey
group by r.r_regionkey, r.r_name
order by r.r_regionkey
) LOOP
dbms_output.put_line (l_row.R_REGIONKEY || ' ' || l_row.R_NAME || ': ' || l_row.names);
END LOOP;
END;
/
dbms_output:
1 EMEA: FRANCE,UNITED KINGDOM
2 APAC: CHINA
db<>fiddle
There are lots of variations of course, it depends exactly what you want to have happen, but you should be able to adapt one of these approaches. Another is to have a function or procedure generate a ref cursor, but again it's not clear what you want. But using dbms_output isn't a great idea, as the caller may not be using a client that looks for and displays that.
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.
Here is one of oracle functions. There is a cursor called c_adv_course_credit which receives 2 parameters. These 2 parameters are using the where statement:
WHERE
-- year
cc.year = p_year AND
-- rela_pk
cc.sequence_number = p_sequence_number AND
cc.closed_ind = 'N';
When I run it in oracle sql developer:
SET SERVEROUTPUT ON
variable res varchar2(200);
EXECUTE :res := advp_test_cursor(2018, 92919);
select :res from dual;
The result text is always "not working"
Here is the full function (not working):
CREATE OR REPLACE Function SISD_OWNER.advp_test_cursor (
p_sequence_number IN NUMBER, -- rela_pk
p_year IN NUMBER -- year
)
RETURN VARCHAR2
IS
v_return_var VARCHAR2(300) := 'not working';
CURSOR c_adv_course_credit (
p_sequence_number IN NUMBER,
p_year IN NUMBER
)
IS
SELECT
cc.EXTERNAL_COURSE_CD
FROM
adv_course_credit cc
WHERE
cc.year = p_year AND
-- rela_pk
cc.sequence_number = p_sequence_number AND
cc.closed_ind = 'N';
BEGIN
FOR v_at_rec IN c_adv_course_credit(p_sequence_number, p_year) LOOP
v_return_var := v_at_rec.EXTERNAL_COURSE_CD;
DBMS_OUTPUT.PUT_LINE('?output = ' || v_return_var);
EXIT;
END LOOP;
RETURN v_return_var;
END;
If I change the cursor to use hard-coded numbers the function works and returns actual result.
WHERE
-- year
cc.year = 2018 AND
-- rela_pk
cc.sequence_number = 92919 AND
cc.closed_ind = 'N';
Your function is defined as (ignoring the data types):
advp_test_cursor(p_sequence_number, p_year)
but you're calling it as
advp_test_cursor(2018, 92919);
which has the arguments the wrong way round. You either need to flip them:
advp_test_cursor(92919, 2018);
or use named parameter notation:
advp_test_cursor(p_year=>2018, p_sequence_number=>92919)
or indeed combine both:
advp_test_cursor(p_sequence_number=>92919, p_year=>2018)
You do not need to use cursors:
CREATE OR REPLACE Function SISD_OWNER.advp_test_cursor (
p_sequence_number IN adv_course_credit.sequence_number%TYPE,
p_year IN adv_course_credit.year%TYPE
) RETURN adv_course_credit.EXTERNAL_COURSE_CD%TYPE
IS
v_return_var adv_course_credit.EXTERNAL_COURSE_CD%TYPE;
BEGIN
SELECT EXTERNAL_COURSE_CD
INTO v_return_var
FROM adv_course_credit
WHERE year = p_year
AND sequence_number = p_sequence_number
AND closed_ind = 'N';
DBMS_OUTPUT.PUT_LINE('?output = ' || v_return_var);
RETURN v_return_var;
EXCEPTION
WHEN NO_DATA_FOUND THEN
RETURN 'Not working';
END;
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;