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

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;
/

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

Select query, insert into table, and return initial query in postgres

I have a rather complex plpgsql stored procedure and I need to select from multiple tables and insert as well.
This is part of what I currently have.
BEGIN
RETURN query
SELECT domains.id, webpages.id as page_id ...
FROM domains
LEFT JOIN domain_settings
ON domain_settings.domain_id = domains.id
RIGHT JOIN webpages
ON webpages.domain_id = domains.id
LEFT JOIN subscriptions
ON webpages.id = subscriptions.page_id
AND subscriptions.user_id = query_user_id
AND subscriptions.comment_id IS NULL
WHERE domains.domain_address = query_domain_url
IF NOT FOUND THEN ...
END;
$$ language plpgsql;
Now, I would like add an insert query into another table using certain values from the return query before the 'if not found then' statement:
INSERT INTO page_visits (domain_id, page_id)
SELECT id, page_id FROM ?? (return query statement)
And after the insert, I want to return the initial return query values. How do I go about doing this? I tried using WITH AS statements, but I can't seem to get it to work
A set-returning PL/pgSQL function builds the return stack while processing the function body. There is no way to access that return stack from within the same function. You could nest the function. Or use a temporary table.
But using a CTE is probably the simplest way for the cas at hand. Going out on a limb, you may be looking for something like this:
CREATE OR REPLACE FUNCTION demo(query_user_id int, query_domain_url text)
RETURNS TABLE (c1 int, c2 int)
LANGUAGE plpgsql AS
$func$
BEGIN
RETURN QUERY
WITH sel AS (
SELECT d.id, w.id as page_id ...
FROM webpages w
JOIN domains d ON d.id = w.domain_id
LEFT JOIN domain_settings ds ON ds.domain_id = d.id
LEFT JOIN subscriptions s ON s.page_id = w.id
AND s.user_id = query_user_id -- origin?
AND s.comment_id IS NULL
WHERE d.domain_address = query_domain_url -- origin?
)
, ins AS (
INSERT INTO tbl (col1, col2)
SELECT main.id, sel.page_id
FROM (SELECT 'foo') AS main(id)
LEFT JOIN sel USING (id) -- LEFT JOIN ?
)
TABLE sel;
IF NOT FOUND THEN
-- do something
END IF;
END
$func$;
Remember, if the transaction does not commit successfully, the INSERT is also rolled back.
The final TABLE sel is just short syntax for SELECT * FROM sel. See:
Is there a shortcut for SELECT * FROM?

Error to postgresql

Pls Help!
count_rate :=
(
SELECT COUNT(trate.rid) AS count_rate
FROM tlot LEFT JOIN trate ON trate.ridlot = tlot.lid
GROUP BY tlot.lid
);
FULL:
CREATE FUNCTION editstatuswait()
RETURNS void
LANGUAGE plpgsql
AS $$
DECLARE count_rate INTEGER;
BEGIN
count_rate := (SELECT COUNT(trate.rid) AS count_rate FROM tlot LEFT JOIN trate ON trate.ridlot = tlot.lid GROUP BY tlot.lid);
IF (count_rate != 0) THEN
UPDATE tlot SET lstatus = 3
WHERE tlot.lexpirationdate < NOW()
AND tlot.lexpirationdate > NOW()-INTERVAL '24 hours' AND tlot.lstatus = 2;
ELSE
UPDATE tlot SET lstatus = 0
WHERE tlot.lexpirationdate < NOW()
AND tlot.lexpirationdate > NOW()-INTERVAL '24 hours' AND tlot.lstatus = 2;
END IF;
END;
$$;
ERROR: [21000] ERROR: more than one row returned by a subquery used as an expression Где: SQL statement
SELECT (SELECT COUNT(trate.rid) AS count_rate FROM tlot LEFT JOIN trate ON trate.ridlot = tlot.lid GROUP BY tlot.lid
I can not understand how to get rid of this error...
The solution will depend on what you are trying to achieve.
A variable can only contain a single value, so your attempt to store the result of a subselect that returns more than one row in count_rate is bound to fail.
You will have to come up with a subselect that returns at most one row (if it returns no row, NULL will be assigned to the variable).
If you are only interested in the first row (unlikely, since there is no ORDER BY), you could append LIMIT 1 to the query.
If you want only the count for a certain tlot.lid, you should use WHERE tlot.lid = ... instead of a GROUP BY.
If you want to process multiple results, you would use a construction like:
FOR count_rate IN SELECT ... LOOP
...
END LOOP;
Remove the GROUP BY:
count_rate := (SELECT COUNT(trate.rid) AS count_rate FROM tlot LEFT JOIN trate ON trate.ridlot = tlot.lid);
Of course, this may not do what you intend. It will at least fix the error.

Pass table as parameter in PL/SQL stored procedure

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;

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.