ORA-00947:not enough values - oracle

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.

Related

SYS_REFCURSOR is returning all the rows from table without considering the IN parameter

I am facing a weird problem here.
PROCEDURE USL_EMPLOYEEBYID (
EMPLOYEE_ID IN NUMBER,
EMPIDCURSOR OUT SYS_REFCURSOR
)
AS
BEGIN
OPEN EMPIDCURSOR FOR
SELECT emp.employee_id,emp.employee_name,emp.present_address,emp.permanent_address,emp.status
FROM Employee_Info emp
WHERE emp.employee_id = EMPLOYEE_ID;
END;
This procedure should give me a single employee upon entering the employee Id. But it is returning all the employees.
What am I doing wrong here?
In your query, Oracle interprets EMPLOYEE_ID as the column EMPLOYEE_ID, not the input parameter, here you find something more; in this way, your where condition is something like a=a.
Change the parameter name to distinguish it from the table column:
PROCEDURE USL_EMPLOYEEBYID (
p_EMPLOYEE_ID IN NUMBER,
po_EMPIDCURSOR OUT SYS_REFCURSOR
)
AS
BEGIN
OPEN po_EMPIDCURSOR FOR
SELECT emp.employee_id,emp.employee_name,emp.present_address,emp.permanent_address,emp.status
FROM Employee_Info emp
WHERE emp.employee_id = p_EMPLOYEE_ID;
END;
this is a good practice, to always know in your code whether you are handling an input parameter, a local variable, a column and so on

pl sql insert into within a procedure and dynamic variables

I need some help with PL SQL. I have to insert some data into table. Another application is calling my procedure and caller is passing few details which I also need to insert into my table.
here is the syntax I am struggling with:
PROCEDURE invform_last2orders_item_insert( p_userId IN NUMBER
,p_accountId IN NUMBER
,p_site_Id IN NUMBER
,p_return_message OUT VARCHAR2) IS
Begin
insert into mytable
(p_userId , p_accountId , p_site_Id , sku, description, 'Cart', 1, unitId)
as
select sku, description, unitId
from mycatalogtable where site_id= p_site_Id ) ;
End;
Can you help me with syntax? I need to pass three parameters from called in parameter and some values returned from select query. How can I achieve this?
thank you for your help.
That would be something like this; see comments within code:
PROCEDURE invform_last2orders_item_insert
( p_userId IN NUMBER
,p_accountId IN NUMBER
,p_site_Id IN NUMBER
,p_return_message OUT VARCHAR2)
IS
Begin
insert into mytable
-- first name all columns you'll be inserting into; I don't know their
-- names so I just guessed
(userid,
accountid,
siteid,
sku,
description,
col1,
col2,
unitid
)
-- if you were to insert only values you got via parameters, you'd use the
-- VALUE keyword and insert those values separately.
-- As some of them belong to a table, use SELECT statement
(select p_userid,
p_accountid,
p_siteid,
c.sku,
c.description,
'Cart',
1,
c.unitid
from mycatalogtable c
where c.site_id = p_site_Id
);
-- I don't know what you are supposed to return; this is just an example
p_return_message := sql%rowcount || ' row(s) inserted';
End;
in your select statement you should have the same number of columns as you are inserting into the table, your code should be something like this example,
DECLARE
userid varchar2(20) := 'Jack';
Begin
INSERT INTO mytable (SELECT userid, SPORT from OLYM.OLYM_SPORTS);
commit;
end;

Is it possible to return the Primary Key on an Insert as select statement - Oracle?

So I usually get the Primary Key of a newly inserted record as the following while using a trigger.
insert into table1 (pk1, notes) values (null, "Tester") returning pk1
into v_item;
I am trying to use the same concept but with an insert using a select statement. So for example:
insert into table1 (pk1, notes) select null, description from table2 where pk2 = 2 returning pk1
into v_item;
Note:
1. There is a trigger on table1 which automatically creates a pk1 on insert.
2. I need to use a select insert because of the size of the table that is being inserted into.
3. The insert is basically a copy of the record, so there is only 1 record being inserted at a time.
Let me know if I can provide more information.
I don't believe you can do this with insert/select directly. However, you can do it with PL/SQL and FORALL. Given the constraint about the table size, you'll have to balance memory usage with performance using l_limit. Here's an example...
Given this table with 100 rows:
create table t (
c number generated by default as identity,
c2 number
);
insert into t (c2)
select rownum
from dual
connect by rownum <= 100;
You can do this:
declare
cursor t_cur
is
select c2
from t;
type t_ntt is table of number;
l_c2_vals_in t_ntt;
l_c_vals_out t_ntt;
l_limit number := 10;
begin
open t_cur;
loop
fetch t_cur bulk collect into l_c2_vals_in limit l_limit;
forall i in indices of l_c2_vals_in
insert into t (c2) values (l_c2_vals_in(i))
returning c bulk collect into l_c_vals_out;
-- You have access to the new ids here
dbms_output.put_line(l_c_vals_out.count);
exit when l_c2_vals_in.count < l_limit;
end loop;
close t_cur;
end;
You can't use that mechanism; as shown in the documentation railroad diagram:
the returning clause is only allowed with the values version, not with the subquery version.
I'm interpreting your second restriction (about 'table size') as being about the number of columns you would have to handle, possibly as individual variables, rather than about the number of rows - I don't see how that would be relevant here. There are ways to avoid having lots of per-column local variables though; you could select into a row-type variable first:
declare
v_item number;
v_row table1%rowtype;
begin
...
select null, description
into v_row
from table2 where pk2 = 2;
insert into table1 values v_row returning pk1 into v_item;
dbms_output.put_line(v_item);
...
or with a loop, which might make things look more complicated than necessary if you really only ever have a single row:
declare
v_item number;
begin
...
for r in (
select description
from table2 where pk2 = 2
)
loop
insert into table1 (notes) values (r.description) returning pk1 into v_item;
dbms_output.put_line(v_item);
...
end loop;
...
or with a collection... as #Dan has posted while I was answering this so I won't repeat! - though again that might be overkill or overly complicated for a single row.

Accept uni or multi-dimensional parameter in PL/SQL function

I'm trying to write a function nbrJobs() in PL/SQL that counts the number of jobs that a user has had in the past.
In order to do this I first need to determine the "employee id", which can be determined by a pair (first name, last name).
I manage to do this when the arguments of the function are:
nbrJobs(firstName IN VARCHAR2(20), lastName IN VARCHAR2(25))
Then, this simple test runs without any problem:
DECLARE
nbrJobsTotal NUMBER;
BEGIN
SELECT nbrJobs(first_name, last_name) INTO nbrJobsTotal
FROM employees
WHERE employee_id = 101
END
Now, the problem is that my function should also work with that kind of call:
SELECT nbrJobs(first_name, last_name) INTO nbrJobsTotal FROM employees
with table employees containing multiple tuples.
So, now I'm confused about the input parameters type.. Should I use a VARRAY, a nested TABLE, a CURSOR, something else ?
What does a SELECT actually returns if multiple rows are selected?
A PL/SQL function is executed for each row of the SELECT statement. Therefore, if you call your function in a regular SELECT SQL statement you will get a value for each record.
Here is an example by concatenating the first and last name together:
CREATE OR REPLACE FUNCTION NAME (p_FIRST_NAME IN VARCHAR2, p_LAST_NAME IN VARCHAR2)
RETURN VARCHAR2
AS
BEGIN
RETURN p_FIRST_NAME || ' ' || p_LAST_NAME;
END;
/
SELECT first_name, last_name, name(first_name, last_name) FROM HR.employees;
FIRST_NAME LAST_NAME NAME(FIRST_NAME,LAST_NAME)
---------- --------- --------------------------
Ellen Abel Ellen Abel
Sundar Ande Sundar Ande
Mozhe Atkinson Mozhe Atkinson
David Austin David Austin
As you can see, for each row the PL/SQL function is executed and concatenates the first and last name and then is returning the result as a new column to the SELECT statement. There is no need for you to change the function to make it work with multiple row.
Now how would you be executing this inside PL/SQL with the SELECT statement that you have used as example above. You will either have to loop over the results with a cursor or you could use a collection type if you just want to fetch the result of all of the rows into a variable.
Using a cursor (
I'm demonstrating this on my example above by using the Cursor FOR LOOP):
BEGIN
FOR result IN (SELECT first_name, last_name, name(first_name, last_name) name FROM HR.employees) LOOP
DBMS_OUTPUT.PUT_LINE(result.first_name || ', ' || result.last_name || ', ' || result.name);
END LOOP;
END;
/
Ellen, Abel, Ellen Abel
Sundar, Ande, Sundar Ande
Mozhe, Atkinson, Mozhe Atkinson
David, Austin, David Austin
What happens here is that I'm executing the very same SELECT statement but now inside the Cursor FOR LOOP which allows me to loop over each individual row that has been return. In this case I just print the result out into the console.
If you want to just save all the rows into a variable you will have to use a PL/SQL Collection:
DECLARE
-- Specify cursor with expected results
CURSOR c1 IS
SELECT first_name, last_name, name(first_name, last_name) name
FROM HR.employees;
-- Create PL/SQL type for a nested table of the rowtype of the cursor (first_name, last_name, name)
TYPE NameSet IS TABLE OF c1%ROWTYPE;
employees NameSet; -- Instantiate a variable of the nested table of records
BEGIN
-- Assign values to nested table of records:
SELECT first_name, last_name, name(first_name, last_name) name
BULK COLLECT INTO employees
FROM HR.employees;
-- Print nested table of records:
FOR i IN employees.FIRST .. employees.LAST LOOP
DBMS_OUTPUT.PUT_LINE (
employees(i).first_name || ' ' ||
employees(i).last_name || ', ' ||
employees(i).name
);
END LOOP;
END;
/
Ellen Abel, Ellen Abel
Sundar Ande, Sundar Ande
Mozhe Atkinson, Mozhe Atkinson
David Austin, David Austin
As you can see here the same SELECT is executed but here we use BULK COLLECT INTO rather than just INTO. This is because the SELECT is returning more than one row, hence we need to tell the compiler that we do indeed expect that so that the compiler doesn't throw an error that more rows have been returned.
Last but not least, given that you use the variable name nbrJobsTotal in your example SELECT nbrJobs(first_name, last_name) INTO nbrJobsTotal FROM employees above, I think what you really want to try to do here is to sum up all the number of different jobs that employees had in your company. You can accomplish just that but using the built-in SUM() function which is an aggregation function, i.e it will only return one row without a GOUP BY clause:
SELECT SUM(nbrJobs(first_name, last_name)) INTO nbrJobsTotal FROM employees
You can use a Table of Type - to bulk collect all rows returned by the select statement.
Based on your example it would look like this:
DECLARE
nbrJobsTotal NUMBER;
TYPE jobsTotalTable_type IS TABLE OF nbrJobsTotal%TYPE;
jobsTotalTable jobsTotalTable_type ;
BEGIN
--bulk collect results
SELECT nbrJobs(first_name, last_name) BULK COLLECT INTO jobsTotalTable
FROM employees;
--print results
FOR indx IN 1 .. jobsTotalTable.COUNT LOOP
DBMS_OUTPUT.PUT_LINE(jobsTotalTable (indx));
END LOOP;
END

Iterate over a column in PL/SQL

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.

Resources