Iterate over a column in PL/SQL - oracle

I have a table Emp with EmpID, Empname, Salary and I am trying to do a calculation for each employee. But I am having problems trying to iterate over each emp to do the calculation. I cant use explicit cursors though.
So right now I am just trying to create the list of empIDs:
Declare
aRows Number;
eid emp_ID%TYPE;
Begin
Select Count(*)
Into aRows
from emp;
Select emp_ID
Into eid
From emp;
FOR days IN 1..Tot_Rows
Loop
Dbms_Output.Put_Line(eid);
eid := eid + 1;
End Loop;
END;
But I get the error:
PLS-00320: the declaration of the type of this expression is incomplete or malformed

The simplest way to iterate over the rows in a table in PL/SQL is to do something like
BEGIN
FOR employees IN (SELECT emp_id FROM emp)
LOOP
dbms_output.put_line( employees.emp_id );
END LOOP;
END;
Alternately, you could fetch all the EID values into a PL/SQL collection and iterate over the collection, as in this example
DECLARE
TYPE emp_id_tbl IS TABLE OF emp.emp_id%type;
l_emp_ids emp_id_tbl ;
BEGIN
SELECT emp_id
BULK COLLECT INTO l_emp_ids
FROM emp;
FOR i IN l_emp_ids .FIRST .. l_empnos.LAST
LOOP
dbms_output.put_line( l_emp_ids (i) );
END LOOP;
END;
If your query can return thousands of rows, however, fetching all the data into the collection may use more of the PGA memory than you'd like and you may need to fetch rows in chunks using the LIMIT clause. But that would seem to be getting ahead of ourselves at this point.

Justin Cave has explained how to do it, but to specifically look at the error you got, that was because of this:
eid emp_ID%TYPE;
When using the %TYPE you have to specify the table name as well as the column name:
eid emp.emp_ID%TYPE;
If you were selecting all the columns in the row you could also look at %ROWTYPE.
Your approach was also making two assumptions: that the initial select into eid found the lowest ID, which is by no means guaranteed; and that all the subsequent ID values are sequential. And you're declaring and populating aRows but referring to Tot_Rows.

Related

ORA-00947:not enough values

guys i am getting error that PL/SQL: ORA-00947: not enough values while running my code.
declare
type e_type is record ( last_name employees.last_name%type,
email employees.email%type,
hire_date employees.hire_date%type,
job_id employees.job_id%type);
type e_list is table of e_type index by pls_integer;
emps e_list;
begin
for x in 100 .. 110 loop
select last_name,email,hire_date,job_id into emps(x) from employees
where employee_id = x ;
---dbms_output.put_line(emps(x).email);
insert into emp(last_name,email,hire_date,job_id) values emps(x);
end loop;
end;
#WilliamRobertson presents an interesting solution (I had actually forgotten that format). However there is still a much simpler solution. That is to use a single sql statement for the select and insert while avoiding the type and variable declarations and the loop altogether:
insert into emp(last_name,email,hire_date,job_id)
select last_name,email,hire_date,job_id
from employees
where employee_id between 100 and 110;
The problem is here:
insert into emp(last_name,email,hire_date,job_id)
values emp(x);
The PL/SQL INSERT Statement Extension where you use a record variable for the values clause does not accept a column list, so it needs to be either
insert into emp values emp(x);
where emp(x) evaluates to a record matching the entire emp row, or else
insert into
( select last_name, email, hire_date, job_id from emp )
values emp(x);
where emp(x) is a record exactly matching the select list.

Insert into not working on plsql in oracle

declare
vquery long;
cursor c1 is
select * from temp_name;
begin
for i in c1
loop
vquery :='INSERT INTO ot.temp_new(id)
select '''||i.id||''' from ot.customers';
dbms_output.put_line(i.id);
end loop;
end;
/
Output of select * from temp_name is :
ID
--------------------------------------------------------------------------------
customer_id
1 row selected.
I have customers table which has customer_id column.I want to insert all the customer_id into temp_new table but it is not being inserted. The PLSQL block executes successfully but the temp_new table is empty.
The output of dbms_output.put_line(i.id); is
customer_id
What is wrong there?
The main problem is that you generate a dynamic statement that you never execute; at some point you need to do:
execute immediate vquery;
But there are other problems. If you output the generated vquery string you'll see it contains:
INSERT INTO ot.temp_new(id)
select 'customer_id' from ot.customers
which means that for every row in customers you'll get one row in temp_new with ID set to the same fixed literal 'customer_id'. It's unlikely that's what you want; if customer_id is a column name from customers then it shouldn't be in single quotes.
As #mathguy suggested, long is not a sensible data type to use; you could use a CLOB but only really need a varchar2 here. So something more like this, where I've also switched to use an implicit cursor:
declare
l_stmt varchar2(4000);
begin
for i in (select id from temp_name)
loop
l_stmt := 'INSERT INTO temp_new(id) select '||i.id||' from customers';
dbms_output.put_line(i.id);
dbms_output.put_line(l_stmt);
execute immediate l_stmt;
end loop;
end;
/
db<>fiddle
The loop doesn't really make sense though; if your temp_name table had multiple rows with different column names, you'd try to insert the corresponding values from those columns in the customers table into multiple rows in temp_new, all in the same id column, as shown in this db<>fiddle.
I guess this is the starting point for something more complicated, but still seems a little odd.

PLSQL Count and Procedure

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;

How to store a select sql result record in list in Oracle

I have a table employee. below query return one record, want to store it in a list so that latter on i can iterate it and fetch the value.
select employee_name,employee_surname from employee where emp_id = '123';
something like
select employee_name,employee_surname bulk collect into list_emp from employee where emp_id = '123';
.
.
FOR indx IN 1 .. list_emp.count
LOOP
<other sqls>
END LOOP;
This can be done by declaring a package level collection of record.
CREATE OR REPLACE PACKAGE pkg_emp_rec AS
TYPE emp_rec IS RECORD ( employee_name employee.employee_name%TYPE,
employee_surname employee.employee_surname%TYPE );
TYPE list_emptype IS
TABLE OF emp_rec;
list_emp list_emptype;
END pkg_emp_rec;
Next you can store and retrieve from this collection in whichever block/procedure you want.
SET SERVEROUTPUT ON
BEGIN
SELECT
employee_name,
employee_surname
BULK COLLECT INTO
pkg_emp_rec.list_emp
FROM
employee;
FOR indx IN 1..pkg_emp_rec.list_emp.count
LOOP
dbms_output.put_line(pkg_emp_rec.list_emp(indx).employee_name);
END LOOP;
END;
/
Note: This is not recommended when your table data is huge. It is always better to fetch from the database table than collections. Only take this as a solution for your homework.
Another approach is to store the employee names as a nested table column, which I give you as an assignment.

Bulk Collect and ForAll - Oracle

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);

Resources