Pl/Sql How to change collection element's without using loop - sql

I want to change a collection's elements indexes without using any loop. How can i do that?
declare
type NumberList is table of pls_integer;
nlist NumberList := NumberList(1,2,3,4,5,6,7,8,9,10);
n_first pls_integer := 1;
n_last pls_integer := 10;
I want to make nlist like (10,9,8,7,6,5,4,3,2,1). Thanks in advance.

you can order desc the elements in a select and collect the result into a new list
select * bulk collect into nlist2 from (
select column_value from table(nlist) order by 1 desc
);
your nested table should be declared as a global type
CREATE or replace TYPE NumberList IS TABLE OF pls_integer;
than you can use it
declare
nlist NumberList := NumberList(1,2,3,4,5,6,7,8,9,10);
n_first pls_integer := 1;
n_last pls_integer := 10;
nlist2 NumberList
begin
select * bulk collect into nlist2 from (
select column_value from table(nlist) order by 1 desc
);
end;

Nested tables are not ordered, so actually your request does not make much sense.
If you like to get the elements in a certain order then use
select column_value from table(nlist) order by 1 desc
Note, you can store such arrays in tables but when you select those elements again then the order of elements can be arbitrary, see Nested Tables:
In the database, a nested table is a column type that stores an
unspecified number of rows in no particular order.
When you retrieve a nested table value from the database into a PL/SQL
nested table variable, PL/SQL gives the rows consecutive indexes,
starting at 1. Using these indexes, you can access the individual rows
of the nested table variable. The syntax is variable_name(index). The
indexes and row order of a nested table might not remain stable as you
store and retrieve the nested table from the database.

Related

Select from table name defined in a loop

I have 2 database connections: a and b where they both share the same schema but not necessarily the same data. I need to check both database's total number of rows for each table using a loop and compare their counts.
Using a nested table collection, my attempt so far:
DECLARE
TYPE nt_rows IS TABLE OF NUMBER;
nt_rows1 nt_num := nt_num();
nt_rows2 nt_num := nt_num();
counter INT := 0;
CURSOR c_tables1 IS
SELECT TABLE_NAME
FROM ALL_TABLES
WHERE OWNER IN ('a')
ORDER BY TABLE_NAME ASC;
CURSOR c_tables2 IS
SELECT TABLE_NAME
FROM ALL_TABLES
WHERE OWNER IN ('b')
ORDER BY TABLE_NAME ASC;
BEGIN block: loop through each cursor and add them to each array
BEGIN
FOR i IN c_tables1 LOOP
nt_rows1.extend;
SELECT COUNT(*) FROM a.i.TABLE_NAME INTO nt_rows1(counter);
END LOOP;
The last line does not work where 'a' is the database connection name and i is index of each table_name.
I also tried:
nt_rows1(counter) := SELECT COUNT(*) FROM a.i.TABLE_NAME;
and it also doesn't work, any suggestions are appreciated.
If you want to query data from a table whose name is in a string (for example a local variable or a cursor field) and hence not known at compile time, you will need to use dynamic SQL.
Try replacing the line
SELECT COUNT(*) FROM a.i.TABLE_NAME INTO nt_rows1(counter);
with
EXECUTE IMMEDIATE 'SELECT COUNT(*) FROM a.' || i.TABLE_NAME INTO nt_rows1(counter);
Note also that your code is not incrementing the counter variable. Since you are starting with counter set to 0, and 0 isn't a valid index into a nested table, I would recommend adding a line to increment counter before the line above.

PL/SQL Statement Ignored. ORA-22905: cannot access rows from a non-nested table item

I tried to select values from the nested table and bulk collecting into an associative array collection. When I try to bulk collect oracle throwing the above exception(PL/SQL: SQL Statement ignored
PL/SQL: ORA-22905: cannot access rows from a non-nested table
item) though I fetch the data from the nested table.
It is not happening in all the cases. When the same package compiled in the different client database, Some case it is not throwing an error and in some environment, it is throwing an error. Can you please help what was the exact issue.
I have not attached the entire package. Instead provided the case where the issue occurs.
DECLARE
TYPE rc_rec_multiset IS record (
rc_id NUMBER,
doc_num VARCHAR2(100)
);
TYPE rc_type IS TABLE OF rc_rec_multiset;
TYPE tab_rec_type_multiset IS
TABLE OF rc_rec_multiset INDEX BY pls_integer;
rc_tab_type rc_type := rc_type() ;
rc_tab_type_dist_rc tab_rec_type_multiset;
BEGIN
SELECT DISTINCT rc_id,
doc_num BULK COLLECT
INTO rc_tab_type_dist_rc
FROM TABLE(rc_tab_type);
END;
You cannot do this using SQL; an associative array is a PL/SQL data type and cannot be used in the SQL scope. In a similar vein, collections defined in the PL/SQL scope cannot be used in SQL scope (in 11g and earlier) - you either need to define collections in the SQL scope (Note - you cannot do this for associative arrays as they are purely PL/SQL) or just use PL/SQL.
Assuming the rc_tab_type collection is not sparse then you can use pure PL/SQL like this:
DECLARE
TYPE rc_rec_multiset IS record (
rc_id NUMBER,
doc_num VARCHAR2(100)
);
TYPE rc_type IS TABLE OF rc_rec_multiset;
TYPE tab_rec_type_multiset IS
TABLE OF rc_rec_multiset INDEX BY pls_integer;
rc_tab_type rc_type := rc_type();
rc_tab_type_dist_rc tab_rec_type_multiset;
BEGIN
/*
* Populate rc_tab_type here.
*/
FOR i IN 1 .. rc_tab_type.COUNT LOOP
rc_tab_type_dist_rc( rc_tab_type(i).rc_id ) := rc_tab_type(i).doc_num;
END LOOP;
END;
If it is sparse then, instead of the FOR loop, you will have to use:
i := rc_tab_type.FIRST;
WHILE i IS NOT NULL LOOP
rc_tab_type_dist_rc( rc_tab_type(i).rc_id ) := rc_tab_type(i).doc_num;
i := rc_tab_type.NEXT(i);
END LOOP;

Reg_exp in stored procedure needs "into"

I have this statement in an Oracle stored procedure
select regexp_substr('hello,world', '[^(,|;|\s|&)]+', 1, level)
from dual
connect by regexp_substr('hello,world', '[^(,|;|\s|&)]+', 1, level) is not null;
However, the compiler complains that I need a "into" clause. I tried (and knew before I did) and it didn't work, because reg_exp is returning multiple values.
Can anyone help? Thanks!
you may use cursors for multiple-row returns :
SQL> set serveroutput on;
SQL> declare
v_abc varchar2(1500);
begin
for c in ( select regexp_substr('hello,world', '[^(,|;|\s|&)]+', 1, level) abc
from dual
connect by regexp_substr('hello,world', '[^(,|;|\s|&)]+', 1, level) is not null )
loop
v_abc := c.abc;
dbms_output.put_line(v_abc);
end loop;
end;
You have to use collections to store multi-row results.
Excerpt from the documentation:
The following example demonstrates using the SELECT INTO statement to query entire rows into a PL/SQL collection of records:
DECLARE
TYPE first_typ IS TABLE OF employees.first_name%TYPE INDEX BY PLS_INTEGER;
TYPE last_typ IS TABLE OF employees.first_name%TYPE INDEX BY PLS_INTEGER;
first_names first_typ;
last_names last_typ;
CURSOR c1 IS SELECT first_name, last_name FROM employees;
TYPE name_typ IS TABLE OF c1%ROWTYPE INDEX BY PLS_INTEGER;
all_names name_typ;
TYPE emp_typ IS TABLE OF employees%ROWTYPE INDEX BY PLS_INTEGER;
all_employees emp_typ;
BEGIN
-- Query multiple columns from multiple rows, and store them in a collection
-- of records.
SELECT first_name, last_name BULK COLLECT INTO all_names FROM EMPLOYEES;
-- Query multiple columns from multiple rows, and store them in separate
-- collections. (Generally less useful than a single collection of records.)
SELECT first_name, last_name
BULK COLLECT INTO first_names, last_names
FROM EMPLOYEES;
-- Query an entire (small!) table and store the rows
-- in a collection of records. Now you can manipulate the data
-- in-memory without any more I/O.
SELECT * BULK COLLECT INTO all_employees FROM employees;
END;
/

How to store multiple rows in a variable in pl/sql function?

I'm writing a pl/sql function. I need to select multiple rows from select statement:
SELECT pel.ceid
FROM pa_exception_list pel
WHERE trunc(pel.creation_date) >= trunc(SYSDATE-7)
if i use:
SELECT pel.ceid
INTO v_ceid
it only stores one value, but i need to store all values that this select returns. Given that this is a function i can't just use simple select because i get error, "INTO - is expected."
You can use a record type to do that. The below example should work for you
DECLARE
TYPE v_array_type IS VARRAY (10) OF NUMBER;
var v_array_type;
BEGIN
SELECT x
BULK COLLECT INTO
var
FROM (
SELECT 1 x
FROM dual
UNION
SELECT 2 x
FROM dual
UNION
SELECT 3 x
FROM dual
);
FOR I IN 1..3 LOOP
dbms_output.put_line(var(I));
END LOOP;
END;
So in your case, it would be something like
select pel.ceid
BULK COLLECT INTO <variable which you create>
from pa_exception_list
where trunc(pel.creation_Date) >= trunc(sysdate-7);
If you really need to store multiple rows, check BULK COLLECT INTO statement and examples. But maybe FOR cursor LOOP and row-by-row processing would be better decision.
You may store all in a rowtype parameter and show whichever column you want to show( assuming ceid is your primary key column, col1 & 2 are some other columns of your table ) :
SQL> set serveroutput on;
SQL> declare
l_exp pa_exception_list%rowtype;
begin
for c in ( select *
from pa_exception_list pel
where trunc(pel.creation_date) >= trunc(SYSDATE-7)
) -- to select multiple rows
loop
select *
into l_exp
from pa_exception_list
where ceid = c.ceid; -- to render only one row( ceid is primary key )
dbms_output.put_line(l_exp.ceid||' - '||l_exp.col1||' - '||l_exp.col2); -- to show the results
end loop;
end;
/
SET SERVEROUTPUT ON
BEGIN
FOR rec IN (
--an implicit cursor is created here
SELECT pel.ceid AS ceid
FROM pa_exception_list pel
WHERE trunc(pel.creation_date) >= trunc(SYSDATE-7)
)
LOOP
dbms_output.put_line(rec.ceid);
END LOOP;
END;
/
Notes from here:
In this case, the cursor FOR LOOP declares, opens, fetches from, and
closes an implicit cursor. However, the implicit cursor is internal;
therefore, you cannot reference it.
Note that Oracle Database automatically optimizes a cursor FOR LOOP to
work similarly to a BULK COLLECT query. Although your code looks as if
it fetched one row at a time, Oracle Database fetches multiple rows at
a time and allows you to process each row individually.

Oracle procedure, placement of multiple records in variables

I'm trying to create my first oracle procedure. The select will return multiple records; I need to be able to place each record in the variables and use the record in later actions in the procedure. Any help please?
key number;
keyCount number;
rub varchar2(50);
srub varchar2(100);
type varchar2(200);
date varchar2(14);
note varchar2(500);
BEGIN
SELECT KEY,COUNT(KEY),RUB,
SRUB,TYPE ,DATE,NOTE FROM Student
WHERE S_KEY = {key};
END;
In PL/SQL we need to select results into matching variables. One way is separate variables for each column (as shown). The alternative is to use a row variable which matches the project of the query; find out more.
You've got an aggregating function - COUNT() so you need a GROUP BY clause which defines the non-aggregating columns. You say you have more than one record so you need to populate a collection not scalar variables. Find out more.
Your procedure should look something like this
create or replace procedure my_first_proc
( p_key in student.s_key%type )
as
type my_rec is record (
key number ,
keyCount number ,
rub varchar2(50); ,
srub varchar2(100) ,
type varchar2(200) ,
date varchar2(14),
note varchar2(500)
);
type my_rec_coll is table of my_rec;
l_student_recs my_rec_coll;
BEGIN
SELECT KEY,COUNT(KEY),RUB,SRUB,TYPE ,DATE,NOTE
bulk collect into l_student_recs
FROM Student
WHERE S_KEY = p_key
group by KEY,RUB,SRUB,TYPE ,DATE,NOTE
;
for idx in l_student_recs.first() .. l_student_recs.last()
loop
-- do some processing here
dbms_output.put_line('RUB = '||l_student_recs(idx).rub);
end loop;
EXCEPTION
when no_data_found then
raise_application_error(-01403, 'no student records for key='||p_key);
END;
Get into good habits:
use sensible variable names
distinguish parameter names from local variables
handle predictable exceptions