Update in PL/SQL Block giving PLS-00103 error - plsql

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;

Related

DBMS_OUTPUT to a table with timestamp

I am deleting a massive table and want to delete the table in batches. I am deleting records older than 467 days. I want to insert the dbms_output status in the following procedure to be written to a table that has two columns such as the number of records to be deleted with timestamp columns:
CREATE OR REPLACE PROCEDURE delete_tab (tablename IN VARCHAR2, nrows IN NUMBER ) IS
sSQL1 VARCHAR2(2000);
sSQL2 VARCHAR2(2000);
nCount NUMBER;
BEGIN
DBMS_OUTPUT.enable (100000);
nCount := 0;
sSQL1:='delete from '|| tablename ||
' where ROWNUM < ' || nrows || ' and where cast(time_stamp as date) < sysdate - 467';
sSQL2:='select count(ROWID) from ' || tablename ||
' where cast(time_stamp as date) < sysdate - 467';
EXECUTE IMMEDIATE sSQL2 INTO nCount;
LOOP
EXECUTE IMMEDIATE sSQL1;
nCount := nCount-nrows;
DBMS_OUTPUT.PUT_LINE('Existing records to be deleted: ' || to_char(nCount));
commit;
EXIT WHEN nCount = 0;
END LOOP;
END delete_tab;
/
Let me know how I can add an insert statement within the block to write the progress.
Besides the missing Insert there are a couple other minor issues and a major issue with this code.
ssql1:='delete from '|| tablename ||
' where ROWNUM < ' || nrows || ' and where cast(time_stamp as date) < sysdate - 467';
If the above were valid then when executed it nrows-1 (not nrows as I suspect you are thinking) every time as long as that many rows still exist. However, it is not valid; the where clause is invalid. The proper format for the where clause is
"Where <condition> and <condition> ..."; do not repeat where.
ssql2:='select count(ROWID) from ' || tablename ||
' where cast(time_stamp as date) < sysdate - 467';
There is no reason to count the rowids, every row has exactly 1. ROWID is pseudo column telling Oracle basically where on the disk the row is located. You have probably heard that it is the fastest way to retrieve a specific - that is true - but it is not true selecting rowid itself is faster, it will if anything, be slower. It would require a full table scan and calculation of its value for every row in the table. The optimizer may notice what is happening and change this to select count() but why hope for that just start with count().
Now we get to the worst issue. Unless the total number of rows to delete is an exact multiple of the parameter nrows then the procedure becomes a never ending loop until it throws the exception ORA-22054: underflow error.
This is because result as the only exit condition is "exit when ncount = 0". Suppose you have 1002 rows to delete and nrows is 1000. The first iteration deletes 999 then reduces ncount to 2 (1002-1000). Then the second iteration deletes another 999 and reduces ncount to -998 (2-1000). The procedure continues iterating until eventually ncount gets so small it cannot be held any longer (something like -1*10^39 - 1).
You could change the exit condition to "exit when nCount < 1". But even that is not necessary, no need for the code to calculate the exit condition at all. Let Oracle tell it. The delete statement returns SQL%rowcount that contains the number of rows processed by the last DML statement. If no are processed it returns 0. So the exist condition becomes a simple "exit when sql%rowcount = 0". Lesson: always be careful with equal 0 conditions.
Taking all this into account the procedure becomes. (Also changed variable names as I do not like ssql1, ssql2 and reusing a variable for 2 things. When using such I get confused as to which is which, esp on large procedures.):
create or replace procedure delete_tab
( tablename in varchar2
, nrows in number
)
is
delete_sql varchar2(2000);
count_sql varchar2(2000);
deleted_count number;
expect_delete number;
begin
dbms_output.enable (100000);
count_sql := 'select count(*) from ' || tablename ||
' where time_stamp < current_timestamp - 467';
dbms_output.put_line('COUNT_SQL: ' || count_sql
);
execute immediate count_sql into expect_delete;
dbms_output.put_line('Existing records to be deleted: ' || to_char(expect_delete));
delete_sql := 'delete from '|| tablename ||
' where rownum <= ' || nrows ||
' and cast(time_stamp as date) < sysdate - 467';
dbms_output.put_line('DELETE_SQL: '
|| delete_sql
);
deleted_count := 0;
loop
execute immediate delete_sql;
exit when sql%rowcount = 0;
deleted_count := deleted_count+sql%rowcount;
commit;
end loop;
insert into delete_log( table_name, date_deleted, rows_deleted)
values (tablename, sysdate, deleted_count) ;
commit; -- commit the deletes in final loop and insert
end delete_tab;
See demo (includes example of danger with ncount = 0 and procedure has additional trace information). BTW in a production run my commit interval (nRows) would something like 500000 or larger.

Using cursors in loop in PL/SQL

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.

PLSQL STORED PROCEDURE does not give result

select count(*)
INTO countExceed
from uid_emp_master k
where k.unique_id in (select k.reviewer_uid
from uid_rm_hierarchy k
where k.unique_id in ('||p_ID_list||'))
and k.band IN( 'A','B','C','D');
if (countExceed > 0) then
quer :='UPDATE UID_RM_HIERARCHY I
SET I.REVIEWER_UID in (SELECT L.REVIEWER_UID
FROM UID_RM_HIERARCHY L
WHERE L.UNIQUE_ID in ('||p_ID_list||') )
WHERE I.REVIEWER_UID in('||p_ID_list||')
and isdeleted=0';
EXECUTE IMMEDIATE quer ;
END IF;
the above stored procedure does not show any result the variable countExceed declared as a number please help me to correct the query.
The issue is in
where k.unique_id in ('||p_ID_list||'))
Here you are saying to look for records
where unique_id = '||p_ID_list||'
exactly as its typed, but what you need is to handle that variable as a list of values.
Say you have a table like this
create table tabTest(id) as (
select 'id1' from dual union all
select 'id2' from dual union all
select 'id3' from dual union all
select 'id4' from dual
)
and your input string is 'id1,id3,1d8';
I see two ways to do what you need; one is with dynamic SQL, for example:
declare
vResult number;
vList varchar2(199) := 'id1,id3,1d8';
vSQL varchar2(100);
begin
vSQL :=
'select count(*)
from tabTest
where id in (''' || replace (vList, ',', ''', ''') || ''')';
--
execute immediate vSQL into vResult;
--
dbms_output.put_line('Result: ' || vResult);
end;
Another way could be by splitting the string into a list of values and then simply using the resulting list in the IN.
For that, there are many answers about how to split a comma separated list string in Oracle.

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.

how to retrieve data from multiple colums from bulk collect

DECLARE
TYPE two_cols IS RECORD
(
family_id family_members.family_id %TYPE,
city family_members.city%TYPE
);
TYPE family_members_t IS TABLE OF two_cols;
l_family_members family_members_t;
BEGIN
SELECT family_id,city
BULK COLLECT INTO l_family_members
FROM (SELECT x.family_id, x.City, x.Member_count,row_number()
OVER (PARTITION BY x.family_id ORDER BY x.Member_count DESC) rn
FROM (SELECT family_id, City, COUNT(*) Member_count
FROM FAMILY_MEMBERS
GROUP BY family_id, City) x) y
WHERE y.rn = 1;
for rec in 1..l_family_members.count
loop
dbms_output.put_line('majority mem of family id'
|| l_family_members.family_id(rec)
|| 'stay in '||l_family_members.city(rec));
end loop;
END;
Error:
ORA-06550: line 23, column 69: PLS-00302: component 'FAMILY_ID' must
be declared ORA-06550: line 23, column 1: PL/SQL: Statement ignored
06550. 00000 - "line %s, column %s:\n%s"
*Cause: Usually a PL/SQL compilation error.
*Action:
I am confused at the output line.. I am not getting how to retrieve data from bulk collect as there are two columns in it..how to distinguish them and retrieve them?
you are trying to select 2 columns into 1 record which doesn't work.
depending on your database version, you may be able to select records which then get bulk collected into a table as follows
DECLARE
TYPE two_cols IS RECORD
(
family_id family_members.family_id %TYPE,
city family_members.city%TYPE
);
TYPE family_members_t IS TABLE OF two_cols;
l_family_members family_members_t;
BEGIN
SELECT two_cols(family_id,city )
BULK COLLECT INTO l_family_members
FROM (SELECT x.family_id, x.City, x.Member_count,row_number()
OVER (PARTITION BY x.family_id ORDER BY x.Member_count DESC) rn
FROM (SELECT family_id, City, COUNT(*) Member_count
FROM FAMILY_MEMBERS
GROUP BY family_id, City) x) y
WHERE y.rn = 1;
for rec in 1..l_family_members.count
loop
dbms_output.put_line('majority mem of family id'
|| l_family_members(rec).family_id
|| 'stay in '||l_family_members(rec).city);
end loop;
END;
NB: I also fixed the reference in the output loop to put the (rec) after the table and before column

Resources