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.
Related
I am trying to create a PL/SQL script that selects the last name and salary of an employee from the table employees. I have a cursor that stores the data and I fetch the cursor which then inserts the data into a new table called BIG_MONEY. I have a user input that selects the number of employees the loop will run through and the loop ends when it reaches the figure they provided.
SET SERVEROUTPUT ON
DROP TABLE BIG_MONEY;
ACCEPT NumberOfStaff
CREATE TABLE BIG_MONEY (
last_name VARCHAR2(10),
salary NUMBER(7, 2)
);
DECLARE V_EMP_ID employees.employee_id%TYPE;
V_EMP_LASTNAME employees.last_name%TYPE;
V_EMP_SALARY employees.salary%TYPE;
CURSOR EMP_Cursor IS
SELECT employee_id, last_name, salary
FROM employees
ORDER BY salary DESC last_name ASC
BEGIN
OPEN EMP_Cursor;
LOOP
FETCH EMP_Cursor INTO V_EMP_ID, V_EMP_LASTNAME, V_EMP_SALARY;
INSERT INTO BIG_MONEY (last_name, salary)
VALUES (V_EMP_LASTNAME, V_EMP_SALARY)
EXIT WHEN EMP_Cursor%ROWCOUNT = '&NumberOfStaff';
DMBS_OUTPUT.PUT_LINE
('Employee' || TO_CHAR (V_EMP_ID) || 'last name is' || TO_CHAR (V_EMP_LASTNAME)
|| 'and makes' || TO_CHAR (V_EMP_SALARY) || 'a month');
END LOOP;
CLOSE EMP_Cursor;
What am I doing wrong here? I would assume my logic is correct but the syntax is where I'm making a mistake.
I have created a package and defined a procedure to delete specific rows retrieved by the cursor.
Before the rows were deleted from the table, I want to take a backup of those records every time the package is compiled and I need the backup table to be created as tablename concatenated with sysdate.
ex: if table name is emp, backup table should be created as emp_2020_10_16
Below is the sample code I have created:
PROCEDURE DELETE_REC(
P_retcode NUMBER,
P_errorbuff VARCHAR2,
P_unit_id NUMBER,
P_join_date VARCHAR2
)
IS
CURSOR cur1
IS
SELECT unit_ID,dept_ID,join_DATE
FROM EMP MMT
WHERE MMT.dep_TYPE_ID IN (44,35)
AND MMT.unit_id = P_unit_id
AND MMT.join_date < to_date(P_join_date,'RRRR/MM/DD HH24:MI:SS');
BEGIN
--begin
-- EXECUTE IMMEDIATE 'Create table EMP_' || to_char(sysdate,'yyyy_mm_dd') || ' as select * from EMP MMT WHERE MMT.dep_TYPE_ID IN (44,35)
AND MMT.unit_id = P_unit_id
AND MMT.join_date < to_date(P_join_date,'RRRR/MM/DD HH24:MI:SS');
--
-- end;
/*Here i would like to create backup table like above before executing the below delete statement but i am not sure about the correct standards that i should be using for above dynamic statement*/
FOR val IN cur1
LOOP
DELETE
FROM EMP MMT
WHERE MMT.dept_ID= val.dept_id;
How can I backup the table using above dynamic statement in best possible way? I am still learning PL&SQL.
Maybe sth like this would help:
create table employees as select * from hr.employees;
--drop table emp_2020_10_18;
--drop table employees;
----------------
declare
vTabName varchar2(50);
nDept_id number := 10;
nCnt number := 0;
vSQL varchar2(1000);
begin
vTabName := 'emp_'||to_char(sysdate, 'yyyy_mm_dd');
-- check if table exists
begin
execute immediate 'select count(*) from emp_tmp' into nCnt;
exception when others then
nCnt := -1;
end;
-- if not exists create one
if nCnt = -1 then
execute immediate 'create table '|| vTabName||' as select * from employees where 1=2' ;
end if;
execute immediate 'insert into '|| vTabName ||' select * from employees where department_id = :nDept_id' using nDept_id;
delete from employees where department_id = nDept_id;
exception when others then
dbms_output.put_line(sqlerrm);
end;
/
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>
I have function which returns sys_refcursor I give you my code's example.
function myfunc( p_city IN VARCHAR2,
p_order IN VARCHAR2)
RETURN SYS_REFCURSOR IS
v_result SYS_REFCURSOR;
begin
OPEN v_result FOR WITH all_prb AS(
select * from tableA ta inner join tableB tb)
'select * from all_prb ff where (:p_city is null or (LOWER(ff.city) like ''%''||:p_city||''%'') ) order by ' || p_order || 'asc' using p_city,p_city;
return v_result;
end myfunc;
and when i am trying to compile it i have ORA-00928: missing SELECT keyword and this error targets line where i have dynamic sql 'select * from all_prb ff where ...'
How can i fix it and how can i write correct dynamic sql ? I am writing dynamic sql for ordering .
I'm not sure why you're bothering with the with clause, it's simpler without a CTE; you just need to identify which table the city column is in:
function myfunc(p_city IN VARCHAR2,
p_order IN VARCHAR2)
RETURN SYS_REFCURSOR IS
v_result SYS_REFCURSOR;
begin
OPEN v_result FOR
'select * from tableA ta
inner join tableB tb on tb.some_col = ta.some_col
where :p_city is null or LOWER(ta.city) like ''%''||:p_city||''%''
order by ' || p_order || ' asc'
using p_city, p_city;
return v_result;
end myfunc;
/
I've guessed it's table A, just change the alias if it's the other one. You also need to specify the join condition between the two tables. (Also noticed I added a space before asc to stop that being concatenated into the order-by string).
This compiles without errors; when run I get ORA-00942: table or view does not exist which is reasonable. If I create dummy data:
create table tablea (some_col number, city varchar2(30));
create table tableb (some_col number);
insert into tablea values (1, 'London');
insert into tablea values (2, 'Londonderry');
insert into tablea values (3, 'East London');
insert into tablea values (4, 'New York');
insert into tableb values (1);
insert into tableb values (2);
insert into tableb values (3);
insert into tableb values (4);
then calling it gets:
select myfunc('lond', 'city') from dual;
SOME_COL CITY SOME_COL
---------- ------------------------------ ----------
3 East London 3
1 London 1
2 Londonderry 2
If you really want to stick with the CTE for some reason then (as #boneist said) that needs to be part of the dynamic statement:
OPEN v_result FOR
'with all_prb as (
select * from tableA ta
inner join tableB tb on tb.some_col = ta.some_col
)
select * from all_prb ff
where :p_city is null or LOWER(ff.city) like ''%''||:p_city||''%''
order by ' || p_order || ' asc'
using p_city, p_city;
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