When using CAST in plsql case, showing error - plsql

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.

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.

Evaluating string/combination of variables as logical expression in oracle pl/sql

In my Pl/Sql code , I have three variables v_var1 , v_operand , v_var2 whose values are populated based on some logic (v_var1 & v_var2 can be date , number , varchar. Associated Operand will be according to data type only). A sample would be
v_var1 = 10 , v_operand = '=' , v_var2 = 20.
Based on these value , I have to evaluate whether the condition "v_var1 -v_operand- v_var2"is true or false.
Ex :- with above values, I have to evaluate whether 10 equals 20 or not.
How can I achieve this ? Can I pass the whole string as '10 = 20' to some function and get the result as false?
One way I can think of is to write CASE statements for evaluating but can there be a better way ?
You could use dynamic SQL to do the evaluation as a filter on the dual table:
declare
v_var1 varchar2(10) := '10';
v_operand varchar2(10) := '=';
v_var2 varchar2(10) := '20';
l_result number;
begin
execute immediate 'select count(*) from dual where :var1 ' || v_operand || ' :var2'
into l_result using v_var1, v_var2;
if l_result = 1 then
dbms_output.put_line('True');
else
dbms_output.put_line('False');
end if;
end;
/
PL/SQL procedure successfully completed.
False
If the condition is true the count will get 1, otherwise it will get 0, and you can then test that via the local variable you select the count into.
Holding dates and numbers as strings isn't ideal, even temporarily, but might be OK as long as you convert to/from the real data types consistently, e.g. always explicitly converting dates with to_date and to_char and specifying the format masks.

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

oracle value exists in collection

I have function (used within a view) with the end result being a list of unique values from a row within a table of comma separated values.
Essentially given a table of:
studentid classes
12345 MATH 1301, HIST 1301, POLS 1301
57495 MATH 2309, HIST 1301
39485 MATH 1301, HIST 1301
I want to see
MATH 1301
MATH 2309
HIST 1301
POLS 1301
The below code works perfect if the source table is small, but when looking at a table of 30,000 rows I get the following error. ORA-06532: Subscript outside of limit
I'm pretty sure my problem is the collection is getting too large since it's getting duplicate values. The duplicate values in themselves only become a problem when the collection becomes too large. How do I keep the duplicate values out of the collection?
I've tried childnames.exists(element) but I believe this only works for seeing if there exists an element at the index value element correct? of I've looked at member of but I don't understand how to implement it.. Is there an easy way to check if a collection element exists? Or am I over looking something simple? Is there a different type other than odcivarchar2list that would allow a larger collection?
CREATE OR REPLACE FUNCTION unique_values_from_csv ( p_del VARCHAR2 := ',')
RETURN SYS.odcivarchar2list
IS
childnames SYS.odcivarchar2list := sys.odcivarchar2list ();
tempvar VARCHAR2(255);
l_idx PLS_INTEGER;
l_list2 VARCHAR2(32767) ;
l_value VARCHAR2(32767);
CURSOR tablewalker_cur
IS
SELECT distinct classes
FROM studentclasses;
BEGIN
FOR recordwalker_rec IN tablewalker_cur
LOOP
l_list2 := recordwalker_rec.classes;
LOOP
l_idx := INSTR (l_list2, p_del);
IF l_idx > 0
THEN
childnames.EXTEND;
tempvar := (LTRIM (RTRIM (SUBSTR (l_list2, 1, l_idx - 1))));
childnames (childnames.COUNT) := tempvar;
l_list2 := SUBSTR (l_list2, l_idx + LENGTH (p_del));
end if;
childnames.EXTEND;
childnames (childnames.COUNT) := (LTRIM (RTRIM (l_list2)));
EXIT;
END LOOP;
END LOOP;
RETURN childnames;
END unique_values_from_csv;
/
create or replace function unique_values_from_csv(p_del varchar2 := ',')
return sys.dbms_debug_vc2coll is
childnames sys.dbms_debug_vc2coll := sys.dbms_debug_vc2coll();
l_idx pls_integer;
l_list2 varchar2(32767) ;
procedure add_if_not_member(new_element varchar2) is
begin
if new_element not member of childnames then
childnames.extend;
childnames(childnames.count) := new_element;
end if;
end;
begin
for recordwalker_rec in (select distinct classes from studentclasses)
loop
l_list2 := recordwalker_rec.classes;
loop
l_idx := instr (l_list2, p_del);
if l_idx > 0 then
add_if_not_member(trim(substr (l_list2, 1, l_idx - 1)));
l_list2 := substr(l_list2, l_idx + length(p_del));
else
add_if_not_member(trim(l_list2));
exit;
end if;
end loop;
end loop;
return childnames;
end unique_values_from_csv;
/
Used SYS.DBMS_DEBUG_VC2COLL, which is a TABLE OF VARCHAR2(1000) and should support any number of elements. Although l_list2 varchar2(32767) will limit the results.
MEMBER OF is the correct condition.
Added an ELSE - the original function was only splitting the list in two.
Removed the cursor - for such a small query, another level of indirection isn't worth it.
Used TRIM() instead of LTRIM(RTRIM())
The best solution would be to throw out this function and stop storing non-atomic data in your database.
Here's some sample data and a query using the function:
create table studentclasses(studentid number, classes varchar2(4000));
insert into studentclasses
select 12345, 'MATH 1301,HIST 1301,POLS 1301' from dual union all
select 57495, 'MATH 2309,HIST 1301' from dual union all
select 39485, 'MATH 1301,HIST 1301' from dual;
commit;
select unique_values_from_csv from dual;
COLUMN_VALUE
MATH 1301
HIST 1301
POLS 1301
MATH 2309
select distinct
regexp_substr(classes, '([^,]+)(,\s*|$)', 1, occ, '', 1) as class
from
studentclasses,
(
select level as occ
from dual
connect by level <= (
select max(regexp_count(classes, ','))+1
from studentclasses
)
)
where
regexp_substr(classes, '([^,]+)(,\s*|$)', 1, occ, '', 1) is not null
order by 1

Not able to store values to a nested table in oracle

I am trying to write a function to show values for monthly data according to the selection made by the user in monthly report. Code snippet below is just trying to fetch values in a nested table and once data is loaded successfully in a nested table, I will call the function to display the table. I have tried a few things; but am running into issues while loading data. Below are 2 different SQLs to create this function but both of them are getting same error regarding incorrect values; I have tried a few things but to no avail:
Snippet 1:
/* Formatted on 10/16/2012 8:40:45 AM (QP5 v5.215.12089.38647) */
CREATE OR REPLACE TYPE tempObject AS OBJECT
(
kpiid number,
kpigroup VARCHAR2 (300)
);
CREATE OR REPLACE TYPE tempTable AS TABLE OF tempObject;
CREATE OR REPLACE FUNCTION KPI_HORIZON.Monthly_All_Data (
mainarea IN VARCHAR2)
RETURN tempTable
IS
MonthlyData temptable := temptable ();
n INTEGER := 0;
BEGIN
IF (mainarea = 'ALL')
THEN
FOR r IN (SELECT DISTINCT kpiid, kpigroup
FROM kpi_summary_reporting
WHERE kpifrequency = 'Monthly' AND active_ind = 'Y')
LOOP
monthlydata.EXTEND;
n := n + 1;
monthlydata (n) := tempobject (r.kpiid, r.kpigroup);
END LOOP;
END IF;
RETURN MonthlyData;
END;
Error: [Error] PLS-00306 (26: 29): PLS-00306: wrong number or types of arguments in call to 'TEMPOBJECT'
Snippet2:
/* Formatted on 10/16/2012 8:27:22 AM (QP5 v5.215.12089.38647) */
CREATE OR REPLACE TYPE tempObject AS OBJECT
(
kpiid NUMBER,
kpigroup VARCHAR2 (300)
);
CREATE OR REPLACE TYPE tempTable AS TABLE OF tempObject;
CREATE OR REPLACE FUNCTION KPI_HORIZON.Monthly_All_Data (
mainarea IN VARCHAR2)
RETURN tempTable
AS
MonthlyData temptable := temptable ();
BEGIN
IF (mainarea = 'ALL')
THEN
SELECT DISTINCT ksr.kpiid, ksr.kpigroup
INTO MonthlyData
FROM kpi_summary_reporting ksr
WHERE kpifrequency = 'Monthly' AND active_ind = 'Y';
ELSE
SELECT DISTINCT kpiid, kpigroup
INTO MonthlyData
FROM kpi_summary_reporting;
END IF;
RETURN MonthlyData;
END;
Error: [Error] ORA-00947 (24: 9): PL/SQL: ORA-00947: not enough values
I would do something like this assuming that the data is small enough that it really makes sense to load it entirely into a nested table in the server's PGA. If the data volume is larger, you probably want to use a pipelined table function instead.
Since your nested table is a table of object types, you need to use the object type constructor.
CREATE OR REPLACE FUNCTION KPI_HORIZON.Monthly_All_Data (
mainarea IN VARCHAR2)
RETURN tempTable
IS
MonthlyData temptable;
BEGIN
IF (mainarea = 'ALL')
THEN
SELECT tempObject( kpiid, kpigroup )
BULK COLLECT INTO monthlydata
FROM kpi_summary_reporting
WHERE kpifrequency = 'Monthly'
AND active_ind = 'Y';
END IF;
RETURN MonthlyData;
END;
I'm always dubious when I see a DISTINCT in a query. Do you really expect to get duplicate rows that you need to remove? If not, you'll be much better served removing the DISTINCT as I did above. If you really need the DISTINCT, then your object type would need a MAP or an ORDER method which would complicate the example a bit.
A demonstration of this working
SQL> CREATE OR REPLACE TYPE tempObject AS OBJECT
2 (
3 kpiid NUMBER,
4 kpigroup VARCHAR2 (300)
5 );
6 /
Type created.
SQL> CREATE OR REPLACE TYPE tempTable AS TABLE OF tempObject;
2 /
Type created.
SQL> create table kpi_summary_reporting (
2 kpiid integer,
3 kpigroup varchar2(300),
4 kpifrequency varchar2(30),
5 active_ind varchar2(1)
6 );
Table created.
SQL> insert into kpi_summary_reporting values( 1, 'Foo', 'Monthly', 'Y' );
1 row created.
SQL> ed
Wrote file afiedt.buf
1 CREATE OR REPLACE FUNCTION Monthly_All_Data (
2 mainarea IN VARCHAR2)
3 RETURN tempTable
4 IS
5 MonthlyData temptable;
6 BEGIN
7 IF (mainarea = 'ALL')
8 THEN
9 SELECT tempObject( kpiid, kpigroup )
10 BULK COLLECT INTO monthlydata
11 FROM kpi_summary_reporting
12 WHERE kpifrequency = 'Monthly'
13 AND active_ind = 'Y';
14 END IF;
15 RETURN MonthlyData;
16* END;
17 /
Function created.
SQL> select monthly_all_data( 'ALL' ) from dual;
MONTHLY_ALL_DATA('ALL')(KPIID, KPIGROUP)
--------------------------------------------------------------------------------
TEMPTABLE(TEMPOBJECT(1, 'Foo'))

Resources