I am struggling with an issue regarding a CLOB.
I would like to create a trigger (after update) which updates the column of a table which is a CLOB.
This CLOB contains lines in this form :
foo|132|65|12/08/2016|18395|
bar|132|54|15/08/2014|32434343|
I would like to modify the CLOB such that the line beginning with "foo" has the value "18395" divided by 1000. The line will look like
foo|132|65|12/08/2016|18.395|
Is there a quick way to modify my CLOB?
Thank you for your help.
EDIT : I found a way to modify the line of the CLOB what I need to do is just to modify the CLOB to replace
foo|132|65|12/08/2016|18395|
by
foo|132|65|12/08/2016|18.395|
This is rather long but effective.
Firstly create a type:
create or replace TYPE "array_str" AS VARRAY(10) OF VARCHAR(256);
Then create a function that will return a array based in a delimiter:
FUNCTION string_to_array (
string_delimited IN VARCHAR2,
delimiter IN VARCHAR2 DEFAULT ','
) RETURN array_str IS
pls_idx PLS_INTEGER;
split_string array_str := array_str();
pls_del_len PLS_INTEGER := length(delimiter);
BEGIN
IF string_delimited IS NOT NULL THEN
LOOP
-- search for delimiter string
pls_idx := instr(l_string_delimited, delimiter);
-- increase the size of array
split_string.extend;
-- check last search of delimiter is success
IF pls_idx = 0 THEN
split_string(l_split_string.count) := substr(l_string_delimited, 1);
ELSE
split_string(l_split_string.count) := substr(l_string_delimited, 1, pls_idx - 1);
END IF;
-- exit from loop when last string
EXIT WHEN nvl(l_pls_idx, 0) = 0;
string_delimited := substr(l_string_delimited, pls_idx + pls_del_len);
END LOOP;
END IF;
RETURN split_string;
END string_to_array;
Then create your trigger:
CREATE OR REPLACE TRIGGER your_trigger AFTER
UPDATE ON table_name
FOR EACH ROW
DECLARE
clob_array array_str;
clob_str CLOB := '';
BEGIN
clob_array := string_to_array(:new.new_clob, '|');
IF ( clob_array(1) = 'foo' ) THEN
clob_array(5) := to_number(clob_array(5) / 1000);
END IF;
FOR i IN 1..clob_array.count - 1 LOOP clob_str := clob_str
|| to_char(clob_array(i))
|| '|';
END LOOP;
-- do something with clob_str
END;
I want to convert a string array to a date type array. But I'm not sure if my date array is correctly defined. Can't find any example of how to define or create an array of date type.
This is how I have declared my string array and date type array.
create or replace TYPE array_collection IS table OF VARCHAR2(50)
--string array is working absolutely fine so dw about that
create or replace type date_array is table of date;
--here i don't if I've defined this array correctly
my convert array procedure:
create or replace procedure convert_arr(
dat_ar in array_collection,perdate_arr out date_array)
as
begin
perdate_arr:=new date_array();
perdate_arr.extend(dat_ar.count);
for i in 1..dat_ar.count loop
perdate_arr(i):=to_date(dat_ar(i), 'yyyy-mm-dd');
end loop;
end convert_arr;
--compiles perfectly
calling anonymous block:
set serveroutput on
declare
--l_dat array_collection;
--l_darray date_array;
l_dat array_collection:=array_collection();
l_darray date_array:=date_array();
begin
l_dat := array_collection('2011-01-01','2011-04-01','2011-05-01');
--l_dat.extend(3);
-- l_dat(1):= to_date(2019-07-08);
-- l_dat(2):= to_date(2019-07-09);
-- l_dat(3):= to_date(2019-06-02);
convert_arr(dat_ar=>l_dat,perdate_arr=>l_darray);
dbms_output.put_line('Number of array:' || l_dat.count);
for i in 1..l_dat.count loop
dbms_output.put_line('Date ' || i || ':' || to_char(l_dat(i),'dd/mm/yyyy'));
end loop;
end;
this block gives me an error:
Error report -
ORA-01861: literal does not match format string
ORA-06512: at line 9
01861. 00000 - "literal does not match format string"
*Cause: Literals in the input must be the same length as literals in
the format string (with the exception of leading whitespace). If the
"FX" modifier has been toggled on, the literal must match exactly,
with no extra whitespace.
*Action: Correct the format string to match the literal.
Tried changing formats but doesn't help. Any help would be highly appreciated. Thankyou
Since l_darray is your date array, loop through it for displaying rather than l_dat
set serveroutput on
declare
--l_dat array_collection;
--l_darray date_array;
l_dat array_collection:=array_collection();
l_darray date_array:=date_array();
begin
l_dat := array_collection('2011-01-01','2011-04-01','2011-05-01');
--l_dat.extend(3);
-- l_dat(1):= to_date(2019-07-08);
-- l_dat(2):= to_date(2019-07-09);
-- l_dat(3):= to_date(2019-06-02);
convert_arr(dat_ar=>l_dat,perdate_arr=>l_darray);
dbms_output.put_line('Number of array:' || l_darray.count);
for i in 1..l_darray.count loop
dbms_output.put_line('Date ' || i || ':' || to_char(l_darray(i),'dd/mm/yyyy'));
end loop;
end;
/
Result
Number of array:3
Date 1:01/01/2011
Date 2:01/04/2011
Date 3:01/05/2011
PL/SQL procedure successfully completed.
I am using dynamic SQL where I am dynamically using value of column name to bind and its value to be bind
OLD CODE
<Outer Loop>
FOR i IN lvaDBOBJDTLRecTab.FIRST .. lvaDBOBJDTLRecTab.LAST
LOOP
DBMS_SQL.BIND_VARIABLE ( lvnInsertCursorId, ':RTTEXT2VC100',
lvaDBOBJDTLRecTab(i).DBONAME );
DBMS_SQL.BIND_VARIABLE ( lvnInsertCursorId, ':RTTEXT3VC100',
lvaDBOBJDTLRecTab(i).DBOTYPE );
3.
.
.
.
100
END LOOP;
Instead of writing BIND_VARIABLE for 100 times , I want to dynamically access value of collection. I am able to fetch the value of columns dynamically, which need to be bind (lvsColForBinding), however value of lvsColValForBind
is coming as 'lvrCurDBOBJDTL(i).DBONAME' , 'lvrCurDBOBJDTL(i).DBOTYPE'
and same for rest of the 98 columns,
<Inner Loop>
FOR j IN lvaMappingTab.FIRST..lvaMappingTab.LAST
LOOP
lvsColForBinding := ':'||lvaMappingTab(j).MstRptColCds;
lvsColValForBind := 'lvrCurDBOBJDTL(i).'||lvaMappingTab(j).RptColCd;
DBMS_SQL.BIND_VARIABLE ( lvnInsertCursorId,lvsColForBinding, lvsColValForBind);
END LOOP;
when DBMS_SQL.BIND_VARIABLE is run for each row, as mentioned earlier Column to be bind comes correct but value to be bind, instead of coming as
value of 'XYZ' = lvrCurDBOBJDTL(i).DBONAME it comes as this in single quotes 'lvrCurDBOBJDTL(i).DBONAME' same for all columns.
how can we extract the value of each element in inner loop. what step we need to do to fetch the value of lvsColValForBind?
While debugging through SQLDEveloper Watches, I can see the, element name, value and type, when adding and double clicking the plsql record variable,
what is the SQL behind that, can we use that in coding ?
My first recommendation is that you use dynamic SQL to generate lots of dumb code instead of using a small amount of smart PL/SQL. If code generation doesn't work, you can use ANYDATA and ANYTYPE to create PL/SQL reflection to dynamically iterate through the elements of a record at run time.
Code Generation
Don't write BIND_VARIABLE 100 times, but create a small program to generate the 100 lines of code for you. If the data is ultimately coming from one table and going into another table, the input and output may be predictable based on data dictionary views like DBA_TAB_COLUMNS.
Hopefully a small query like this could help generate all the code for a single table:
--Generate PL/SQL statements for binds.
select
'DBMS_SQL.BIND_VARIABLE(lvnInsertCursorId, '':RTTEXT'||column_id||'VC100'', lvaDBOBJDTLRecTab(i).'||column_name||');'
from dba_tab_columns
where owner = 'SOME_OWNER'
and table_name = 'SOME_TABLE'
order by 1;
Then you can copy-and-paste the output into the PL/SQL block. You'll probably also want a warning, like "do not modify, this code is autogenerated by the procedure CODE_TRON_2000".
This approach will only work if the PL/SQL code is predictable, based on the data dictionary or some other metadata.
PL/SQL Reflection
There's no pure PL/SQL reflection for PL/SQL types* but there's a simple workaround if you're willing to create the record types as SQL objects instead. If all your PL/SQL records are based on object types then ANYDATA and ANYTYPE can be used to dynamically access attributes. Object types and PL/SQL record types are pretty similar, it should be relatively painless to convert one to the other.
For example, if you create an object type that contains a number and a string:
create or replace type v_type is object(a number, b varchar2(1));
This (painful) PL/SQL block shows how to iterate through all the records of a collection, and then iterate through all of the attributes in each record. (The code prints the values for, you'll have to add the binding parts yourself.)
declare
type v_nt_type is table of v_type;
v_values v_nt_type := v_nt_type(v_type(1, 'A'), v_type(2, 'B'));
begin
--For each record:
for i in 1 .. v_values.count loop
declare
v_anydata anydata := anydata.ConvertObject(v_values(i));
v_number number;
v_varchar2 varchar2(4000);
v_result pls_integer;
v_anytype anytype;
v_dummy_num pls_integer;
v_dummy_char varchar2(4000);
v_dummy_anytype anytype;
v_number_of_elements number;
begin
--Get the ANYTYPE and the number of elements.
v_result := v_anydata.getType(v_anytype);
v_result := v_anytype.getInfo
(
prec => v_dummy_num,
scale => v_dummy_num,
len => v_dummy_num,
csid => v_dummy_num,
csfrm => v_dummy_num,
schema_name => v_dummy_char,
type_name => v_dummy_char,
version => v_dummy_char,
numelems => v_number_of_elements
);
--For each element in the record:
for i in 1 .. v_number_of_elements loop
--Find the type of the element:
v_anydata.piecewise;
v_result := v_anytype.getAttrElemInfo(
pos => i,
prec => v_dummy_num,
scale => v_dummy_num,
len => v_dummy_num,
csid => v_dummy_num,
csfrm => v_dummy_num,
attr_elt_type => v_dummy_anytype,
aname => v_dummy_char);
--This is where you do something interesting with the values.
--(The same code merely prints the values.)
if v_result = dbms_types.typecode_number then
v_result := v_anydata.getNumber(num => v_number);
dbms_output.put_line(v_number);
elsif v_result = dbms_types.typecode_varchar2 then
v_result := v_anydata.getVarchar2(c => v_varchar2);
dbms_output.put_line(v_varchar2);
--TODO: Add other potential types here.
end if;
end loop;
end;
end loop;
end;
/
Results:
1
A
2
B
* You're right that there must be some way to find this run time information, if the debugger gets it. But as far as I know there is no way for PL/SQL to retrieve that debug information. Maybe it's only available to an OCI(?) interface?
When you call bind_variable, you are binding an actual value to a placeholder. So if you provide a string that is the name of your variable, well, that string is the value bound to the placeholder.
If the array holds those values, then simply reference the array element and not the name of that element, as in:
DBMS_SQL.BIND_VARIABLE (
lvnInsertCursorId,
lvaMappingTab(j).MstRptColCds,
lvrCurDBOBJDTL(i).lvaMappingTab(j).RptColCd);
But I am pretty sure that's not what you've got there. Hope this helps!
#Jon
Thanks for your inputs, that helped. also I am able to iterate cols without creating OBJECTs using DBMS_SQL.DESCRIBE_COLUMNS.
**Below code still need little bit fine tuning, but mostly working :)
BEGIN
COLS_TRAVERSE('SELECT * FROM ALL_OBJECTS WHERE ROWNUM<=100');
END;
create or replace PROCEDURE COLS_TRAVERSE ( p_query in varchar2 )
AS
v_curid NUMBER;
v_desctab DBMS_SQL.DESC_TAB;
v_colcnt NUMBER;
v_RowNumcnt NUMBER := 1;
v_Colname_var VARCHAR2(10000);
v_name_var VARCHAR2(10000);
v_num_var NUMBER;
v_date_var DATE;
v_row_num NUMBER;
p_sql_stmt VARCHAR2(1000);
BEGIN
v_curid := DBMS_SQL.OPEN_CURSOR;
DBMS_SQL.PARSE(v_curid, p_query, DBMS_SQL.NATIVE);
DBMS_SQL.DESCRIBE_COLUMNS(v_curid, v_colcnt, v_desctab);
-- Define columns:
FOR i IN 1 .. v_colcnt LOOP
IF v_desctab(i).col_type = 2 THEN
DBMS_SQL.DEFINE_COLUMN(v_curid, i, v_num_var);
ELSIF v_desctab(i).col_type = 12 THEN
DBMS_SQL.DEFINE_COLUMN(v_curid, i, v_date_var);
ELSE
DBMS_SQL.DEFINE_COLUMN(v_curid, i, v_name_var, 50);
END IF;
END LOOP;
v_row_num := dbms_sql.execute(v_curid);
-- Fetch rows with DBMS_SQL package:
WHILE DBMS_SQL.FETCH_ROWS(v_curid) > 0 LOOP
FOR i IN 1 .. v_colcnt
LOOP
v_Colname_var := v_desctab(i).col_name;
dbms_output.put_line( 'Name:' ||v_Colname_var );
IF (v_desctab(i).col_type = 1) THEN
DBMS_SQL.COLUMN_VALUE(v_curid, i, v_name_var);
dbms_output.put_line( 'String Value:' || v_name_var );
ELSIF (v_desctab(i).col_type = 2) THEN
DBMS_SQL.COLUMN_VALUE(v_curid, i, v_num_var);
dbms_output.put_line( 'Number Value:' || v_num_var);
ELSIF (v_desctab(i).col_type = 12) THEN
DBMS_SQL.COLUMN_VALUE(v_curid, i, v_date_var);
dbms_output.put_line( 'Date Value:' || v_date_var );
END IF;
END LOOP;
dbms_output.put_line( 'End of Row Number # ' ||v_RowNumcnt );
v_RowNumcnt := v_RowNumcnt+1;
END LOOP;
DBMS_SQL.CLOSE_CURSOR(v_curid);
END;
/
DBMS_OUT PUT
Name:OWNER
String Value:SYS
Name:OBJECT_NAME
String Value:ORA$BASE
Name:SUBOBJECT_NAME
String Value:
Name:OBJECT_ID
Number Value:134
Name:DATA_OBJECT_ID
Number Value:
Name:OBJECT_TYPE
String Value:EDITION
Name:CREATED
Date Value:30-03-18
Name:LAST_DDL_TIME
Date Value:30-03-18
Name:TIMESTAMP
String Value:2018-03-30:21:37:22
Name:STATUS
String Value:VALID
Name:TEMPORARY
String Value:N
Name:GENERATED
String Value:N
Name:SECONDARY
String Value:N
Name:NAMESPACE
Number Value:64
Name:EDITION_NAME
String Value:
Name:SHARING
String Value:NONE
Name:EDITIONABLE
String Value:
Name:ORACLE_MAINTAINED
String Value:Y
Name:APPLICATION
String Value:N
Name:DEFAULT_COLLATION
String Value:
Name:DUPLICATED
String Value:N
Name:SHARDED
String Value:N
Name:CREATED_APPID
Number Value:
Name:CREATED_VSNID
Number Value:
Name:MODIFIED_APPID
Number Value:
Name:MODIFIED_VSNID
Number Value:
End of Row Number # 1
Name:OWNER
String Value:SYS
Name:OBJECT_NAME
String Value:DUAL
Name:SUBOBJECT_NAME
String Value:
Name:OBJECT_ID
Number Value:143
Name:DATA_OBJECT_ID
Number Value:143
Name:OBJECT_TYPE
String Value:TABLE
Name:CREATED
Date Value:30-03-18
Name:LAST_DDL_TIME
Date Value:31-03-18
Name:TIMESTAMP
String Value:2018-03-30:21:37:22
Name:STATUS
String Value:VALID
Name:TEMPORARY
String Value:N
Name:GENERATED
String Value:N
Name:SECONDARY
String Value:N
Name:NAMESPACE
Number Value:1
Name:EDITION_NAME
String Value:
Name:SHARING
String Value:METADATA LINK
Name:EDITIONABLE
String Value:
Name:ORACLE_MAINTAINED
String Value:Y
Name:APPLICATION
String Value:N
Name:DEFAULT_COLLATION
String Value:USING_NLS_COMP
Name:DUPLICATED
String Value:N
Name:SHARDED
String Value:N
Name:CREATED_APPID
Number Value:
Name:CREATED_VSNID
Number Value:
Name:MODIFIED_APPID
Number Value:
Name:MODIFIED_VSNID
Number Value:
End of Row Number # 2
How can I check if two associative arrays are same or not?
E.g.
ARRAY1('ID') := 1;
ARRAY1('NAME') := 'Joe';
ARRAY2('ID') := 1;
ARRAY2('NAME') := 'Joe';
-- and i want to do like this
IF ARRAY1 = ARRAY2 THEN
-- Do something.
END IF;
You can compare two associative array using a function to compare the keys and values:
DECLARE
TYPE test_array IS TABLE OF VARCHAR2(200) INDEX BY VARCHAR2(10);
array1 test_array;
array2 test_array;
array3 test_array;
FUNCTION is_equal(
arr1 test_array,
arr2 test_array
) RETURN BOOLEAN
IS
i VARCHAR2(10);
BEGIN
i := arr1.FIRST;
WHILE i IS NOT NULL LOOP
-- Check if the keys in the first array do not exists in the
-- second array or if the values are different.
IF ( NOT arr2.EXISTS( i ) )
OR ( arr1(i) <> arr2(i) )
OR ( arr1(i) IS NULL AND arr2(i) IS NOT NULL )
OR ( arr1(i) IS NOT NULL AND arr2(i) IS NULL )
THEN
RETURN FALSE;
END IF;
i := arr1.NEXT(i);
END LOOP;
i := arr2.FIRST;
WHILE i IS NOT NULL LOOP
-- Check if there are any keys in the second array that do not
-- exists in the first array.
IF ( NOT arr1.EXISTS( i ) )
THEN
RETURN FALSE;
END IF;
i := arr2.NEXT(i);
END LOOP;
RETURN TRUE;
END;
BEGIN
array1('ID') := '1';
array1('NAME') := 'Joe';
array2('ID') := '1';
array2('NAME') := 'Joe';
array3('ID') := '1';
array3('NAME') := 'Joe';
array3('XYZ') := 'ABC';
IF is_equal( array1, array2 ) THEN
DBMS_OUTPUT.PUT_LINE( '1 and 2 are the same' );
ELSE
DBMS_OUTPUT.PUT_LINE( '1 and 2 are different' );
END IF;
IF is_equal( array1, array3 ) THEN
DBMS_OUTPUT.PUT_LINE( '1 and 3 are the same' );
ELSE
DBMS_OUTPUT.PUT_LINE( '1 and 3 are different' );
END IF;
END;
/
Outputs:
1 and 2 are the same
1 and 3 are different
Oracle documentation is your friend.
https://docs.oracle.com/database/121/LNPLS/composites.htm#LNPLS99915
In particular:
You cannot compare associative array variables to the value NULL or to each other.
Which means, if you need to compare associative arrays to each other, you have only two choices... check them element by element, for example in a loop (on the elements of one array - and make sure you check to see if they have the same number of elements); or, if you need to do this often, write your own function that compares arrays in that manner, and call it whenever you need to.
Just don't ask why Oracle is not providing a native PL/SQL function for this; you will very likely get no answers.
Is there a way to either return a populated record type or make the assignments as part of a CASE statement?
for example:
DECLARE
TYPE field_definition_rec IS RECORD(FIELD NUMBER(3),
FIELD_ID VARCHAR2(2),
DEFINITION VARCHAR2(200));
loc_rec field_definition_rec;
BEGIN
loc_rec := CASE
WHEN 1 THEN field = 1
field_id = 'afd',
definition = 'fdas'
END CASE;
-- OR
CASE
WHEN 1 THEN
loc_rec.field = 1
loc_rec.field_id = 'afd',
loc_rec.definition = 'fdas'
END CASE;
END;
I was hoping to use a case over an IF for simplicity sake.