I'm trying to add a few consecutive numbers to the same index in an associative array in PL/SQL (Oracle).
I have defined the associative array as follows:
TYPE map_varchar IS TABLE OF NUMBER(30) INDEX BY VARCHAR2(30);
l map_varchar;
I have an XML clob which I loop through and get a description(varchar) and amount(number) values. Say I have a description: 'A' and the following value 3,5,6
I want to map this as: 'A' > 14
for r in (SELECT t.*
FROM XMLTABLE('*'
PASSING xmltype(rec1.xml).
EXTRACT('/ProductS/')
COLUMNS description VARCHAR2(30) PATH 'some_xml_tag',
amount NUMBER(30) PATH '_another_xml_tag') t)
LOOP
l(r.description) := l(r.description) + r.amount;
dbms_output.put_line('v_modifier_value_p: ' || r.description || ' amount: ' || r.saving);
END LOOP;
I'm trying the above but the line:
l(r.description) := l(r.description) + r.amount;
is not working.
How can this be done in PLSQL?
l(r.description) apparently needs to be initialized. So I added something like this:
if l.exists(r.description) then
null;
else
l(r.description) := 0;
end if;
this works now.
replace the statement
l(r.description) := l(r.description) + r.amount;
with the following
if l.exists(r.description) then
l(r.description) := l(r.description) + r.amount;
else
l(r.description) := r.amount;
end if;
Related
create or replace function lstnation (listdisplay in varchar2)
return varchar2 is
nName varchar2 (1000) default null;
listD varchar2(1000) default null;
cursor display_nation
is
select nation.n_name
from nation
inner join region
on region.r_regionkey = nation.n_nationkey
where region.r_regionname = listdisplay;
BEGIN
open display_nation;
loop
fetch display_nation into nName;
exit when display_nation%notfound;
IF
listD := listD || RTRIM(nName)||' , ';
end loop;
close display_nation;
return listD;
end lstnation;
/
DECLARE
rKey region.r_regionkey%type;
rName region.r_name%type;
nList varchar2(1000);
cursor outer_block is
select region.r_regionkey, region.r_name, lstnation(region.r_name)
from region;
BEGIN
open outer_block;
loop
fetch outer_block into rKey, rName, nList;
exit when outer_block%notfound;
dbms.output.put_line(rkey || ' ' || RTRIM(rName) || ': '|| nList);
end loop;
close outer_block;
end;
/
I get two errors, how can I fix it
LINE/COL ERROR
19/12 PLS-00103: Encountered the symbol "=" when expecting one of the
following:
. ( * # % & = - + < / > at in is mod remainder not rem then
<an exponent (**)> <> or != or ~= >= <= <> and or like like2
like4 likec between || multiset member submultiset
20/2 PLS-00103: Encountered the symbol "END" when expecting one of the
following:
begin function pragma procedure subtype type
current cursor delete
exists prior
You can save some coding and efficiency by replacing the cursor loop with the listagg function
select listagg(rtrim(nation.n_name),',')
from nation
inner join region
on region.r_regionkey = nation.n_nationkey
where region.r_regionname = listdisplay;
So that will collate all the matching rows, and use whatever delimiter is passed in. One thing to be aware of, you have listD varchar2(1000) so as long as the results from the query are less than 1000, you are OK. If you expect a larger result set, you may need to increase or use a clob.
If for some reason, you still want to use the loop method, then you need to fix your IF statement:
loop
fetch display_nation into nName;
exit when display_nation%notfound;
IF <condition> THEN
listD := listD || RTRIM(nName)||' , ';
END IF;
end loop;
so I am trying to write to an array in PL/SQL, and I always get the subscript outside of limit error. I've seen similar posts and implemented everything based on those answers, I can't seem to find what I'm doing wrong. The line giving the error is "arr_quartosLivres(counter) := q.id;" I've tried to extend the array and it still doesn't work, however, either way, the look only runs 21 times (because there are only 21 values in the table quarto) so it shouldn't even need to be extended. Any help would be highly appreciated! Thank you
SET SERVEROUTPUT ON;
DECLARE
p_idReserva reserva.id%type := 408;
v_dataEntradaReserva reserva.data_entrada%type;
counter integer := 0;
type arr_aux IS varray(21) of quarto.id%type;
arr_quartosLivres arr_aux := arr_aux();
BEGIN
SELECT data_entrada INTO v_dataEntradaReserva FROM reserva WHERE id = p_idreserva;
FOR q IN (SELECT * FROM quarto)
LOOP
BEGIN
IF isQuartoIndisponivel(q.id, v_dataEntradaReserva)
THEN DBMS_OUTPUT.PUT_LINE('nao disponivel' || counter);
arr_quartosLivres(counter) := q.id;
ELSE DBMS_OUTPUT.PUT_LINE('disponivel' || counter);
END IF;
counter := counter + 1;
END;
END LOOP;
END;
The index values for varray begin with 1. Your logic is trying to use index value 0. Thus index out of range. BTW extend does not apply to varray, when declared a varray has a fixed size. You have 3 solutions: initialize counter to 1 instead of 0, or move incrementing it prior to its use as an index. Since as it stands you increment every time through the loop, even when the IF condition returns false and you do not use the counter as an index, leaving a NULL value in the array.But you use counter for 2 different purposes: Counting rows processed and index into the array. Since the row value may not be put into the array then your 3rd option is to introduce another variable for the index. Further there is no need for the BEGIN ... End block in the loop.
declare
p_idreserva reserva.id%type := 408;
v_dataentradareserva reserva.data_entrada%type;
counter integer := 0;
type arr_aux is varray(21) of quarto.id%type;
arr_quartoslivres arr_aux := arr_aux();
varray_index integer := 1 ; -- index varaibal for varray.
begin
select data_entrada into v_dataentradareserva from reserva where id = p_idreserva;
for q in (select * from quarto)
loop
if isquartoindisponivel(q.id, v_dataentradareserva)
then
dbms_output.put_line('nao disponivel' || counter || ' at index ' || varray_index);
arr_quartoslivres(varray_index) := q.id;
varray_index := varray_index + 1;
else
dbms_output.put_line('disponivel' || counter);
end if;
counter := counter + 1;
end loop;
end;
For example i have an array like
"a(1):=1 ,a(2):=2, a(3) := 3"
and now my array count =3 "(a.count)"
then i delete middle member "a.delete(2)" then i wanna make my array like this "a(1):=1;a(2):=3" and my array count = 2 ("a.count") how can i do this ?
ps:i need to this with big sized array so i think i should use, for or while loop but how...
The collection where you have deleted some element is called sparse collection. Below you have example how to iterate that type of collection and how to use it with forall.
declare
type a is table of number;
ar a;
v_idx number;
begin
select level bulk collect into ar from dual connect by level< 1000;
ar.delete(1);
ar.delete(4);
ar.delete(10);
ar.delete(88);
v_idx := ar.first;
while v_idx is not null loop
dbms_output.put_line('idx: '||v_idx ||' value:'|| ar(v_idx));
v_idx := ar.next(v_idx);
end loop;
-- FORALL i IN INDICES OF ar
-- INSERT INTO test_table VALUES ar(i);
end;
Thank you but i should change array too , i need to take same output when i print array members like
for i in ar.first..ar.last loop
dbms_output.put_line(ar(i));
end loop;
declare
type a is table of number;
ar a;
begin
select level bulk collect into ar from dual connect by level< 1000;
ar.delete(1);
ar.delete(4);
ar.delete(10);
ar.delete(88);
-- ar is sparse collection;
ar := ar MULTISET intersect ar;
-- ar is dense collection and for i in .... is possible
FOR i IN ar.first .. ar.last LOOP
DBMS_OUTPUT.put_line(ar(i));
END LOOP;
end;
you can try this approach assign values of first spared collection to second continues collection and use second collection for further processing...
declare
type num_arr is table of number;
v_num_arr1 num_arr; --first collection
v_num_arr2 num_arr := num_arr(); -- second collection initialization and declaration
v_idx number;
v_col_index number := 1;
begin
-- fill 10 element.
select level || '1' as num1 bulk collect into v_num_arr1 from dual connect by level < 10;
for x in v_num_arr1.first .. v_num_arr1.last loop
dbms_output.put_line('index: ' || x || ' value: ' || v_num_arr1(x));
end loop;
dbms_output.put_line('');
-- delete element
v_num_arr1.delete(3);
v_num_arr1.delete(7);
v_idx := v_num_arr1.first;
while v_idx is not null loop
dbms_output.put_line('index: ' || v_idx || ' value: ' || v_num_arr1(v_idx));
-- filling second collection with regular index by variable v_col_index
if v_num_arr1(v_idx) is not null then
v_num_arr2.extend(1);
v_num_arr2(v_col_index) := v_num_arr1(v_idx);
v_col_index := v_col_index + 1;
end if;
v_idx := v_num_arr1.next(v_idx);
end loop;
dbms_output.put_line('second collection elements
');
--check second colleciton
for x in v_num_arr2.first .. v_num_arr2.last loop
dbms_output.put_line('index: ' || x || ' value: ' || v_num_arr2(x));
end loop;
end;
I have a program to generate dynamic query string based on input. This query may select from any tables or joined tables in my DB, and the column names and number of columns are unknown.
Now with this query string as the only input, I want to fetch all data from the result and output them line by line, is there any way to do this ?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Thank Thinkjet for the reference. I have solved the problem, to help the others, here is the piece of code I used:
DECLARE
v_curid NUMBER;
v_desctab DBMS_SQL.DESC_TAB;
v_colcnt NUMBER;
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;
p_sql_stmt :='SELECT * FROM emp';
DBMS_SQL.PARSE(v_curid, p_sql_stmt, 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
IF (v_desctab(i).col_type = 1) THEN
DBMS_SQL.COLUMN_VALUE(v_curid, i, v_name_var);
ELSIF (v_desctab(i).col_type = 2) THEN
DBMS_SQL.COLUMN_VALUE(v_curid, i, v_num_var);
ELSIF (v_desctab(i).col_type = 12) THEN
DBMS_SQL.COLUMN_VALUE(v_curid, i, v_date_var);
END IF;
END LOOP;
END LOOP;
DBMS_SQL.CLOSE_CURSOR(v_curid);
END;
/
You can do that with DBMS_SQL package.
Update
To get more detailed reference about DBMS_SQL go here.
If you are building your string within PL/SQL, you can run it with EXECUTE IMMEDIATE. <- link. Use the BULK COLLECT INTO and output the collection.
<PRE>
DECLARE
RUN_S CLOB;
IGNORE NUMBER;
SOURCE_CURSOR NUMBER;
PWFIELD_COUNT NUMBER DEFAULT 0;
L_DESCTBL DBMS_SQL.DESC_TAB2;
Z_NUMBER NUMBER;
BEGIN
RUN_S := ' SELECT 1 AS VAL1,
2 AS VAL2,
CURSOR (SELECT 11 AS VAL11,
12 AS VAL12
FROM DUAL) AS CUR1,
CURSOR (SELECT 11 AS VAL11,
12 AS VAL12
FROM DUAL) AS CUR2
FROM DUAL';
SOURCE_CURSOR := DBMS_SQL.OPEN_CURSOR;
DBMS_SQL.PARSE(SOURCE_CURSOR, RUN_S, DBMS_SQL.NATIVE);
DBMS_SQL.DESCRIBE_COLUMNS2(SOURCE_CURSOR, PWFIELD_COUNT, L_DESCTBL); -- get record structure
FOR I IN 1 .. PWFIELD_COUNT LOOP
DBMS_OUTPUT.PUT_LINE('Col ' || I || ' Type:' || L_DESCTBL(I).COL_TYPE);
IF L_DESCTBL(I).COL_TYPE = 2 THEN
DBMS_SQL.DEFINE_COLUMN(SOURCE_CURSOR, I, Z_NUMBER);
END IF;
NULL;
END LOOP;
IGNORE := DBMS_SQL.EXECUTE(SOURCE_CURSOR);
LOOP
IF DBMS_SQL.FETCH_ROWS(SOURCE_CURSOR) > 0 THEN
FOR I IN 1 .. PWFIELD_COUNT LOOP
IF L_DESCTBL(I).COL_TYPE IN (2) THEN
DBMS_SQL.COLUMN_VALUE(SOURCE_CURSOR, I, Z_NUMBER);
DBMS_OUTPUT.PUT_LINE('Col ' || I || ' Value:' || Z_NUMBER);
END IF;
END LOOP;
ELSE
EXIT;
END IF;
END LOOP;
END;
</PRE>
I have SQL strings that my users write. They look like:
SELECT Name, Age from Users WHERE Name LIKE '%a%' AND {UsersWhere}
On the oracle server side when such an SQL is to be executed I want to replace the {tags} first. The replacements for the {tags} will be valid SQL sub strings I am holding in a table. Pre-manufactered sub sqls. So the treated string will be valid SQL.
Is there some fancy build-in Oracle function to make this happen?
Thanks for a hint!
I have written a small function for anyone interested:
CREATE OR REPLACE FUNCTION SA.REPLACE_VARIABLES (p_sql IN VARCHAR2)
RETURN VARCHAR2
IS
vs_return VARCHAR2 (4000);
-- Deklarationen
vs_sql VARCHAR2(4000);
vs_substring VARCHAR2(4000);
vs_variable VARCHAR2(200);
vs_variable_content VARCHAR2(4000);
BEGIN
vs_sql := p_sql;
IF INSTR(p_sql, '{') > 0 THEN
vs_substring := vs_sql;
WHILE LENGTH(vs_substring) > 0 LOOP
IF INSTR(vs_substring, '{') <> 0
THEN
vs_variable := SUBSTR(vs_substring, INSTR(vs_substring, '{'), INSTR(vs_substring, '}') - INSTR(vs_substring, '{') + 1);
-- Do whatever you want with the variable
--vs_sql := REPLACE(vs_sql, vs_variable, vs_variable_content);
-- Substring verkürzen
vs_substring := SUBSTR(vs_substring, INSTR(vs_substring, vs_variable) + LENGTH(vs_variable) + 1);
ELSE
vs_substring := '';
END IF;
END LOOP;
END IF;
RETURN vs_sql;
EXCEPTION
WHEN OTHERS
THEN
-- Err -handle
END REPLACE_VARIABLES;
/
I'd just keep it simple:
v_sql := REPLACE(v_sql, '{UsersWhere}', '...whatever you need...');