I am a newbie to stored procedure and to PL/SQL. There is an existing procedure to copy data from one table to another. I want to rewrite the stored procedure to accept table name and column names as arguments.Did googling on the solution but couldn't come up with a solid solution.
Also planning to add column names as argument so that the column names don't have to be repeatedly added in multiple stored procedures which uses the same tables and columns, helps to reduce maintenance when columns names gets added/removed. Code has been added.
Can anyone help me with this? Any sample code will be very helpful.
create or replace procedure copy_data(startDate DATE, endDate DATE,
mainTable varchar2, subTable varchar2, cpyTbl varchar2)
IS
commit_size NUMBER :=1000;
existing_columns NUMBER;
after_deletion_columns NUMBER;
removed_columns NUMBER;
TYPE order_ids IS TABLE OF subTable.id%TYPE INDEX BY PLS_INTEGER;
removable_order_ids order_ids;
bulk_errors EXCEPTION;
PRAGMA EXCEPTION_INIT (bulk_errors, -24381);
CURSOR C1 is select id FROM subTable where ord_id in (select ord_id from
mainTable where tmstmp BETWEEN startDate AND endDate);
BEGIN
open C1;
LOOP
FETCH C1 BULK COLLECT INTO removable_order_ids LIMIT commit_size;
forall indx in 1..removable_order_ids.COUNT
INSERT INTO cpyTbl (id, ord_id, name, phon_nbr)
select id, ord_id, name, phon_nbr from subTable
where ord_id = removable_order_ids(indx) LOG ERRORS INTO
ERR$_cpyTbl('INSERT') REJECT LIMIT UNLIMITED;
COMMIT;
EXIT WHEN removable_order_ids.COUNT < commit_size;
END LOOP;
COMMIT;
end;
Related
I'm pretty new to Oracle. I need to know how to use procedural language to insert data from one table to another, Using Cursor and Table API. I have searched a lot and have come here.
For example lets take a scenario of employees, we need to fetch all the details of the employees from the employees table and insert it into a table named "employees_backup".
I need to know how it can be implemented using Cursor,Table API with best programming practice.
I have done the following. but i havent done cursor implementation and Table API implementation. So guide me how to do it.
Package Specification:
create or replace employee_package
AS
procedure getemployees(department_id IN NUMBER);
I'm passing department_id to get only the specific department staffs.
Package Body:
CREATE OR REPLACE
PACKAGE BODY employee_package AS
PROCEDURE getemployees(department_id IN NUMBER) AS
BEGIN
INSERT INTO employees_backup select * from employees WHERE
department_id = department_id;
END employee_package;
This is done without Using Table API. I need to know how to implement it using Table API. If cursor can be used, Including Cursor implementation as well. Hope i have explained the question well enough to not get flagged.
You could code along the following lines:
CREATE OR REPLACE PACKAGE employee_package AS
PROCEDURE getemployees(p_department_id IN NUMBER);
PROCEDURE getmanyemployees(p_department_id IN NUMBER);
PROCEDURE getfastemployees(p_department_id IN NUMBER);
END employee_package;
/
This impliments 3 procedures to compare the (prefered) plain SQL and the cursor bulk collect method.
CREATE OR REPLACE PACKAGE BODY employee_package AS
PROCEDURE getfastemployees(p_department_id IN NUMBER) AS
BEGIN
INSERT INTO backup_employees e
SELECT * FROM employees WHERE department_id = p_department_id;
END getfastemployees;
PROCEDURE getemployees(p_department_id IN NUMBER) AS
TYPE emp_type IS TABLE OF employees%ROWTYPE INDEX BY PLS_INTEGER;
emp_array emp_type;
BEGIN
SELECT *
BULK COLLECT INTO emp_array
FROM employees
WHERE department_id = p_department_id;
FORALL i IN 1 .. emp_array.COUNT
INSERT INTO backup_employees VALUES emp_array(i);
END getemployees;
PROCEDURE getmanyemployees(p_department_id IN NUMBER) AS
CURSOR cur (c_department_id NUMBER) IS
SELECT *
FROM employees
WHERE department_id = c_department_id;
TYPE emp_type IS TABLE OF cur%ROWTYPE;
emp_array emp_type;
BEGIN
OPEN cur(p_department_id);
LOOP
FETCH cur BULK COLLECT INTO emp_array LIMIT 1000;
FORALL i IN 1..emp_array.COUNT
INSERT INTO backup_employees VALUES emp_array(i);
EXIT WHEN cur%NOTFOUND;
END LOOP;
CLOSE cur;
COMMIT;
END getmanyemployees;
END employee_package;
/
For 100.000 employees, getfastemployees(50) takes 0.88s, getmanyemployees 1.54, and getemployees 9.5s. Interestingly, for 2.7 million employees, getfastemployees and getmanyemployees are still in the same region.
See also bulk collect ...for all usage etc.
Currently trying to create a PL/SQL procedure. I am a complete noob at PL/SQL, as you can tell!
We have had to create a table using SQL, and we are looking to automatically update the table with a procedure. If the customer has requested more than 4 jobs, we are looking to input their details into this table as a frequent customer.
I currently have at the moment:
CREATE TABLE PublisherDetails
(PublisherName VARCHAR2 (40),
City VARCHAR2 (20) ,
PhoneNo NUMBER (11),
jobNo NUMBER (10),
startDate DATE,
completionDate DATE)
;
SELECT Publisher.Name AS PublisherName,
Publisher.City, Publisher.PhoneNo,
COUNT (*) AS PublisherJobCount
FROM Publisher
INNER JOIN PrintJob
ON Publisher.Name = PrintJob.PublisherName
GROUP BY Publisher.Name, Publisher.City, Publisher.PhoneNo;
Create or replace procedure Task3
IS CountPublisherJobs NUMBER;
DECLARE No_data_Found EXCEPTION
BEGIN
SELECT count(*) INTO CountPublisherJobs
OPEN Task3;
LOOP
IF PublisherJobCount < 3
THEN INSERT INTO PublisherDetails (PublisherName, City, PhoneNo)
FROM Publisher
WHERE PublisherName = publisher.name
Else
Insert Into PublisherDetails (JobNo, StartDate, CompletionDate )
SELECT jobNo, startDate, completionDate
FROM PrintJob
Where PublisherName = publishers.name
FETCH Task3 INTO PublisherDetails, publishername, city, phoneNo;
EXIT WHEN c1%NOTFOUND;
INSERT INTO temp VALUES (PublisherName, City, PhoneNo, JobNo, StartDate, CompletionDate);
END IF;
COMMIT;
END LOOP;
EXCEPTION
WHEN NO_DATA_FOUND THEN
dbms_output.put_line('Sorry no data found');
END;
/
Its churning up errors and I am not sure why. Any help as always is appreciated.
There are a number of things incorrect with the syntax of your procedure.
The basic format for a stored procedure is
Create {Or Replace} Procedure PROCEDURE_NAME {(i_param IN datatype)}
Is
<<Declaration Section>>
Begin
<<code section>>
Exception
<<Exceptions>>
End PROCEDURE_NAME;
From what you have described above, you want to insert a record into a table, when a condition is met in another table.
To accomplish this, I would need to see the underlying data structure, what you have provided doesn't show the tables the data is currently in (is there a JOB table for instance? a Customer table?).
The NO_DATA_FOUND exception does not need to be declared, it is an Oracle exception
Your Select Count(*) Into CountJobs is missing a From TABLE, and any predicates you want to add, although I am not sure what you are trying to accomplish with this.
You are attempting to open a cursor on the procedure name. You have not defined a cursor with the name Task3
You have not declared the CountPublisherJobs variable
I would suggest perhaps revisiting the basic structure for a stored procedure.
Edit
Based on your response, you could achieve the result using the following:
Create Or Replace Procedure addFrequentPublisher
Is
Cursor frequentPublishers Is
Select PUBLISHER_ID
From JOB
Group By
PUBLISHER_ID
Having Count(*) >= 4;
Begin
For i In frequentPublishers
Loop
Insert Into FREQUENT_CUSTOMER ...
End Loop;
End;
I am trying to use %ROWTYPE in my code and trying to insert value into it using a cursor for loop as below :
CREATE or REPLACE PROCEDURE test_acr (
PROJECT_START_DATE IN DATE,USER_ID IN VARCHAR2)
IS TYPE acr_new IS TABLE OF acr_projected_new%ROWTYPE
INDEX BY SIMPLE_INTEGER;
acr_projected_neww acr_new;
CURSOR WEEKENDING_DATE IS
SELECT WEEKEND_DATE
FROM weekending_table
WHERE WEEKEND_DATE BETWEEN PROJECT_START_DATE AND sysdate;
BEGIN
FOR WEEKEND_DATE_REC in WEEKENDING_DATE LOOP
INSERT INTO acr_projected_neww(WEEKEND_DATE,USERID,TIMESTAMP,ACR_PROJECTED,artificial_id)
SELECT WEEKEND_DATE_REC.WEEKEND_DATE,USER_ID,sysdate,
(select sum(acr_h.activity_impact)
FROM ACR_HISTORY acr_h
LEFT JOIN Activity act on act.activity_id = acr_h.activity_id
LEFT JOIN Activity_Date_Duration act_d on act_d.activity_id = act.activity_id),1 from dual;
END LOOP;
END test_acr;
When i try to run this i get below error:
Error(54,14): PL/SQL: ORA-00942: table or view does not exist
My Requirement is to create virtual table and insert the data into it using cursor for loop if not then any other means is appreciated.
Please help it will be greatly appreciated!
Looks like table name is incorrect in your INSERT statement.
You need not use two queries. Instead, define your cursor such that it has all the columns of the records you want to store in the collection. Then use BULK COLLECT INTO instead of insert as shown. Define your collection as table of cursor%ROWTYPE.
CREATE OR REPLACE PROCEDURE test_acr
IS
CURSOR WEEKENDING_DATE
IS
SELECT a.col1,a.col2,b.col1,b.col2 ,c.col1
from table1 a , table2 b LEFT JOIN table3 c; --Here include all the data from the required tables.
TYPE acr_new
IS
TABLE OF WEEKENDING_DATE%ROWTYPE;
acr_projected_neww acr_new;
BEGIN
FETCH WEEKENDING_DATE BULK COLLECT INTO acr_projected_neww;
END test_acr;
If you need to manipulate your data (access each row - then see script below, this is sequential access (inserts) into nested table (PL/SQL collection)
CREATE or REPLACE PROCEDURE test_acr (PROJECT_START_DATE IN DATE,USER_ID IN VARCHAR2)
IS
TYPE acr_new
IS TABLE OF acr_projected_new%ROWTYPE; // nested table, notice absence of INDEX by clause
acr_projected_neww acr_new := acr_projected_neww(); // instantiation, constructor call
CURSOR WEEKENDING_DATE IS
SELECT WEEKEND_DATE
FROM weekending_table
WHERE WEEKEND_DATE BETWEEN PROJECT_START_DATE AND sysdate;
BEGIN
FOR WEEKEND_DATE_REC in WEEKENDING_DATE
LOOP
acr_new.extend; // make room for the next element in collection
acr_new(acr_new.last) := WEEKEND_DATE_REC; // Adding seq. to the end of collection
...
END LOOP;
END test_acr;
However if you want to BULK INSERT (there is no requirement to get access to each row) see script below
CREATE or REPLACE PROCEDURE test_acr (PROJECT_START_DATE IN DATE,USER_ID IN VARCHAR2)
IS
TYPE acr_new IS TABLE OF acr_projected_new%ROWTYPE; // no INDEX BY clause
acr_projected_neww acr_new = acr_new(); // Notice constructor call
CURSOR WEEKENDING_DATE IS
SELECT WEEKEND_DATE
FROM weekending_table
WHERE WEEKEND_DATE BETWEEN PROJECT_START_DATE AND sysdate;
BEGIN
FETCH WEEKENDING_DATE BULK COLLECT INTO acr_projected_neww;
...
END LOOP;
END test_acr;
I have used temporary table outside my procedure:
CREATE GLOBAL TEMPORARY TABLE "MY_TEMP"
( "WEEKEND_DATE" DATE,
"USERID" VARCHAR2(255 BYTE),
"TIMESTAMP" TIMESTAMP (6),
"ACR_PROJECTED" NUMBER,
"ARTIFICIAL_ID" NUMBER
) ON COMMIT PRESERVE ROWS ;
i have just used the above temporary table inside my Procedure
create or replace PROCEDURE GET_ACR_TEST(
PROJECT_START_DATE IN DATE ,
USER_ID IN VARCHAR2,
) AS
CURSOR WEEKENDING_DATE IS
SELECT WEEKEND_DATE, DURATION
FROM weekending_table where WEEKEND_DATE between PROJECT_START_DATE and sysdate;
Begin
FOR WEEKEND_DATE_REC in WEEKENDING_DATE
LOOP
insert into MY_TEMP (WEEKEND_DATE,USERID,TIMESTAMP,ACR_PROJECTED,artificial_id)
select WEEKEND_DATE_REC.WEEKEND_DATE,USER_ID,sysdate,
(select sum(acr_h.activity_impact)
from ACR_HISTORY acr_h
LEFT JOIN Activity act on act.activity_id = acr_h.activity_id
LEFT JOIN Activity_Date_Duration act_d on act_d.activity_id = act.activity_id),1
from dual;
End Loop;
END GET_ACR_TEST;
The above method is working.
Thank you all for your comments!
I am looking for the syntax to access a column from the Record Type (Index by table). Below is the sample code. How shall i run the Update script in below Declare block which need empid from the V_Emprec record type. I have created a proc also which needs same parameter(empid).
Can this be done using %Rowtype or i need to create type with emp_stage.empid%type?
If i create 2 TYPES for Empid and Ename as emp_stg.column_name%type, can i use those to replace the Insert script using Rowtype v_emprec?
Please tell the syntax to do this.
create table emp_master(empid number, ename varchar2(50));
create table emp_stage (empid number, ename varchar2(50));
create procedure update_emp_name(P_empid in emp_master.empid%type)
is
begin
Update emp_stage set ename =INITCAP(ename) WHERE EMPID =P_empid;
commit;
end;
Declare
Type emprec is table of emp_master%rowtype index by pls_integer;
v_emprec emprec;
Begin
Select empid,ename bulk collect into v_emprec from emp_master;
ForAll i in 1..v_emprec.count
Insert into emp_stage values v_emprec(i);
Update emp_stage set ename =INITCAP(ename) WHERE EMPID =v_emprec.empid(i);
/*Need Correct Syntax to use empid from the v_emprec type*/
update_emp_name();
commit;
End;
Thanks
You can do the update in a second forall, and reference the record field as you would any record field or table column; you just have the index reference in the wrong place:
ForAll i in 1..v_emprec.count
Insert into emp_stage values v_emprec(i);
ForAll i in 1..v_emprec.count
Update emp_stage set ename = INITCAP(ename)
WHERE EMPID = v_emprec(i).empid;
You can't call a procedure with forall, it only allows DML. You can use a normal for loop though:
for i in 1..v_emprec.count loop
update_emp_name(v_emprec(i).empid);
end loop;
But as that does single row-by-row updates, and incurs extra context switches, that will be less efficient than the forall approach; or indeed a single update of all the rows. You can also loop around the collection and initcap the fields before doing the insert:
for i in 1..v_emprec.count loop
v_emprec(i).ename := INITCAP(v_emprec(i).ename);
end loop;
ForAll i in 1..v_emprec.count
Insert into emp_stage values v_emprec(i);
Or change the insert to refer to the fields separately and do the initcap during the insert (which also won't work with 10g). Or do the initcap in the query:
Select empid, INITCAP(ename) bulk collect into v_emprec from emp_master;
ForAll i in 1..v_emprec.count
Insert into emp_stage values v_emprec(i);
This is the code i used in stored procedure;
CREATE OR REPLACE PROCEDURE MY_STORE_PROCEDURE (new_date in date)
IS
BEGIN
execute immediate 'INSERT INTO TEMP_1 ( ID CHAR(10),
A_CNT NUMBER,
JOIN_DT DATE,
)
SELECT
L1.ID,
L1.A_CNT,
L1.JOIN_DT,
FROM ACTVY_1 L1
WHERE L1.JOIN_DT = new_date';
END;
===========================================================
Below is the code i used to call store procedure with passing value. value is date which store procedure reciece and used to pull date from a table. but it is giving me error.
DECLARE
a_date DATE;
BEGIN
a_date :=to_DATE ('01-NOV-2013', 'DD-MON-YYYY');
MY_STORE_PROCEDURE(a_date);
END;
Please suggest is there any syntax error or what is issue.
Based on your example, there is no reason to use dynamic SQL. You also have a bunch of errors. Try this:
CREATE OR REPLACE PROCEDURE MY_STORE_PROCEDURE (new_date IN DATE)
IS
BEGIN
INSERT INTO TEMP_1 (ID, A_CNT, JOIN_DT)
SELECT L1.ID, L1.A_CNT, L1.JOIN_DT
FROM ACTVY_1 L1
WHERE L1.JOIN_DT = new_date;
END;