Pass table as parameter in PL/SQL stored procedure - sql

Please excuse any poorly phrased questions as I have very little experience with PL/SQL and Oracle. However, I am currently unaware of how to pass a table as a parameter. Currently, this procedure outputs the one entry that matches the designated inputs. For instance, if trade product = a, trade region = c, counter party region = b, trade desk = d, trade legal entity = e, hedge product = f and hedge legal entity = g, then it returns this unique combination. However, I'd like to have the ability to assign multiple values to each parameter and return all the results. For instance, based on the previous example, if I designate that hedge legal entity = g or h, then i'd like to see 2 entries -"a,c,b,d,e,f,g" AND "a,c,b,d,e,f,h". I believe this is done by changing my parameters types to tables of varchar2s instead of a single varchar2. Any insight is greatly appreciated.
CREATE OR REPLACE PROCEDURE SOX_SCOPING.BM_ENTIRE_TABLE_ARRAY_PARAMS (
trade_product in varchar2
, trade_region in varchar2
, counterparty_region in varchar2
, trade_desk in varchar2
, trade_legal_entity in varchar2
, hedge_product in varchar2
, hedge_legal_entity in varchar2
, out_cursor out sys_refcursor) IS
BEGIN
open out_cursor for
SELECT D.PRODUCTS AS TRADE_PRODUCTS, D.TRADER_REGION, D.REGION, D.DESK AS TRADE_DESK, D.LEGAL_ENTITY AS TRADE_LEGAL_ENTITY,F.PRODUCTS AS HEDGE_PRODUCTS, F.LEGAL_ENTITY AS HEDGE_LEGAL_ENTITY
FROM
(SELECT C.*, BM_LEGAL_ENTITY.LEGAL_ENTITY
FROM
(SELECT B.*, BM_DESK.DESK
FROM
(SELECT *
FROM
(SELECT BM_PRODUCTS.*,BM_TRADER_REGION.REGION AS TRADER_REGION
FROM SOX_SCOPING.BM_PRODUCTS
CROSS JOIN SOX_SCOPING.BM_TRADER_REGION)
CROSS JOIN SOX_SCOPING.BM_COUNTERPARTY_REGION) B
LEFT OUTER JOIN SOX_SCOPING.BM_DESK
ON B.PRODUCTS = BM_DESK.DESK_TO_PRODUCT) C
LEFT OUTER JOIN BM_LEGAL_ENTITY
ON C.DESK = BM_LEGAL_ENTITY.LEGAL_ENTITY_TO_DESK) D
CROSS JOIN (
SELECT E.*,BM_LEGAL_ENTITY.LEGAL_ENTITY
FROM
(SELECT BM_PRODUCTS.*,BM_DESK.DESK
FROM SOX_SCOPING.BM_PRODUCTS
LEFT OUTER JOIN SOX_SCOPING.BM_DESK
ON BM_PRODUCTS.PRODUCTS = BM_DESK.DESK_TO_PRODUCT) E
LEFT OUTER JOIN BM_LEGAL_ENTITY
ON E.DESK = BM_LEGAL_ENTITY.LEGAL_ENTITY_TO_DESK) F
where
d.products in (trade_product)
and D.TRADER_REGION in (trade_region)
and D.REGION in (counterparty_region)
and D.DESK in (trade_desk)
and D.LEGAL_ENTITY in (trade_legal_entity)
and F.PRODUCTS in (hedge_product)
and F.LEGAL_ENTITY in (hedge_legal_entity)
ORDER BY TRADE_PRODUCTS, TRADER_REGION,REGION,TRADE_DESK,HEDGE_PRODUCTS,HEDGE_LEGAL_ENTITY;
EXCEPTION
WHEN NO_DATA_FOUND THEN
NULL;
WHEN OTHERS THEN
-- Consider logging the error and then re-raise
RAISE;
END BM_ENTIRE_TABLE_ARRAY_PARAMS;
/

This is the way I would write it if I had to create a procedure with a variable for the table name and an OUT parameter as SYS_REFCURSOR. Hopefully you can adapt it to your code.
CREATE OR replace PROCEDURE PROC_ONE(vstablename VARCHAR2,
c1 OUT SYS_REFCURSOR)
IS
sqlstmt VARCHAR2(255);
BEGIN
sqlstmt := 'select * from '
||vstablename;
OPEN c1 FOR sqlstmt;
END;

Related

ORA-00957: duplicate column name while executing the procedure

I am getting an error
ORA-00957: duplicate column name error while trying to create procedure
What is the problem ? How can I fix my query?
The procedure was created successfully. But when I try to execute the procedure, I got
ORA-00957: duplicate column name ORA-06512: at "SQL_BOTTZCVHAFEXQPWFMKGLKIGTC.SP_CRNA", line 91
When I execute each command in the query one-by-one, it executes properly. But executing it from the procedure call is not working.
CREATE OR replace PROCEDURE sp_crna
AS
v_query1 CLOB;
BEGIN
BEGIN
v_query1:='DROP TABLE TOP20 PURGE';
EXECUTE IMMEDIATE v_query1;
EXCEPTION
WHEN OTHERS THEN
NULL;
END;
BEGIN
v_query1:=
'CREATE TABLE top20 AS
SELECT ROWNUM AS rn,
country
|| '' ''
||total_cases AS top20
FROM (
SELECT *
FROM corona_project
ORDER BY total_cases DESC)
WHERE ROWNUM<21';
EXECUTE IMMEDIATE v_query1;
END;
BEGIN
v_query1:='DROP TABLE BOTTOM20 PURGE';
EXECUTE IMMEDIATE v_query1;
EXCEPTION
WHEN OTHERS THEN
NULL;
END;
BEGIN
v_query1:=
'CREATE TABLE bottom20 AS
SELECT ROWNUM AS rn,
country
|| '' ''
||total_cases AS BOTTOM20
FROM (SELECT *
FROM corona_project
ORDER BY total_cases)
WHERE ROWNUM < 21';
EXECUTE IMMEDIATE v_query1;
END;
BEGIN
v_query1:='DROP TABLE MIDDLE_ROW PURGE';
EXECUTE IMMEDIATE v_query1;
EXCEPTION
WHEN OTHERS THEN
NULL;
END;
BEGIN
v_query1:=
'CREATE TABLE MIDDLE_ROW AS
SELECT ROWNUM AS rn,
SUM(total_cases) AS total_cases,
SUM(deaths) deaths,
SUM(recovered) recovered,
SUM(new_cases) new_cases,
SUM(new_deaths) new_deaths,
SUM(seriouscritical) seriouscritical
FROM corona_project
GROUP BY ROWNUM,
total_cases,
deaths,
recovered,
new_cases,
new_deaths,
seriouscritical';
EXECUTE IMMEDIATE v_query1;
END ;
BEGIN
v_query1:='DROP TABLE DISPLAY PURGE';
EXECUTE IMMEDIATE v_query1;
EXCEPTION
WHEN OTHERS THEN
NULL;
END;
BEGIN
v_query1:=
'CREATE TABLE display AS
SELECT T.*,
M.*,
B.*
FROM top20 T
left join bottom20 B
ON T.rn = B.rn
left join middle_row M
ON M.rn = T.rn';
EXECUTE IMMEDIATE v_query1;
END;
END sp_crna;
/
EXEC sp_crna;
From my point of view, the whole procedure is wrong. In Oracle, we really, Really, REALLY rarely create tables dynamically. We do create them once (even if they are (global or private) temporary tables) and use them many times.
What you should do, in my opinion, is to create tables at SQL level and - using the stored procedure (if you want it) INSERT rows into those pre-created tables. When you don't need that data any more, TRUNCATE tables or DELETE their contents. According to what you posted, you'd delete tables at the beginning of that procedure.
Something like this:
create table top20
(rn number,
top20 varchar2(100));
create table bottom20
(rn number,
bottom20 varchar2(100));
create table middle_row
(rn number,
total_cases number,
deaths number,
...);
Now, create the procedure which will insert rows:
create or replace procedure p_rows as
begin
delete from top20;
delete from bottom20;
delete from middle_row;
insert into top20 (rn, top20)
select ...;
...
end;
/
Run it whenever you want:
begin
p_rows;
end;
/
Instead of the display table, use a view:
create or replace view display as
select t.rn,
t.top20,
b.bottom20,
m.total_cases,
m.deaths,
m....
from top20 t left join bottom20 b on b.rn = t.rn
left join middle_row m on m.rn = t.rn;
Though, I'm not sure what is the rn column supposed to do in this context. ROWNUM maybe isn't the best column to be used for joins, so ... think about it (and its replacement).
As of error you got, this piece of code is wrong (in your procedure):
CREATE TABLE display AS
SELECT T.*,
M.*,
B.*
FROM top20 T
left join bottom20 B
ON T.rn = B.rn
left join middle_row M
ON M.rn = T.rn
Why? Because those table share the same column (rn), and you can't have two (or more) columns with the same name in the same table. It means that you can't (and shouldn't) use asterisk, but name all columns - one-by-one - providing column aliases where necessary, e.g.
CREATE TABLE display AS
SELECT T.rn top_rn, t.top20,
M.rn middle_rn, m.total_cases, m.deaths, m...,
b.rn bottom_rn, b.bottom20
FROM top20 T
left join bottom20 B
ON T.rn = B.rn
left join middle_row M
ON M.rn = T.rn

Oracle Trigger PLS-00103. Query with multiple rows in temporal variable

I'm trying to write a trigger that raises error when someone attempts to rate a product which has not been bought. I have come up with a query to get the purchase history of a client :
SELECT nref
FROM CartClient a
INNER JOIN PaidCart b
ON a.idpurchase = b.idpurchase
INNER JOIN CartDetails c
ON b.idpurchase = c.idpurchase
WHERE a.Id = '12345672X'
which works fine.
So the next trigger should check if the product of a new rating (:new.NRef) has not been bought, namely is not part of the result of the last query (NOT IN).
CREATE OR REPLACE TRIGGER cant_rate
BEFORE INSERT ON Rating
FOR EACH ROW
BEGIN
IF (:new.NRef NOT IN (SELECT nref FROM CartClient a
INNER JOIN PaidCart b
ON a.idpurchase = b.idpurchase
INNER JOIN CartDetails c
ON b.idpurchase = c.idpurchase
WHERE a.Id =:new.Id)) THEN
RAISE_APPLICATION_ERROR(-20603,'Cant rate a not bought product');
END IF;
END;
I get error:
"PLS-00103:Encountered the symbol “INNER” when expecting one of".
I have tried to store the result of the query in a temporal variable using SELECT INTO. But, it's a multiple row result. What could I do?
How about such an approach?
CREATE OR REPLACE TRIGGER cant_rate
BEFORE INSERT ON Rating
FOR EACH ROW
DECLARE
l_exists NUMBER(1) := 0;
BEGIN
SELECT MAX(1)
INTO l_exists
FROM dual
WHERE EXISTS (SELECT nref FROM CartClient A
INNER JOIN PaidCart b
ON A.idpurchase = b.idpurchase
INNER JOIN CartDetails C
ON b.idpurchase = C.idpurchase
WHERE A.ID = :NEW.ID
AND a.nref = :NEW.nref --> is it "a.nref"?
);
IF l_exists = 0 THEN
RAISE_APPLICATION_ERROR(-20603,'Cant rate a not bought product');
END IF;
END;
Note remark "is it a.nref?" - you never said which table owns that nref column so I presumed it is cartclient; modify it, if necessary.
As of your attempt: if you executed it in SQL*Plus or SQL Developer, you'd see a message regarding subquery in IF; something like this:
LINE/COL ERROR
--------- -------------------------------------------------------------
2/3 PL/SQL: Statement ignored
2/21 PLS-00405: subquery not allowed in this context
So, no - you can't do it that way.
You probably don't get the actual error: PLS-00405 subquery not allowed in this context.
Simple stupid test:
BEGIN
IF 1 NOT IN ( SELECT 2 FROM DUAL )
THEN
NULL;
END IF;
END;
/
You can adjust your trigger in this way or similar:
CREATE OR REPLACE TRIGGER cant_rate
BEFORE INSERT ON Rating
FOR EACH ROW
DECLARE
l_test PLS_INTEGER;
BEGIN
SELECT CASE
WHEN :new.NRef NOT IN ( SELECT nref
FROM CartClient a
INNER JOIN PaidCart b
ON a.idpurchase = b.idpurchase
INNER JOIN CartDetails c
ON b.idpurchase = c.idpurchase
WHERE a.Id = :new.Id
)
THEN 1
ELSE 0
END
INTO l_test;
FROM DUAL;
IF l_test = 1
THEN
...
END IF;
END;
/

Error when creating procedure with existing query

I have a query that I am trying to use within a procedure. The query works exactly as it should but once I place it inside of the BEGIN and END of the procedure, it tells me "name is already used by an existing object". Not sure what this means, any help would be great.
Here is the orignal query:
SELECT
course.course_no,
course.description,
section.section_no,
enrcount.countofenrollment
FROM
course
INNER JOIN section ON course.course_no = section.course_no
INNER JOIN (
SELECT
section_id,
COUNT(*) countofenrollment
FROM
enrollment
GROUP BY
section_id
) enrcount ON section.section_id = enrcount.section_id
WHERE
enrcount.countofenrollment < 6;
Here is the procedure:
CREATE PROCEDURE PRC_Enrollment
AS
BEGIN
SELECT
course.course_no,
course.description,
section.section_no,
enrcount.countofenrollment
FROM
course
INNER JOIN section ON course.course_no = section.course_no
INNER JOIN (
SELECT
section_id,
COUNT(*) countofenrollment
FROM
enrollment
GROUP BY
section_id
) enrcount ON section.section_id = enrcount.section_id
WHERE
enrcount.countofenrollment < 6;
END;
Use create or replace procedure.. instead of just create procedure.. in order to resolve the error youve mentioned above. That means already the procedure name has been used.
First of all INTO clause should be added to return the values of the columns
CREATE OR REPLACE PROCEDURE PRC_Enrollment AS
v_course_no course.course_no%type;
v_description course.description%type;
v_section_no section.section_no%type;
v_count_enrl int;
BEGIN
SELECT c.course_no, c.description, s.section_no, e.countofenrollment
INTO v_course_no, v_description, v_section_no , v_count_enrl
FROM course c
JOIN section s
ON c.course_no = s.course_no
JOIN (SELECT section_id, COUNT(*) countofenrollment
FROM enrollment
GROUP BY section_id) e
ON s.section_id = e.section_id
WHERE e.countofenrollment < 6;
EXCEPTION WHEN NO_DATA_FOUND THEN NULL;
END;
/
Alternatively you can return those values as out parameters by using
CREATE OR REPLACE PROCEDURE PRC_Enrollment(
v_course_no out course.course_no%type,
v_description out course.description%type,
v_section_no out section.section_no%type,
v_count_enrl out int
) AS
instead of returning them as local variables as in the first case.
alias your tables with a letter, usually preferred the first letter
of the tables
add exception handling, at least against the case no data found in
order not to get error
Btw, you get such an error, if OR REPLACE has not been added after CREATE keyword for the second and subsequent compilations.

How can I insert into a nested table from the resultset of a select statement?

I have two tables with nested tables of the same type, the type is:
CREATE OR REPLACE TYPE tipo_valor AS OBJECT (
ano DATE, --year
cantidad INTEGER --ammount of exported wine
) ;
CREATE OR REPLACE TYPE hist_export AS OBJECT (
nombre_pais VARCHAR2(100), --name of importer country
cantidad tipo_valor --type referenced above
);
the nested table:
CREATE OR REPLACE TYPE nt_hist_exp IS
TABLE OF hist_export;
And my two tables are:
CREATE TABLE bodega ( --winery
id_bod INTEGER NOT NULL,
exp_an_bod nt_hist_exp ,
)
CREATE TABLE marca ( --wine
id_marca INTEGER NOT NULL,
exp_an_marca nt_hist_exp
)
I have procedure with a select statement that collects the export ammounts from the wine table on a certain year and orders it by country,
PROCEDURE exp_bod ( p_ano DATE,
p_bod_nom VARCHAR2)IS
sumatoria INTEGER;
p_idbod INTEGER;
BEGIN
SELECT id_bod INTO p_idbod
FROM bodega
WHERE nombre_bod = p_bod_nom;
DBMS_OUTPUT.PUT_LINE(to_char(p_idbod));
SELECT nt.nombre_pais,sum(nt.cantidad.cantidad)
INTO sumatoria
FROM bodega b
JOIN presentacion p on p.bodega_fk = b.id_bod
JOIN marca m on m.id_marca = p.marca_fk
CROSS JOIN TABLE(m.exp_an_marca) nt
WHERE b.id_bod = p_idbod
AND nt.cantidad.ano = p_ano
group by nt.nombre_pais
order by nt.nombre_pais;
);
end exp_bod;
the second select in this procedure successfully returns what I need which is a resultset with two columns,one with the country names and the second one with the export ammounts all summed up and ordered, what I want is to insert the rows from that resultset into the nested table in the winery table including the year which is received as an argument by the function
You could use insert as select, creating an instance of your object type as part of the query:
INSERT INTO TABLE (SELECT exp_an_bod FROM bodega b WHERE b.nombre_bod = p_bod_nom)
SELECT hist_export(nt.nombre_pais, tipo_valor(nt.cantidad.ano, sum(nt.cantidad.cantidad)))
FROM bodega b
JOIN presentacion p on p.bodega_fk = b.id_bod
JOIN marca m on m.id_marca = p.marca_fk
CROSS JOIN TABLE(m.exp_an_marca) nt
WHERE b.nombre_bod = p_bod_nom
AND nt.cantidad.ano = p_ano
GROUP BY nt.nombre_pais, nt.cantidad.ano;
I'm assuming nombre_bod is a column on bodega, though you haven't shown that in the table definition, which means you don't really need a separate look-up for that.
This also assumes that exp_an_bod is not null; it can be empty though. It also doesn't make any allowance for an existing row for the country, but it's not very clear from your data model whether than can exist or what should happen if it does. You can update en existing entry using the same mechanism though, as long as you can identify it.
You can do it in PL/SQL like this:
declare
hist_exp nt_hist_exp;
begin
select exp_an_bod
into hist_exp
from bodega
where id_bod = 123;
hist_exp.extend;
hist_exp(hist_exp.LAST) := hist_export('xyz', 456);
update bodega
set exp_an_bod = hist_exp
where id_bod = 123;
end;
If you like to UPDATE rather then INSERT you can also use
UPDATE (select nombre_pais, cantida, id_bod FROM bodega CROSS JOIN TABLE(exp_an_bod))
SET nombre_pais = 'abc'
WHERE id_bod = 123
and cantida = 456;
You may also try
INSERT INTO (select nombre_pais, cantida, id_bod FROM bodega CROSS JOIN TABLE(exp_an_bod)) ...
but I don't think this is possible - I never tried.

pl sql function select into variable in loop on a returning table

I have a type object as below
Emptable type is empname,empid,rank
Then I have a Plsql function as below and this errors out. I need to run a sql select statement against the returned list of empids and load it the returned list. and below code keeps erroring..
create or replace function emp_details return emptable
is
l_result_col emptable := emptable();
n integer := 0;
rxvalue number;
begin
for r in (select empname,empid from table)
loop
l_result_col.extend;
n := n + 1;
(select sum(xyz) into rxvalue from A inner join B on A.x=B.x and A.id=r.empid);
l_result_col(n) := t_col(r.empname, r.empid,rxvalue);
end loop;
return l_result_col;
end;
/
Any Help is appreciated..
Thank you!
Why not do it straightforward without involving PL/SQL code?
select t_col(r.empname, r.empid, sum(xyz))
bulk collect into l_result_col
from table r
left join (A join B on A.x = B.x) on A.id = r.empid
group by r.empname, r.empid;
I think your select shouldn't be in parentheses. In this context it is not a subselect but a separate PL/SQL statement.