associative array and sysdate - oracle

I have query building plsql code. I use bind variables and associative array to store their values. Something like that:
declare
type myt table of varchar2(4000) index by varchar2(100);
vars myt;
v_cursor integer;
newQuery varchar2(1000) := 'select * from blabla where ';
bind_key varchar2(100);
rows_count integer;
begin
-- query building
if 1=1 then
newQuery := newQuery || 'col1 = :bind_col1';
vars(':bind_col1') := sysdate; -- problem!!!
end if;
....
-- query execution
v_cursor := dbms_sql.open_cursor;
dbms_sql.parse(v_cursor, newQuery, dbms_sql.v7);
bind_key := vars.first;
loop
exit when bind_key is null;
dbms_sql.bind_variable(v_cursor, bind_key, vars(bind_key));
bind_key := vars.next(bind_key);
end loop;
row_count := dbms_sql.execute(v_cursor);
dbms_sql.close_cursor(v_cursor);
end;
Are there any more generic type than varchar2, so there will not be conversion date->varchar2->date?

dbms_sql.bind_variable can bind variables of many datatypes. You can't however declare a table of <any_data_type> (which would be logically equivalent to something like Object[] in java for example.)
When you're working with VARCHAR2 you can use bijective explicit conversion, for example:
newQuery := newQuery || 'col1 = to_date(:bind_col1, ''yyyymmdd hh24:mi:ss'')';
vars(':bind_col1') := to_char(sysdate, 'yyyymmdd hh24:mi:ss');

Related

PLSQL Dynamic SQL Bind Variable by Name

I want to execute an sql statement dynamically in my procedure. As far as I could see, the binding is done according to the order of the usage.
Is there a way in which I can say something like
:a --> par_a_
There is a differnce between execute immediate 'plsql code' and execute immediate 'sql', In dynamic pl/sql oracle will use real bindes and you may specify it once in correct order and it will replace all binds if there are repeted. with sql you should spesify all binds instead of repeting it.
declare
l_sql varchar2(4000) := 'select :a from dual union all select :a from dual';
l_pl_sql varchar2(4000) := 'begin dbms_output.put_line(:a); dbms_output.put_line(:a); end;';
type t_tab_str is table of varchar2(4000);
l_res t_tab_str ;
begin
execute immediate l_sql bulk collect into l_res using '1','2';
for i in 1.. l_res.last loop
dbms_output.put_line(l_res(i));
end loop;
execute immediate l_pl_sql using '1';
end;
you may use dbms_sql and it function bind
declare
l_sql varchar2(4000) := 'select :a from dual union all select :a from dual';
type t_tab_str is table of varchar2(4000);
l_res t_tab_str ;
l_sql_id number;
l_ret number;
type curtype is ref cursor;
l_cursor curtype ;
begin
dbms_sql.parse(l_sql_id ,l_sql,dbms_sql.native);
dbms_sql.bind_variable(l_sql_id,'a','1');
l_ret := dbms_sql.execute(l_sql_id);
l_cursor := dbms_sql.to_refcursor(l_sql_id);
fetch l_cursor bulk collect into l_res;
for i in 1.. l_res.last loop
dbms_output.put_line(l_res(i));
end loop;
end;
Seems to me what you are after is USING keyword.
Below is example from oracle documentation.
DECLARE
plsql_block VARCHAR2(500);
new_deptid NUMBER(4);
new_dname VARCHAR2(30) := 'Advertising';
new_mgrid NUMBER(6) := 200;
new_locid NUMBER(4) := 1700;
BEGIN
-- Dynamic PL/SQL block invokes subprogram:
plsql_block := 'BEGIN create_dept(:a, :b, :c, :d); END;';
/* Specify bind arguments in USING clause.
Specify mode for first parameter.
Modes of other parameters are correct by default. */
EXECUTE IMMEDIATE plsql_block
USING IN OUT new_deptid, new_dname, new_mgrid, new_locid;
END;
/
https://docs.oracle.com/cd/B28359_01/appdev.111/b28370/dynamic.htm

PL/SQL file writing with generic input

I recently created a PL/SQL program that creates five different pipe delimited files from related data in a database.
I could not find a way to dynamically pull different tabular data in this case cursors, into a generic procedure that would create the files.
Instead I had to create five separate procedures, one for each file, that took in five different cursors, one for each file requirement record selection.
I can't help but think that there has to be a better way. I was looking into reference cursors but I don't think they are exactly what I am looking for.
How can I achieve this in PL/SQL?
I think what I am looking for is some generic type that can take any data from a cursor given any amount of records and record columns and have the ability to query itself to find what data is in it.
Pass the cursor into your procedure as a SYS_REFCURSOR. Then, use DBMS_SQL.TO_CURSOR_NUMBER(); to convert the ref cursor to a DBMS_SQL cursor.
Then, use DBMS_SQL.DESCRIBE_COLUMNS to figure out the columns in the cursor and DBMS_SQL.DEFINE_COLUMN, DBMS_SQL.FETCH_ROWS and DBMS_SQL.VALUE to get the data from the cursor into PL/SQL variables. Then, write your PL/SQL variables to your output file.
Here's some code that puts all that together for you.
DECLARE
l_rc SYS_REFCURSOR;
PROCEDURE dump_cursor (p_rc IN OUT SYS_REFCURSOR) IS
-- Dump the results of p_rc to log
l_cursor INTEGER;
l_column_count INTEGER;
l_column_descriptions SYS.DBMS_SQL.desc_tab;
l_status INTEGER;
l_column_value VARCHAR2 (4000);
l_column_width NUMBER;
l_rec_count NUMBER := 0;
l_line VARCHAR2 (4000);
FUNCTION get_length (l_column_def IN SYS.DBMS_SQL.desc_rec)
RETURN NUMBER IS
l_width NUMBER;
BEGIN
l_width := l_column_def.col_max_len;
l_width := CASE l_column_def.col_type WHEN 12 THEN /* DATE */
20 WHEN 2 THEN /* NUMBER */
10 ELSE l_width END;
-- Don't display more than 256 characters of any one column (this was my requirement -- your file writer probably doesn't need to do this
l_width := LEAST (256, GREATEST (l_width, l_column_def.col_name_len));
RETURN l_width;
END get_length;
BEGIN
-- This is the date format that I want to use for dates in my output
EXECUTE IMMEDIATE 'alter session set nls_date_format=''DD-MON-YYYY HH24:MI:SS''';
l_cursor := sys.DBMS_SQL.to_cursor_number (p_rc);
-- Describe columns
sys.DBMS_SQL.describe_columns (c => l_cursor, col_cnt => l_column_count, desc_t => l_column_descriptions);
l_line := '';
FOR i IN 1 .. l_column_count LOOP
l_column_width := get_length (l_column_descriptions (i));
l_line := l_line || RPAD (l_column_descriptions (i).col_name, l_column_width);
l_line := l_line || ' ';
DBMS_SQL.define_column (l_cursor,
i,
l_column_value,
4000);
END LOOP;
DBMS_OUTPUT.put_line (l_line);
l_line := '';
FOR i IN 1 .. l_column_count LOOP
l_column_width := get_length (l_column_descriptions (i));
l_line := l_line || RPAD ('-', l_column_width, '-');
l_line := l_line || ' ';
DBMS_SQL.define_column (l_cursor,
i,
l_column_value,
4000);
END LOOP;
DBMS_OUTPUT.put_line (l_line);
-- l_status := sys.DBMS_SQL.execute (l_cursor);
WHILE (sys.DBMS_SQL.fetch_rows (l_cursor) > 0) LOOP
l_rec_count := l_rec_count + 1;
l_line := '';
FOR i IN 1 .. l_column_count LOOP
DBMS_SQL.COLUMN_VALUE (l_cursor, i, l_column_value);
l_column_value := TRANSLATE (l_column_value, CHR (10), CHR (200));
l_column_width := get_length (l_column_descriptions (i));
IF l_column_value IS NULL THEN
l_line := l_line || RPAD (' ', l_column_width);
ELSE
l_line := l_line || RPAD (l_column_value, l_column_width);
END IF;
l_line := l_line || ' ';
END LOOP;
DBMS_OUTPUT.put_line (l_line);
END LOOP;
IF l_rec_count = 0 THEN
DBMS_OUTPUT.put_line ('No data found.');
ELSE
DBMS_OUTPUT.put_line (l_rec_count || ' rows returned.');
END IF;
sys.DBMS_SQL.close_cursor (l_cursor);
-- It would be better to store the current NLS_DATE_FORMAT on entry and restore it here, instead of assuming that it was
-- set to DD-MON-YYYY.
EXECUTE IMMEDIATE 'alter session set nls_date_format=''DD-MON-YYYY''';
EXCEPTION
WHEN OTHERS THEN
EXECUTE IMMEDIATE 'alter session set nls_date_format=''DD-MON-YYYY''';
-- Add your own handling here.
END dump_cursor;
-- Tester code, make sure server output is on
BEGIN
OPEN l_rc FOR 'SELECT object_id, object_name, object_type FROM dba_objects WHERE rownum <= 15';
dump_cursor(l_rc);
END;

get key and value of associative arrays

My problem is that i want to get key and value from an assosiative array, but i only can find how to get the value from the key. This is what i find:
DECLARE
TYPE assoc_array IS TABLE OF VARCHAR2(30)
INDEX BY VARCHAR2(30);
state_array assoc_array;
BEGIN
state_array('Alaska') := 'Juneau';
state_array('California') := 'Sacramento';
dbms_output.put_line(state_array('Alaska'));
dbms_output.put_line(state_array('California'));
END;
This prints Juneau and Sacramento, but i want something like this:
DECLARE
TYPE assoc_array IS TABLE OF VARCHAR2(30)
INDEX BY VARCHAR2(30);
state_array assoc_array;
BEGIN
state_array('Alaska') := 'Juneau';
state_array('California') := 'Sacramento';
for x in 1..state_array.count loop
dbms_output.put_line(state_array(x).key || state_array(x).value);
end loop;
END;
Is that possible?. Thanks in advance!!
Actually there is a way, kindly consider the code bellow
declare
type assoc_array is table of varchar2(30) index by varchar2(30);
state_array assoc_array;
l_idx varchar2(30);
begin
state_array('Alaska') := 'Juneau';
state_array('California') := 'Sacramento';
l_idx := state_array.first;
while (l_idx is not null) loop
dbms_output.put_line('Key = ' || l_idx || ':Value = ' || state_array(l_idx));
l_idx := state_array.next(l_idx);
end loop;
end;
The output will be
Key = Alaska:Value = Juneau
Key = California:Value = Sacramento
You cannot do this using associative arrays. Because, see below.
DECLARE
TYPE assoc_array IS TABLE OF VARCHAR2(30)
INDEX BY VARCHAR2(30);
state_array assoc_array;
BEGIN
state_array('Alaska') := 'Juneau';
state_array('California') := 'Sacramento';
for x in 1..state_array.count loop
dbms_output.put_line('x ='||x);
end loop;
END;
would print
x = 1
x = 2
There is no way for oracle to know that x = 1 = Alaska.
You should use binary array to do something like this.

PLSQL Oracle 10 - executing a stored proc

I'm trying to execute a stored procedure, but I'm not sure if my syntax is wrong or not.
set dbms_output.put_line on;
declare
v_premnum NUMBER(10);
v_util_type CHAR(10);
v_result VARCHAR2(200);
Begin
execute myprocedure( 'E', v_util_type, 73105 , v_premnum);
dbms_output.put_line = v_result
end;
/
There are two ways. Either your procedure has an output parameter:
declare
v_premnum NUMBER(10) := 1234;
v_util_type CHAR(10) := 'the type';
v_result VARCHAR2(200);
begin
myprocedure('E', v_util_type, 73105 , v_premnum, v_result);
dbms_output.put_line(v_result);
end;
Or it should be a function returning the desired value:
declare
v_premnum NUMBER(10) := 1234;
v_util_type CHAR(10) := 'the type';
v_result VARCHAR2(200);
begin
v_result := myfunction('E', v_util_type, 73105 , v_premnum);
dbms_output.put_line(v_result);
end;

Oracle PL/SQL - How to create a simple array variable?

I'd like to create an in-memory array variable that can be used in my PL/SQL code. I can't find any collections in Oracle PL/SQL that uses pure memory, they all seem to be associated with tables. I'm looking to do something like this in my PL/SQL (C# syntax):
string[] arrayvalues = new string[3] {"Matt", "Joanne", "Robert"};
Edit:
Oracle: 9i
You can use VARRAY for a fixed-size array:
declare
type array_t is varray(3) of varchar2(10);
array array_t := array_t('Matt', 'Joanne', 'Robert');
begin
for i in 1..array.count loop
dbms_output.put_line(array(i));
end loop;
end;
Or TABLE for an unbounded array:
...
type array_t is table of varchar2(10);
...
The word "table" here has nothing to do with database tables, confusingly. Both methods create in-memory arrays.
With either of these you need to both initialise and extend the collection before adding elements:
declare
type array_t is varray(3) of varchar2(10);
array array_t := array_t(); -- Initialise it
begin
for i in 1..3 loop
array.extend(); -- Extend it
array(i) := 'x';
end loop;
end;
The first index is 1 not 0.
You could just declare a DBMS_SQL.VARCHAR2_TABLE to hold an in-memory variable length array indexed by a BINARY_INTEGER:
DECLARE
name_array dbms_sql.varchar2_table;
BEGIN
name_array(1) := 'Tim';
name_array(2) := 'Daisy';
name_array(3) := 'Mike';
name_array(4) := 'Marsha';
--
FOR i IN name_array.FIRST .. name_array.LAST
LOOP
-- Do something
END LOOP;
END;
You could use an associative array (used to be called PL/SQL tables) as they are an in-memory array.
DECLARE
TYPE employee_arraytype IS TABLE OF employee%ROWTYPE
INDEX BY PLS_INTEGER;
employee_array employee_arraytype;
BEGIN
SELECT *
BULK COLLECT INTO employee_array
FROM employee
WHERE department = 10;
--
FOR i IN employee_array.FIRST .. employee_array.LAST
LOOP
-- Do something
END LOOP;
END;
The associative array can hold any make up of record types.
Hope it helps,
Ollie.
You can also use an oracle defined collection
DECLARE
arrayvalues sys.odcivarchar2list;
BEGIN
arrayvalues := sys.odcivarchar2list('Matt','Joanne','Robert');
FOR x IN ( SELECT m.column_value m_value
FROM table(arrayvalues) m )
LOOP
dbms_output.put_line (x.m_value||' is a good pal');
END LOOP;
END;
I would use in-memory array. But with the .COUNT improvement suggested by uziberia:
DECLARE
TYPE t_people IS TABLE OF varchar2(10) INDEX BY PLS_INTEGER;
arrayvalues t_people;
BEGIN
SELECT *
BULK COLLECT INTO arrayvalues
FROM (select 'Matt' m_value from dual union all
select 'Joanne' from dual union all
select 'Robert' from dual
)
;
--
FOR i IN 1 .. arrayvalues.COUNT
LOOP
dbms_output.put_line(arrayvalues(i)||' is my friend');
END LOOP;
END;
Another solution would be to use a Hashmap like #Jchomel did here.
NB:
With Oracle 12c you can even query arrays directly now!
Another solution is to use an Oracle Collection as a Hashmap:
declare
-- create a type for your "Array" - it can be of any kind, record might be useful
type hash_map is table of varchar2(1000) index by varchar2(30);
my_hmap hash_map ;
-- i will be your iterator: it must be of the index's type
i varchar2(30);
begin
my_hmap('a') := 'apple';
my_hmap('b') := 'box';
my_hmap('c') := 'crow';
-- then how you use it:
dbms_output.put_line (my_hmap('c')) ;
-- or to loop on every element - it's a "collection"
i := my_hmap.FIRST;
while (i is not null) loop
dbms_output.put_line(my_hmap(i));
i := my_hmap.NEXT(i);
end loop;
end;
Sample programs as follows and provided on link also https://oracle-concepts-learning.blogspot.com/
plsql table or associated array.
DECLARE
TYPE salary IS TABLE OF NUMBER INDEX BY VARCHAR2(20);
salary_list salary;
name VARCHAR2(20);
BEGIN
-- adding elements to the table
salary_list('Rajnish') := 62000; salary_list('Minakshi') := 75000;
salary_list('Martin') := 100000; salary_list('James') := 78000;
-- printing the table name := salary_list.FIRST; WHILE name IS NOT null
LOOP
dbms_output.put_line ('Salary of ' || name || ' is ' ||
TO_CHAR(salary_list(name)));
name := salary_list.NEXT(name);
END LOOP;
END;
/
Using varray is about the quickest way to duplicate the C# code that I have found without using a table.
Declare your public array type to be use in script
type t_array is varray(10) of varchar2(60);
This is the function you need to call - simply finds the values in the string passed in using a comma delimiter
function ConvertToArray(p_list IN VARCHAR2)
RETURN t_array
AS
myEmailArray t_array := t_array(); --init empty array
l_string varchar2(1000) := p_list || ','; - (list coming into function adding final comma)
l_comma_idx integer;
l_index integer := 1;
l_arr_idx integer := 1;
l_email varchar2(60);
BEGIN
LOOP
l_comma_idx := INSTR(l_string, ',', l_index);
EXIT WHEN l_comma_idx = 0;
l_email:= SUBSTR(l_string, l_index, l_comma_idx - l_index);
dbms_output.put_line(l_arr_idx || ' - ' || l_email);
myEmailArray.extend;
myEmailArray(l_arr_idx) := l_email;
l_index := l_comma_idx + 1;
l_arr_idx := l_arr_idx + 1;
END LOOP;
for i in 1..myEmailArray.count loop
dbms_output.put_line(myEmailArray(i));
end loop;
dbms_output.put_line('return count ' || myEmailArray.count);
RETURN myEmailArray;
--exception
--when others then
--do something
end ConvertToArray;
Finally Declare a local variable, call the function and loop through what is returned
l_array t_array;
l_Array := ConvertToArray('email1#gmail.com,email2#gmail.com,email3#gmail.com');
for idx in 1 .. l_array.count
loop
l_EmailTo := Trim(replace(l_arrayXX(idx),'"',''));
if nvl(l_EmailTo,'#') = '#' then
dbms_output.put_line('Empty: l_EmailTo:' || to_char(idx) || l_EmailTo);
else
dbms_output.put_line
( 'Email ' || to_char(idx) ||
' of array contains: ' ||
l_EmailTo
);
end if;
end loop;

Resources