I have the following code snippet to insert rows into t1 table, however when I execute the procedure no rows are getting populated in t1 table.
CREATE OR REPLACE PROCEDURE my_proc
IS
TYPE rt_t1 IS TABLE OF t1%ROWTYPE
INDEX BY BINARY_INTEGER;
vrt_t1 rt_t1;
TYPE t_emp_no_list IS TABLE OF t1.emp_no%TYPE
INDEX BY BINARY_INTEGER;
TYPE t_emp_name_list IS TABLE OF t1.emp_name%TYPE
INDEX BY BINARY_INTEGER;
TYPE t_loc_name_list IS TABLE OF t1.loc_name%TYPE
INDEX BY BINARY_INTEGER;
TYPE t_hire_date_list IS TABLE OF t1.hire_date%TYPE
INDEX BY BINARY_INTEGER;
l_emp_no t_emp_no_list;
l_emp_name t_emp_name_list;
l_loc_name t_loc_name_list;
l_hire_date t_hire_date_list;
BEGIN
SELECT empno,
ename,
loc,
hiredate
BULK COLLECT INTO l_emp_no,
l_emp_name,
l_loc_name,
l_hire_date
FROM (SELECT empno,
ename,
dept.loc,
emp.hiredate
FROM emp JOIN dept ON emp.deptno = dept.deptno);
FORALL i IN vrt_t1.FIRST .. vrt_t1.LAST
INSERT INTO t1 (emp_no,
emp_name,
loc_name,
hire_date)
VALUES (l_emp_no (i),
l_emp_name (i),
l_loc_name (i),
l_hire_date (i));
COMMIT;
EXCEPTION
WHEN OTHERS
THEN
RAISE;
END;
You should use cursor to achieve your requirement. See below:
CREATE OR REPLACE PROCEDURE my_proc
IS
TYPE rt_t1 IS TABLE OF t1%ROWTYPE
INDEX BY BINARY_INTEGER;
vrt_t1 rt_t1;
cursor cur is
SELECT empno,
ename,
dept.loc,
emp.hiredate
FROM emp JOIN dept ON emp.deptno = dept.deptno;
BEGIN
OPEN cur;
fetch cur BULK COLLECT INTO vrt_t1;
close cur;
FORALL i IN 1 .. vrt_t1.count
INSERT INTO t1
VALUES vrt_t1(i);
COMMIT;
EXCEPTION
WHEN OTHERS
THEN
RAISE;
END;
PS: the above code will work if the number of columns of both the tables are same;
EDIT..In case you are inserting specific rows to a table then you need to create a RECORD to do so. See below example. Your code is failing coz you are trying to inserting multiple collection in one go. In your case first colelction would be an insert and others should be an update.
CREATE TABLE t1
(
emp_no NUMBER,
emp_name VARCHAR2 (30),
loc_name VARCHAR2 (30),
hire_date DATE
);
--------------
SQL> select * from t1;
no rows selected
CREATE OR REPLACE PROCEDURE my_proc
IS
TYPE TBL IS RECORD
(
emp_no number,
emp_name varchar2(100),
loc_name varchar2(100),
hire_date date
);
TYPE t_emp IS TABLE OF TBL INDEX BY PLS_INTEGER;
var_emp_det t_emp;
BEGIN
SELECT ENO,
ENAME,
JOB,
HIREDATE
BULK COLLECT INTO var_emp_det
from emp_sal;
FORALL i IN 1..var_emp_det.count
INSERT INTO t1 (emp_no,
emp_name,
loc_name,
hire_date)
values (var_emp_det(i).emp_no,
var_emp_det(i).emp_name,
var_emp_det(i).loc_name,
var_emp_det(i).hire_date);
COMMIT;
EXCEPTION
WHEN OTHERS
THEN
RAISE;
END;
Output:
SQL> execute my_proc;
PL/SQL procedure successfully completed.
SQL> select * from t1;
EMP_NO EMP_NAME LOC_NAME
---------- ------------------------------ ------------------------------
HIRE_DATE
---------
2 Thomas IT
03-JAN-14
Related
I can define a one-column table and bulk collect into it. i.e:
create type table_of_strings as table of varchar2(200);
DECLARE
l_tab table_of_strings;
BEGIN
select emp_name bulk collect into l_tab from emp;
END;
But how do I collect into multi-column tables? Say:
create type emp_row as object (emp_name varchar2(200), emp_salary Number);
create type emp_table as table of emp_row ;
DECLARE
l_tab emp_table ;
BEGIN
-- I have tried things like this but would fail:
select (emp_name, emp_salary) bulk collect into l_tab from emp;
select emp_name, emp_salary bulk collect into l_tab from emp;
select * bulk collect into l_tab from (
select emp_name, emp_salary from emp);
END;
Thank you in advance!
Peter
Like this:
select emp_row(emp_name, emp_salary) bulk collect into l_tab from emp;
Here's a slightly modified example based on Scott's schema.
SQL> CREATE TYPE emp_row AS OBJECT (emp_name VARCHAR2 (200), emp_salary NUMBER);
2 /
Type created.
SQL> CREATE TYPE emp_table AS TABLE OF emp_row;
2 /
Type created.
SQL> SET SERVEROUTPUT ON
PL/SQL code:
SQL> DECLARE
2 l_tab emp_table;
3 l_max_sal NUMBER;
4 BEGIN
5 SELECT emp_row (ename, sal)
6 BULK COLLECT INTO l_tab
7 FROM emp
8 WHERE deptno = 10;
9
10 FOR i IN l_tab.FIRST .. l_tab.LAST
11 LOOP
12 DBMS_OUTPUT.put_line (
13 l_tab (i).emp_name || ' - ' || l_tab (i).emp_salary);
14 END LOOP;
15
16 SELECT MAX (emp_salary) INTO l_max_sal FROM TABLE (l_tab);
17
18 DBMS_OUTPUT.put_line ('Max salary = ' || l_max_sal);
19 END;
20 /
CLARK - 2450
KING - 5000
MILLER - 1300
Max salary = 5000
PL/SQL procedure successfully completed.
SQL>
Let's say that I have a "Rules" table that has a column that contains oracle queries in a varchar2 column:
Row
Query
1
select 'Hardcoded_Tablename_1' tablename, request_id from table_1 where status >= 4 and resolve_date < *some date math goes here*
2
select 'Table_2' tablename, t2.request_id from table_2 t2 join table_1 t1 on t1.request_id = t2.parent_id where t1.status >= 4 and t1.resolve_date < *some date math goes here*
If this were never going to change, I'd just make a view with a union of these queries.
Our requirement is that we be able to add to or to modify these rules on-the-fly at the whims of leadership.
So, what I need is either:
a very smart view (I think impossible) that executes and unions all of these stored query strings
or
a table function that returns the results of the union of these stored query strings. (I think this is the more likely solution)
It will only ever be those two columns: The hardcoded name of the table and the ID of the record.
Can someone help get me started on this?
Thanks
You can use a PIPELINED function.
First create the types:
CREATE TYPE request_data IS OBJECT (tablename VARCHAR2(30), request_id NUMBER);
CREATE TYPE request_list IS TABLE OF request_data;
Then the function:
CREATE FUNCTION get_requests RETURN request_list PIPELINED
IS
BEGIN
FOR r IN (SELECT "QUERY" FROM table_name ORDER BY "ROW")
LOOP
DECLARE
c_cursor SYS_REFCURSOR;
v_tablename VARCHAR2(30);
v_request_id NUMBER;
BEGIN
OPEN c_cursor FOR r."QUERY";
LOOP
FETCH c_cursor INTO v_tablename, v_request_id;
EXIT WHEN c_cursor%NOTFOUND;
PIPE ROW (request_data(v_tablename, v_request_id));
END LOOP;
CLOSE c_cursor;
EXCEPTION
WHEN NO_DATA_NEEDED THEN
CLOSE c_cursor;
RETURN;
END;
END LOOP;
END;
/
Then, if you have the sample data:
CREATE TABLE table_name ("ROW", "QUERY") AS
SELECT 1, q'[select 'Hardcoded_Tablename_1' tablename, request_id from table_1 where status >= 4 and resolve_date < SYSDATE]' FROM DUAL UNION ALL
SELECT 2, q'[select 'Table_2' tablename, t2.request_id from table_2 t2 join table_1 t1 on t1.request_id = t2.parent_id where t1.status >= 4 and t1.resolve_date < SYSDATE]' FROM DUAL
CREATE TABLE table_1 (request_id, status, resolve_date) AS
SELECT 42, 4, SYSDATE - 1 FROM DUAL;
CREATE TABLE table_2 (request_id, parent_id) AS
SELECT 57, 42 FROM DUAL;
Then you can use the function in a table collection expression:
SELECT *
FROM TABLE(get_requests());
Which outputs:
TABLENAME
REQUEST_ID
Hardcoded_Tablename_1
42
Table_2
57
db<>fiddle here
One option might be a function that returns refcursor.
SQL> select * from rules;
CROW QUERY
---------- ----------------------------------------------------------------------------------------------------
1 select 'EMP' tablename, empno from emp where hiredate = (select max(hiredate) from emp)
2 select 'DEPT' tablename, d.deptno from emp e join dept d on d.deptno = e.deptno where e.hiredate = (
select min(hiredate) from emp)
Function creates union of all queries from the rules table and uses it as a source for the refcursor:
SQL> create or replace function f_test return sys_refcursor
2 is
3 l_rc sys_refcursor;
4 l_str clob;
5 begin
6 for cur_r in (select query from rules order by crow) loop
7 l_str := l_str || cur_r.query ||' union all ';
8 end loop;
9 l_str := rtrim(l_str, ' union all ');
10
11 open l_rc for l_str;
12 return l_rc;
13 end;
14 /
Function created.
Testing:
SQL> select f_test from dual;
F_TEST
--------------------
CURSOR STATEMENT : 1
CURSOR STATEMENT : 1
TABL EMPNO
---- ----------
EMP 7876
DEPT 20
SQL>
I have a stored procedure like below where multiple employee IDs will be passed as comma-separated value (multiple IDs). It is throwing error as "ORA-01722: invalid number". I know it's because of passing varchar2 variable for the numeric ID column. But is there any way we can achieve this simply?
create or replace PROCEDURE Fetch_Emp_Name(Emp_id in varchar2)
IS
BEGIN
select Name from EMP where id in (emp_id);
END;
You can use dynamic sql.
create or replace PROCEDURE Fetch_Emp_Name(emp_id in varchar2) IS
v_result varchar2;
begin
execute immediate
'select Name from EMP where id in (' || 'emp_id' || ')'
into
v_result;
end;
Also you can use package dbms_sql for dynamic sql.
Update
Another approach. I think may be better.
create or replace PROCEDURE Fetch_Emp_Name(emp_id in varchar2) IS
v_result varchar2;
begin
select
Name
from
EMP
where
id in
(
select
to_number(regexp_substr(emp_id, '[^,]+', 1, level))
from
dual
connect by regexp_substr(emp_id, '[^,]+', 1, level) is not null
);
exception
when no_data_found then
-- error1;
when too_many_rows then
-- error2;
end;
Sorry for before, I did not get the question in the right way. If you get a lot of IDs as different parameters, you could retrieve the list of names as an string split by comma as well. I put this code where I handled by regexp_substr the name of different emp_ids you might enter in the input parameter.
Example ( I am assuming that the IDs are split by comma )
create or replace PROCEDURE Fetch_Emp_Name(p_empid in varchar2) IS
v_result varchar2(4000);
v_append emp.name%type;
v_emp emp.emp_id%type;
counter pls_integer;
i pls_integer;
begin
-- loop over the ids
counter := REGEXP_COUNT(p_empid ,'[,]') ;
--dbms_output.put_line(p_empid);
if counter > 0
then
i := 0;
for r in ( SELECT to_number(regexp_substr(p_empid,'[^,]+',1,level)) as mycol FROM dual CONNECT BY LEVEL <= REGEXP_COUNT(p_empid ,'[,]')+1 )
loop
--dbms_output.put_line(r.mycol);
v_emp := r.mycol ;
select name into v_append from emp where emp_id = v_emp;
if i < 1
then
v_result := v_append ;
else
v_result := v_result ||','|| v_append ;
end if;
i := i + 1;
end loop;
else
v_emp := to_number(p_empid);
select name into v_result from emp where emp_id = v_emp;
end if;
dbms_output.put_line(v_result);
exception
when no_data_found
then
raise_application_error(-20001,'Not Employee found for '||v_emp||' ');
when too_many_rows
then
raise_application_error(-20002,'Too many employees for id '||v_emp||' ');
end;
Test
SQL> create table emp ( emp_id number, name varchar2(2) ) ;
Table created.
SQL> insert into emp values ( 1 , 'AA' );
1 row created.
SQL> insert into emp values ( 2 , 'BB' ) ;
1 row created.
SQL> commit;
SQL> insert into emp values ( 3 , 'CC' ) ;
1 row created.
SQL> select * from emp ;
EMP_ID NA
---------- --
1 AA
2 BB
3 CC
SQL> exec Fetch_Emp_Name('1') ;
AA
PL/SQL procedure successfully completed.
SQL> exec Fetch_Emp_Name('1,2,3') ;
AA,BB,CC
PL/SQL procedure successfully completed.
SQL>
CREATE OR replace PACKAGE emp_pkg
AS
PROCEDURE find_emp1(
c_id IN employees.employee_id%TYPE);
END emp_pkg;
/
CREATE OR replace PACKAGE BODY emp_pkg
AS
PROCEDURE Find_emp1(c_id IN employees.employee_id%TYPE)
IS
v_row employees%ROWTYPE;
BEGIN
SELECT first_name,
employee_id,
hire_date
INTO v_row
FROM employees
WHERE employee_id = c_id;
dbms_output.Put_line('ID:'
|| v_row.employee_id);
dbms_output.Put_line('NAME:'
||v_row.first_name);
dbms_output.Put_line('HIRE_DATE:'
|| v_row.hire_date);
END find_emp1;
END emp_pkg;
/
I think you have a mismatch between the 3 values in the select statement and the number of columns in your table. What are the table columns?
I have the table below:
CREATE TABLE req1_tb(TableName VARCHAR2(43),
ColumnName VARCHAR2(98),
Edit_ind CHAR)
Here's the dml for this table:
insert into req1_tb VALUES('Employees','employee_id','Y');
insert into req1_tb VALUES('Employees','first_name','Y');
insert into req1_tb VALUES('Employees','last_name','N');
insert into req1_tb VALUES('Employees','email','N');
insert into req1_tb VALUES('Employees','job_id','N');
insert into req1_tb VALUES('Employees','salary','Y');
insert into req1_tb VALUES('Employees','commission_pct','Y');
insert into req1_tb VALUES('Employees','hire_date','N');
insert into req1_tb VALUES('Employees','department_id','Y');
I assumed that edit_ind column in enter code here below table will change dynamically
SQL> SELECT * FROM REQ1_TB;
TABLENAME COLUMNNAME EDIT_IND
------------------------------------------- --------------- ----------
Employees employee_id Y
Employees first_name Y
Employees last_name N
Employees email N
Employees job_id N
Employees salary Y
Employees commission_pct Y
Employees hire_date N
Employees department_id Y
I have created procedure that will dynamically print columns who are marked 'Y' only:
CREATE OR REPLACE PROCEDURE dyn_sql_sp
AS
cols VARCHAR2(2000);
v_cols VARCHAR2(2000);
cls VARCHAR2(2000);
v_employee_id number;
emp employees%rowtype;
cnt number;
cursor tab_c
is
select columnname from req1_tb
where EDIT_IND='Y';
cursor col_c
is
select employee_id from employees;
BEGIN
for i in tab_C
loop
cols:=cols||'emp.'||i.columnname||',';
end loop;
cols:=rtrim(cols,',');
for i in col_c
loop
EXECUTE IMMEDIATE 'SELECT ' || cols || ' FROM employees WHERE employee_id = :1'
INTO emp
USING i.employee_id;
end loop;
dbms_output.put_line(cols);
Exception
When Others Then
dbms_output.put_line(sqlerrm||sqlcode);
end;
/
While executing I got the following error:
SQL> exec dyn_sql_sp;
ORA-01007: variable not in select list-1007
In your procedure the below code is going to create problem. As far i understand you are trying to select columns of table employee depending on 'Y' flag from table req1_tb.
Problematic Part:
for i in col_c
loop
EXECUTE IMMEDIATE 'SELECT ' || cols || ' FROM employees WHERE employee_id = :1'
--***The INTO clause is problematic here. Since the you select list is not*** having all the columns same as your cursor variable.
INTO emp
USING i.employee_id;
end loop;
Now, you are not trying the same logic while declaring a variable to hold the data returned from those selected columns in Execute Immediate statement.
For that the variable declaration should also be dynamic. So you declaration
emp employees%rowtype;
should be such that it also have all the selected columns satisfying condition flag 'Y'. You cannot insert few columns selected from your select statement to a cursor variable having all the columns.