Using cursors in loop in PL/SQL - plsql

I have table that contains 3 column.First column name is id , second column's name is parent_id and third one is expression.What i want to do is to search expression column for id.For example I send id value then if parent_id column has a value I want to send parent_id value and want to check expression column has 'E' or not.If It has null value and result has parent_id then I want to send parent_id value and again I want to check expression column has 'E' or not.If expression column has a value like that 'E', I updated variable resultValue as 1 and end loop.
my table A : It should return resultValue =1
id |parent_id|expression
123 |null | null
45 |123 | 'E'
22 |45 | null
my table B : It should return resultValue = 0
id |parent_id|expression
30 |null | null
20 |30 | null
10 |20 | null
my table C : It should return resultValue = 0
id |parent_id|expression
30 |null | null
20 |30 | null
10 |null | null
If first sending id(10) does not contain parent_id(table C) resultValue variable should be 0. If I find 'E' expression any parent row resultValue variable should return 1.
I created a code block with cursor.For the first time I used cursor.I am not sure using cursor with this kind of problem is a good idea or not.My code is running but to open cursor then to close cursor then again opening cursor it is good idea?
DECLARE
resultValue NUMBER := 0;
CURSOR c(v_id NUMBER )
IS
SELECT id_value, id_parent, expression FROM students WHERE id_value = v_id;
PROCEDURE print_overpaid
IS
id_value NUMBER;
id_parent NUMBER;
expression VARCHAR2(20);
BEGIN
LOOP
FETCH c INTO id_value, id_parent, expression;
EXIT
WHEN c%NOTFOUND;
IF id_parent IS NULL AND expression IS NULL THEN
EXIT;
END IF;
IF id_parent IS NOT NULL THEN
CLOSE c;
OPEN c(id_parent);
ELSIF id_parent <> NULL AND expression = 'X' OR id_parent IS NULL AND expression = 'X' THEN
resultValue := 1;
EXIT;
END IF;
END LOOP;
END print_overpaid;
BEGIN
OPEN c(22);
print_overpaid;
DBMS_OUTPUT.PUT_LINE(' My resultValue is : ' || resultValue);
CLOSE c;
END;

If I understood your description correctly, you are looking to see it the specified id of any row in the parentage contains 'E' in the column expression. You are correct that closing and reopening a cursor is not really a good idea. Although I do like your use of a nested procedure. However, it's not really necessary as this can be solved with a single query. The approach will be a recursive CTE that checks the target row for 'E' until a row contains it or the row does not have a parent.
with search_for_e(id, parent_id, e_cnt) as
( select id, parent_id, case when expression = 'E' then 1 else 0 end
from exp_tbl
where id = &id
union all
select t.id,t.parent_id, case when t.expression = 'E' then 1 else 0 end
from search_for_e s
join exp_tbl t on (t.id = s.parent_id)
where t.parent_id is not null
and s.e_cnt = 0
)
select max(e_cnt)
from search_for_e;
See fiddle here, it also contains an anonymous block implementation with nested function and one with cursor.

Related

Assign variable error if 'no rows selected' from query

I want to assign a variable from the sale of today by the menu item input. If the menu item has been sold yet, the variable should return 0. But it showed error instead:
Error report -
SQL Error: ORA-00905: missing keyword
00905. 00000 - "missing keyword"
This is my code:
SELECT SUM(Selling_Price*Quantity_Sold)
INTO V_Today_Sale
FROM Bill_Item BI, Bill B
WHERE BI.Bill_Number = B.Bill_Number AND
Menu_Item_Number = 1 AND
Bill_Date = sysdate AND
(NVL(Paid_YN,'N') = 'Y' OR NVL(Posted_YN,'N') = 'Y')
GROUP BY Menu_Item_Number
ORDER BY Menu_Item_Number;
If I remove the variable assignment
SELECT SUM(Selling_Price*Quantity_Sold)
FROM Bill_Item BI, Bill B
WHERE BI.Bill_Number = B.Bill_Number AND
Menu_Item_Number = 1 AND
Bill_Date = sysdate AND
(NVL(Paid_YN,'N') = 'Y' OR NVL(Posted_YN,'N') = 'Y')
GROUP BY Menu_Item_Number
ORDER BY Menu_Item_Number;
the result will be:
no rows selected
It's correct since no sale has been made for today yet. But how can I make the query return 0 instead of no rows selected so that I can assign it to the variable?
Are you running the query as shown to test without putting it in a block (using PL/SQL or Toad)? If so, you need to define the variable and structure like this, with a call to NVL() around the SUM() call so V_Today_Sale gets a value if both inputs to SUM() are NULL, and add an exception to catch the NO_DATA_FOUND condition that sets V_Today_Sale to 0 if the where clause conditions are not met:
SET SERVEROUTPUT ON;
DECLARE
V_Today_Sale number := 0;
BEGIN
SELECT NVL(SUM(Selling_Price*Quantity_Sold), 0)
INTO V_Today_Sale
FROM Bill_Item BI, Bill B
WHERE BI.Bill_Number = B.Bill_Number AND
Menu_Item_Number = 1 AND
Bill_Date = sysdate AND
(NVL(Paid_YN,'N') = 'Y' OR NVL(Posted_YN,'N') = 'Y')
GROUP BY Menu_Item_Number
ORDER BY Menu_Item_Number;
EXCEPTION
WHEN NO_DATA_FOUND THEN
V_Today_Sale :=0;
END;
DBMS_OUTPUT.PUT_LINE(V_Today_Sale);

Dynamic Column Update PLSQL trigger

I have two tables A and B
In table A there are two columns "Sequence Number" and "Content".
Name Null? Type
------- ----- ------------
SEQ_NO NUMBER(6)
CONTENT VARCHAR2(20)
In table B there are multiple statement columns like "Stmt_1", "Stmt_2", "Stmt_3" etc.
Name Null? Type
------ ----- ------------
STMT_1 VARCHAR2(20)
STMT_2 VARCHAR2(20)
STMT_3 VARCHAR2(20)
STMT_4 VARCHAR2(20)
I want to create a trigger on table A such that after every insert on table A, according to the "Sequence Number" value the corresponding column in table B gets updated.
For example: If table A has "Sequence Number" = 1 , then "Stmt_1" of table B gets updated to the value of "Content" column in table A.
If table A is given as
"SEQ_NO" "CONTENT"
1 "This is Content"
Then Table B should look like:
"STMT_1","STMT_2","STMT_3","STMT_4"
"This is Content","","",""
My approach is as follows:
create or replace trigger TestTrig
after insert on A for each row
begin
declare
temp varchar2(6);
begin
temp := concat("Stmt_",:new.seq_no);
update B
set temp = :new.content;
end;
end;
But I am getting an error in the update statement.
Does anyone know how to approach this problem?
You need to use dynamic SQL (and ' is for string literals, " is for identifiers):
create or replace trigger TestTrig
after insert on A
for each row
DECLARE
temp varchar2(11);
begin
temp := 'Stmt_' || TO_CHAR(:new.seq_no, 'FM999990');
EXECUTE IMMEDIATE 'UPDATE B SET ' || temp || ' = :1' USING :NEW.content;
end;
/
You probably want to handle errors when seq_no is input as 5 and there is no STMT_5 column in table B:
create or replace trigger TestTrig
after insert on A
for each row
DECLARE
temp varchar2(11);
INVALID_IDENTIFIER EXCEPTION;
PRAGMA EXCEPTION_INIT(INVALID_IDENTIFIER, -904);
begin
temp := 'Stmt_' || TO_CHAR(:new.seq_no, 'FM999990');
EXECUTE IMMEDIATE 'UPDATE B SET ' || temp || ' = :1' USING :NEW.content;
EXCEPTION
WHEN INVALID_IDENTIFIER THEN
NULL;
end;
/
However
I would suggest that you do not want a table B or a trigger to update it and you want a VIEW instead:
CREATE VIEW B (stmt_1, stmt2, stmt3, stmt4) AS
SELECT *
FROM A
PIVOT (
MAX(content)
FOR seq_no IN (
1 AS stmt_1,
2 AS stmt_2,
3 AS stmt_3,
4 AS stmt_4
)
);
fiddle;

Update in PL/SQL Block giving PLS-00103 error

I am trying to create a PL/SQL block to update a empty column, is_true, in a new table created_table on the basis of another table, table1. The id fields on the tables match and there are no rows in created_table that doesn't have a corresponding row in table1. I'm basing the update on the status column of table1, but instead of 'Yes' and 'No', I need it reflected as 'Y' and 'N':
created_table: expected results:
id | is_true | other columns id | is_true | other columns
---|---------|-------------- ---|---------|--------------
1 | null | ... 1 | 'Y' | ...
2 | null | ... 2 | 'N' | ...
table1:
id | status | other columns
---|--------|--------------
1 | 'Yes' | ...
2 | 'No' | ...
Since created_table is very large, I'm trying to update it using a PL/SQL procedure, so that in case of failure midway, I'll still have updated rows. The next run of the procedure can then pick up where it previously failed without processing already processed rows.
I've tried testing with this code block:
DECLARE
is_true varchar2 (5) created_table.is_true%type;
BEGIN
FOR status IN (SELECT a.status
from table1 a
left join created_table b
where and a.id=b.id )
LOOP
IF status = 'Yes' THEN
UPDATE created_table SET is_true= 'Y'
ELSE
UPDATE created_table SET is_true= 'N'
WHERE ROWNUM := status.ROWNUM
END IF;
DBMS_OUTPUT.PUT_LINE('Done');
END LOOP;
END;
But it's giving me errors:
PLS-00103: Encountered the symbol "end-of-file" when expecting one of the following:
* & = - + ; < / > at in is mod remainder not rem
What can I do to make it work?
Your code had multiple errors:
DECLARE
-- is_true varchar2 (5) created_table.is_true%type; -- PLS-00103: Encountered the symbol "CREATED_TABLE" when expecting one of the following:
is_true created_table.is_true%type;
BEGIN
FOR status IN (SELECT a.status
from table1 a
-- left join created_table b -- ORA-00905: missing keyword
-- where and a.id=b.id )
left join created_table b on a.id = b.id)
LOOP
-- IF status = 'Yes' THEN -- PLS-00306: wrong number or types of arguments in call to '='
IF status.status = 'Yes' THEN
-- UPDATE created_table SET is_true= 'Y' -- ORA-00933: SQL command not properly ended
UPDATE created_table SET is_true= 'Y';
ELSE
UPDATE created_table SET is_true= 'N'
-- WHERE ROWNUM := status.ROWNUM -- ORA-00920: invalid relational operator and ORA-00933: SQL command not properly ended
WHERE ROWNUM = status.ROWNUM;
END IF;
DBMS_OUTPUT.PUT_LINE('Done');
END LOOP;
END; -- PLS-00103: Encountered the symbol "end-of-file" when expecting one of the following:
/
The PLS-00103 is really just telling you that a / is missing. After that the remaining error is PLS-00302: component 'ROWNUM' must be declared. From "Oracle Database Online Documentation": rownum is a pseudocolumn.
Your SQL would update every row in the table setting is_true to 'Y' on the first encounter of status being 'Yes', since you didn't give it a WHERE clause. I'm assuming that is not your intent.
There was also no COMMIT in your PL/SQL block, so in effect it would have been the same as running a normal SQL.
Based on the situation you've described, I made some changes to the code block. This will provide with a limit for how many rows can be processed prior to a COMMIT. I set the limit to 5. You should change that to something appropriate. It will not pick up any rows that have empty values for is_true, so in effect it will only work on non-processed rows:
DECLARE
commit_counter PLS_INTEGER := 1; -- count commits
commit_limit PLS_INTEGER := 5; -- rows for commit limit
counter PLS_INTEGER := commit_limit;
BEGIN
FOR rec IN (SELECT a.status, a.id
FROM created_table b
JOIN table1 a ON a.id = b.id
WHERE b.is_true IS NULL) -- do not pick up processed rows
LOOP
IF rec.status = 'Yes' THEN
UPDATE created_table SET is_true = 'Y'
WHERE id = rec.id;
ELSE
UPDATE created_table SET is_true = 'N'
WHERE id = rec.id;
END IF;
counter := counter - 1;
IF counter < 1 THEN
counter := commit_limit; --reset counter
commit_counter := commit_counter + 1;
COMMIT;
END IF;
END LOOP;
COMMIT; -- all rows are processed;
DBMS_OUTPUT.PUT_LINE(commit_counter || ' COMMITS');
END;
/
This will update all the rows in one go, still only updating the "empty" rows:
UPDATE created_table b
SET is_true = (SELECT CASE a.status WHEN 'Yes' THEN 'Y'
ELSE 'N'
END
FROM table1 a
WHERE a.id = b.id)
WHERE b.is_true IS NULL;

When using CAST in plsql case, showing error

I want to put single clob data column value in 2 varchar2 columns by checking the length of CLOB column, but i am getting error in case statement, line is marked in * *, it says syntex error , what am i doing wrong
DECLARE
v_tot_rows NUMBER (3);
rqst_xml_1 ISG.CERT_TEST_CASE_GTWY_TXN.RQST_XML_1_TX%TYPE;
rqst_xml_2 ISG.CERT_TEST_CASE_GTWY_TXN.RQST_XML_2_TX%TYPE;
CURSOR req_res_populate_cur
IS
SELECT scptc.SWR_CERT_PRJCT_TEST_CASE_ID,
orb_txn.MIME_HEAD_TX,
orb_txn.RSPNS_XML_TX,
orb_msg.RQST_GNRL_VLD_JSON_TX,
orb_msg.RQST_TEST_CASE_VLD_JSON_TX,
orb_msg.MRCH_ID
(
CASE
WHEN DBMS_LOB.GETLENGTH (orb_txn.RQST_XML_TX) <= 4000 THEN
rqst_xml_1 := CAST ( orb_txn . RQST_XML_TX AS VARCHAR2 ( 4000 ) ) * ,
rqst_xml_2 := ''
WHEN DBMS_LOB.GETLENGTH(orb_txn.RQST_XML_TX)>4000 THEN
rqst_xml_1:=CAST(substr(orb_txn.RQST_XML_TX,1,4000) AS VARCHAR2(4000)),
rqst_xml_2:=CAST(substr(orb_txn.RQST_XML_TX,4001)
END
)
FROM ISG.online_messages msg
JOIN ISG.SWR_CERT_PRJCT_TEST_CASE scptc
ON msg.online_message_id = scptc.TXN_ID,
ISG.GTWY_PLTFM_TXN_MSG orb_msg
JOIN ISG.GTWY_PLTFM_TXN orb_txn
ON orb_msg.GTWY_PLTFM_TXN_ID = orb_txn.GTWY_PLTFM_TXN_ID
WHERE msg.SPEC_ID = 60;;
BEGIN
FOR req_res IN req_res_populate_cur
LOOP
DBMS_OUTPUT.PUT_LINE (req_res.SWR_CERT_PRJCT_TEST_CASE_ID,
req_res.MIME_HEAD_TX,
req_res.rqst_xml_1,
req_res.rqst_xml_2,
req_res.RSPNS_XML_TX,
req_res.RQST_GNRL_VLD_JSON_TX,
req_res.RQST_TEST_CASE_VLD_JSON_TX,
req_res.MRCH_ID);
END LOOP;
END;
Your problem is your invalid SELECT-statement. You're trying to set variables (of your plsql-block) within a query. That's not intended or allowed.
You need to select the values into columns. Here i added two columns. One for each xml-value.
SELECT scptc.SWR_CERT_PRJCT_TEST_CASE_ID,
orb_txn.MIME_HEAD_TX,
orb_txn.RSPNS_XML_TX,
orb_msg.RQST_GNRL_VLD_JSON_TX,
orb_msg.RQST_TEST_CASE_VLD_JSON_TX,
orb_msg.MRCH_ID,
CASE --Column-Start
WHEN DBMS_LOB.GETLENGTH (orb_txn.RQST_XML_TX) <= 4000
THEN
CAST (orb_txn.RQST_XML_TX AS VARCHAR2 (4000))
WHEN DBMS_LOB.GETLENGTH (orb_txn.RQST_XML_TX) > 4000
THEN
CAST (
SUBSTR (orb_txn.RQST_XML_TX, 1, 4000) AS VARCHAR2 (4000))
END
AS my_rqst_xml_1, -- Column-End. In this column you'll have the value for xml_1
CASE --Column-Start
WHEN DBMS_LOB.GETLENGTH (orb_txn.RQST_XML_TX) <= 4000
THEN
''
WHEN DBMS_LOB.GETLENGTH (orb_txn.RQST_XML_TX) > 4000
THEN
CAST (SUBSTR (orb_txn.RQST_XML_TX, 4001) AS VARCHAR2 (4000))
END
AS my_rqst_xml_2 -- Column-End. In this column you'll have the value for xml_12
FROM ISG.online_messages msg
JOIN ISG.SWR_CERT_PRJCT_TEST_CASE scptc
ON msg.online_message_id = scptc.TXN_ID,
ISG.GTWY_PLTFM_TXN_MSG orb_msg
JOIN ISG.GTWY_PLTFM_TXN orb_txn
ON orb_msg.GTWY_PLTFM_TXN_ID = orb_txn.GTWY_PLTFM_TXN_ID
WHERE msg.SPEC_ID = 60
Afterwards you can work with the result and get the values from it.
BEGIN
FOR req_res IN req_res_populate_cur
LOOP
DBMS_OUTPUT.PUT_LINE (req_res.SWR_CERT_PRJCT_TEST_CASE_ID,
req_res.MIME_HEAD_TX,
req_res.my_rqst_xml_1, -- here we can see the values
req_res.my_rqst_xml_2, -- here too
req_res.RSPNS_XML_TX,
req_res.RQST_GNRL_VLD_JSON_TX,
req_res.RQST_TEST_CASE_VLD_JSON_TX,
req_res.MRCH_ID);
-- And here we could store the values into variables or call some procedures etc.
rqst_xml_1 := req_res.my_rqst_xml_1;
rqst_xml_2 := req_res.my_rqst_xml_2;
END LOOP;
END;
I've to guess, but it seems you didn't want to declare the variables:
rqst_xml_1 ISG.CERT_TEST_CASE_GTWY_TXN.RQST_XML_1_TX%TYPE;
rqst_xml_2 ISG.CERT_TEST_CASE_GTWY_TXN.RQST_XML_2_TX%TYPE;
This would be only needed if you want to work with the values.

Parsing large text in xml format in Pl/Sql

I have a log table and the table has a varchar2 field which holds xml string like below:
In this example ClientName attribute did not change but Clientsurname changed.
I want to capture changed columns and their previous and new values.
The log table contains millions of records.
Which method can you suggest for parsing this data in an efficient way?
<r>
<columntag nameattribute="ClientName">
<new_value>Jeffrey</new_value>
<previous_value>Jeffrey</previous_value>
</columntag>
<columntag nameattribute="ClientSurname">
<new_value>Dijk</new_value>
<previous_value>Disk</previous_value>
</columntag>
</r>
Thank you
not 100% sure the below is what you are after but it should give you some ideas about how to go about it. Hope it is helpfull
CREATE TABLE "RM4SERV"."LOG_TEST" ( "TESTLOG" VARCHAR2(4000 BYTE))
Insert into RM4SERV.LOG_TEST (TESTLOG) values ('<r><columntag nameattribute="ClientName"><new_value>Jeffery</new_value><previous_value>Jeffery</previous_value> </columntag><columntag nameattribute="ClientSurname"><new_value>Dijk</new_value><previous_value>Disk</previous_value></columntag></r>');
Insert into RM4SERV.LOG_TEST (TESTLOG) values ('<r><columntag nameattribute="ClientName"><new_value>Jeffery</new_value><previous_value>Jeffery</previous_value> </columntag><columntag nameattribute="ClientSurname"><new_value>Disk</new_value><previous_value>Disk</previous_value></columntag></r>');
Insert into RM4SERV.LOG_TEST (TESTLOG) values ('<r><columntag nameattribute="ClientName"><new_value>Jeffery</new_value><previous_value>Jim</previous_value> </columntag><columntag nameattribute="ClientSurname"><new_value>Dijks</new_value><previous_value>Diskett</previous_value></columntag></r>');
declare
v_logrec varchar2(4000) := null;
v_recnum number := 0;
cursor c_logs is
select testlog from log_test;
cursor c_records is
select extractValue(x.column_value, '/columntag/#nameattribute') as column_name,
extractValue(x.column_value, '/columntag/new_value') as new_value,
extractValue(x.column_value, '/columntag/previous_value') as previous_value
from TABLE(XMLSequence(extract(xmltype.createxml(v_logrec), '//columntag'))) x
where extractValue(x.column_value, '/columntag/new_value') != extractValue(x.column_value, '/columntag/previous_value');
begin
for v_log in c_logs loop
v_logrec := v_log.testlog;
v_recnum := v_recnum + 1;
dbms_output.put_line(v_recnum);
for v_rec in c_records loop
SYS.dbms_output.put_line(v_rec.column_name || ' : *' || v_rec.new_value || '* : *' || v_rec.previous_value || '*');
end loop;
end loop;
end;
This would give you the below output (so Surname different in first record, nothing different in the second and both different in the third)...
1
ClientSurname : Dijk : Disk
2
3
ClientName : Jeffery : Jim
ClientSurname : Dijks : Diskett

Resources