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;
/
Related
I am passing ID's in oracle proc. ID's can be in 1000's. currently its able to process around 600 ID's, if I pass more than 600 ID's - I am getting ORA-01460 unimplemented or unreasonable conversion requested. ID is varchar2 datatype, how can I process 1000's of Id's in varchar2 or what will be the best strategy to handle this kind of issue. Any guidance/suggestion will be highly appreciated. Can this be solved using CLOB datatype?
//this is how I am processing Id's
create or replace procedure Emp(
emp_id in varchar2
)
//passing those id's in CTE before passing to subquery
WITH
EMP_LIST AS(
select regexp_substr(emp_id,'[^,]+', 1, level) from dual
connect by level <= LENGTH(regexp_substr(emp_id, '[^,]+'))+1
)
Pass a collection or VARRAY rather than passing a comma-delimited string:
CREATE TYPE number_list IS TABLE OF NUMBER(10,0);
Then you can use it something like:
CREATE PROCEDURE emp(
emp_ids IN number_list
)
IS
BEGIN
-- Do something with the ids like inserting them into a table
INSERT INTO employees ( id )
SELECT COLUMN_VALUE
FROM TABLE( emp_ids );
-- Or something like this:
SELECT something
INTO some_variable -- you need to define this variable first
FROM some_table
WHERE emp_id MEMBER OF emp_ids;
END;
/
Update
If you can't create anything then you can use a built-in collection like SYS.ODCINUMBERLIST:
CREATE PROCEDURE emp(
emp_ids IN SYS.ODCINUMBERLIST
)
IS
BEGIN
-- Do something with the ids like inserting them into a table
INSERT INTO employees ( id )
SELECT COLUMN_VALUE
FROM TABLE( emp_ids );
-- Or something like this:
SELECT something
INTO some_variable -- you need to define this variable first
FROM some_table
WHERE emp_id IN ( SELECT COLUMN_VALUE FROM TABLE( emp_ids ) );
END;
/
(Note: SYS.ODCI*LIST types are VARRAY data types and do not support the MEMBER OF operator like collections do; instead you can get the values from the VARRAY using a nested SELECT statement with a TABLE() collection expression.)
However, if you really can't CREATE anything then you won't be able to CREATE PROCEDURE .... not sure there is any solution to that apart from talking to your DBA.
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.
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.
I have the following table name template, there are a couple with the same name and a number at the end: fmj.backup_semaforo_geo_THENUMBER, for example:
select * from fmj.backup_semaforo_geo_06391442
select * from fmj.backup_semaforo_geo_06398164
...
Lets say I need to select a column from every table which succeeds with the 'fmj.backup_semaforo_geo_%' filter, I tried this:
SELECT calle --This column is from the backup_semaforo_geo_# tables
FROM (SELECT table_name
FROM all_tables
WHERE owner = 'FMJ' AND table_name LIKE 'BACKUP_SEMAFORO_GEO_%');
But I'm getting the all_tables tables name data:
TABLE_NAME
----------
BACKUP_SEMAFORO_GEO_06391442
BACKUP_SEMAFORO_GEO_06398164
...
How can I achieve that without getting the all_tables output?
Thanks.
Presumably your current query is getting ORA-00904: "CALLE": invalid identifier, because the subquery doesn't have a column called CALLE. You can't provide a table name to a query at runtime like that, unfortunately, and have to resort to dynamic SQL.
Something like this will loop through all the tables and for each one will get all the values of CALLE from each one, which you can then loop through. I've used DBMS_OUTPUT to display them, assuming you're doing this in SQL*Plus or something that can deal with that; but you may want to do something else with them.
set serveroutput on
declare
-- declare a local collection type we can use for bulk collect; use any table
-- that has the column, or if there isn't a stable one use the actual data
-- type, varchar2(30) or whatever is appropriate
type t_values is table of table.calle%type;
-- declare an instance of that type
l_values t_values;
-- declare a cursor to generate the dynamic SQL; where this is done is a
-- matter of taste (can use 'open x for select ...', then fetch, etc.)
-- If you run the query on its own you'll see the individual selects from
-- all the tables
cursor c1 is
select table_name,
'select calle from ' || owner ||'.'|| table_name as query
from all_tables
where owner = 'FMJ'
and table_name like 'BACKUP_SEMAFORO_GEO%'
order by table_name;
begin
-- loop around all the dynamic queries from the cursor
for r1 in c1 loop
-- for each one, execute it as dynamic SQL, with a bulk collect into
-- the collection type created above
execute immediate r1.query bulk collect into l_values;
-- loop around all the elements in the collection, and print each one
for i in 1..l_values.count loop
dbms_output.put_line(r1.table_name ||': ' || l_values(i));
end loop;
end loop;
end;
/
May be a dynamic SQL in a PLSQL program;
for a in (SELECT table_name
FROM all_tables
WHERE owner = 'FMJ' AND table_name LIKE 'BACKUP_SEMAFORO_GEO_%')
LOOP
sql_stmt := ' SELECT calle FROM' || a.table_name;
EXECUTE IMMEDIATE sql_stmt;
...
...
END LOOP;
Here is my first procedure (sample)
CREATE OR REPLACE PROCEDURE GPTOWNER_CORP_AMF.testt1
AS
po_status VARCHAR2(100);
po_cur_1 SYS_REFCURSOR;
po_cur_2 SYS_REFCURSOR;
BEGIN
OPEN po_cur_1 FOR
select app_var_row_seq,app_var_name,app_var_value,app_var_description,r_date
from TMP_PMT_APP_VARIABLES_REF
where ROWNUM < 5;
OPEN po_cur_2 FOR
select config_to_lob_row_seq,config_row_seq,lobref_row_seq,r_date
from TMP_PMT_CONFIG_TO_LOB_DAT
where ROWNUM < 6;
TESTT2(po_cur_1,po_cur_2,po_status);
DBMS_output.put_line(po_status);
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE(SQLERRM||SQLCODE);
END;
Here is my second procedure (sample)
CREATE OR REPLACE procedure GPTOWNER_CORP_AMF.testt2 (pi_cur_1 IN sys_refcursor, pi_cur_2 IN sys_refcursor,po_status OUT VARCHAR2)
AS
app_var_row_seq NUMBER;
app_var_name VARCHAR2(100);
app_var_value VARCHAR2(1000);
app_var_description VARCHAR2(1000);
r_date1 DATE;
config_to_lob_row_seq NUMBER;
config_row_seq VARCHAR2(100);
lobref_row_seq NUMBER;
r_date2 DATE;
BEGIN
LOOP
FETCH pi_cur_1 into app_var_row_seq,app_var_name,app_var_value,app_var_description,r_date1;
FETCH pi_cur_2 into config_to_lob_row_seq,config_row_seq,lobref_row_seq,r_date2;
EXIT WHEN (pi_cur_2%NOTFOUND AND pi_cur_1%NOTFOUND ) ;
INSERT INTO testt1testt2 (colid,col1,col2,col3,col4,col5,col6,col7,col8,col9)
VALUES(colid.nextval,app_var_row_seq,app_var_name,app_var_value,app_var_description,r_date1,config_to_lob_row_seq,config_row_seq,lobref_row_seq,r_date2);
END LOOP;
DBMS_OUTPUT.PUT_LINE ('rows inserted:' || pi_cur_1%ROWCOUNT || 'and' || pi_cur_2%ROWCOUNT);
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE(SQLERRM||SQLCODE);
END;
My problem statement is that from first procedure I am getting two refcursor as output and in the second procedure I am trying to read them and put them into a temp table which will be used by another procedure. Cant union the two select statements as they are having different set of output. Is there any better mechanism to do so , as by my approach I am facing issue as when I run the first procedure (say first select return 4 row and second select return 6 rows) the need is that 6 rows would be inserted into temp table but the columns that are read from first select will be inserted as NULL when there is now row fetched , but in my case duplicate row is getting inserted. Any help would be appreciated. And do post if anyone needs more info on the same.
If I understand you right, then you don't really need to union them - but join them.
Since there is no really relation between the 2 tables and you want nulls in "both side"s you need to full outer join them.
I will not ask you, why you want them both on the same temp table if there is no relation between them. But if you do this why not just use an insert-select ?
INSERT INTO testt1testt2 (colid,col1,col2,col3,col4,col5,col6,col7,col8,col9)
SELECT colid.nextval, app_var_row_seq,app_var_name,app_var_value,app_var_description, t1.r_date,
config_to_lob_row_seq,config_row_seq,lobref_row_seq, t2.r_date
FROM (select app_var_row_seq,app_var_name,app_var_value,app_var_description,r_date
from TMP_PMT_APP_VARIABLES_REF
where ROWNUM < 5) t1
FULL OUTER JOIN (select config_to_lob_row_seq,config_row_seq,lobref_row_seq,r_date
from TMP_PMT_CONFIG_TO_LOB_DAT
where ROWNUM < 6) t2 on 1=2
UPDATE:
If the requirement is to get 2 refcursors, then my approach isn't relevant...
What you can do though, is have 2 insert commands one like this:
INSERT INTO testt1testt2 (colid,col1,col2,col3,col4,col5,col6,col7,col8,col9)
VALUES (colid.nextval,app_var_row_seq,app_var_name,app_var_value,app_var_description,r_date1,null,null,null,null);
and the other like:
INSERT INTO testt1testt2 (colid,col1,col2,col3,col4,col5,col6,col7,col8,col9)
VALUES (colid.nextval,null,null,null,null,null,config_to_lob_row_seq,config_row_seq,lobref_row_seq,r_date2);
If you really want to do it nicely, you can use bulk insert for performance, see example here