ORACLE stored procedure returning too many rows - oracle

I'm attempting to write a stored procedure in Oracle (which I have come to hate (beside the point))
When executing the stored proc I'm told I've retrieved too many rows (eg. more than 1), but when querying the data via text, it tells me clearly that only one row matches this criteria.
create or replace
PROCEDURE GETADDRESSCOORDS
(
HOUSE IN VARCHAR2
, STREET IN VARCHAR2
, X OUT NUMBER
, Y OUT NUMBER
) AS
BEGIN
SELECT X_COORD, Y_COORD INTO X,Y FROM MASTER_ADDRESS
WHERE HOUSE=HOUSE AND STR_NAME=STREET AND PRE_DIR IS NULL;
END GETADDRESSCOORDS;
When run, I receive this error msg:
SQL> execute getaddresscoords('1550', 'BEDFORD', :X, :Y)
BEGIN getaddresscoords('1550', 'BEDFORD', :X, :Y); END;
*
ERROR at line 1:
ORA-01422: exact fetch returns more than requested number of rows
ORA-06512: at "TAXLOTS.GETADDRESSCOORDS", line 9
ORA-06512: at line 1
So I got too many rows...but when I execute this:
SQL> SELECT MAX(rownum) from MASTER_ADDRESS where HOUSE='1550'
AND STR_NAME='BEDFORD' AND PRE_DIR IS NULL;
MAX(ROWNUM)
-----------
1
what am I missing here?

Your problem is related to variable scoping. In your SELECT statement, HOUSE will always refer to the column in the table, not to the parameter of the same name.
Generally, when writing PL/SQL, you use some sort of naming convention to differentiate parameters and local variables from columns in tables in order to make this more obvious. In your case, you probably want something like
create or replace
PROCEDURE GETADDRESSCOORDS
(
P_HOUSE IN VARCHAR2
, P_STREET IN VARCHAR2
, P_X OUT NUMBER
, P_Y OUT NUMBER
) AS
BEGIN
SELECT X_COORD, Y_COORD
INTO P_X,P_Y
FROM MASTER_ADDRESS
WHERE HOUSE=P_HOUSE
AND STR_NAME=P_STREET
AND PRE_DIR IS NULL;
END GETADDRESSCOORDS;
If you were to declare local variables, you would similarly use some sort of naming convention to differentiate them from columns in tables (i.e. l_local_variable).
You could explicitly specify the scope resolution for variables that match the names of columns as well but that tends to get much uglier (and you have to be very careful that you don't miss any situations where a column name and a variable name match and the scope resolution isn't explicitly specified). It would be legal to write
create or replace
PROCEDURE GETADDRESSCOORDS
(
HOUSE IN VARCHAR2
, STREET IN VARCHAR2
, X OUT NUMBER
, Y OUT NUMBER
) AS
BEGIN
SELECT X_COORD, Y_COORD
INTO X,Y
FROM MASTER_ADDRESS ma
WHERE ma.HOUSE=getAddressCoords.HOUSE
AND ma.STR_NAME=getAddressCoords.STREET
AND ma.PRE_DIR IS NULL;
END GETADDRESSCOORDS;
but this would not be very conventional.

Related

Error(13,61): PL/SQL: ORA-00984: column not allowed here IN PROCEDURE WHEN PASS IN PARAMETER

CREATE OR REPLACE PROCEDURE INSEMP
(
EMPNOS IN VARCHAR2
, ENAMES IN VARCHAR2
, JOBAS IN VARCHAR2
, MGRS IN VARCHAR2
, HIREDATES IN VARCHAR2
, SALS IN VARCHAR2
, COMMISSIONS IN VARCHAR2
, DEPTNOS IN VARCHAR2
) AS
BEGIN
INSERT INTO emp VALUES (EMPNOS,ENAMES,JOBAS,MGRS,HIREDATES,SALS,COMMS,DEPTNOS);
END INSEMP;
When I execute the above, I get an error: Error(13,67): PL/SQL: ORA-00984: column not allowed here.
I know when we insert
INSERT INTO EMP VALUES ('DSFD'DSFDFD', ...)
we have to use single quotes, but how do I pass the values in via the parameters?
Your issue is that you have a parameter called COMMISSIONS but in your insert statement, you are passing in a value of COMMS.
Also, make sure your parameters have different names to the columns you're trying to insert into; this is (IMO) good practice across any PL/SQL code you're writing, as you can have unexpected results when name collisions happen.
Change your insert statement to use the right parameter name (or, alternatively, change the name of the parameter to match your insert statement) and it should start working.
One point, though: it is bad practice to not list the columns you are inserting into. In real production code, if someone added a column to the table, your code would break.
Your insert statement would be better written as:
insert into <table> (<list of columns to be inserted into>)
values (<list of values to insert into those columns>);

Record type, Collection and Bulk collect at the same time in oracle plsql

I am facing a challenge in implementing a scenario in code.
I am trying to use record type, collections and bulk collect at the same time during a proof of concept. But I am unable to and I am getting errors.
I don't know how to pass the bulk collect argument as an input parameter to the proc which I had created in the package below...
CREATE OR REPLACE PACKAGE poc1
AS
TYPE poc_rectype IS RECORD
(
id VARCHAR2 (20),
name VARCHAR2 (20)
);
PROCEDURE poc1_prc (poc_rec1 IN poc_rectype);
END poc1;
CREATE OR REPLACE PACKAGE BODY poc1
AS
PROCEDURE poc1_prc (poc_rec1 IN poc_rectype)
IS
BEGIN
FOR i IN 1 .. poc_rec1.COUNT
LOOP
DBMS_OUTPUT.PUT_LINE ('poc_rec1' || poc_rec1.COUNT);
END LOOP;
*-- i want to print the records passed from the execution script here
-- later i want to do some insertion in some table..*
DBMS_OUTPUT.PUT_LINE ('executed');
END poc1_prc;
END poc1;
Here I am trying to pass only one record for now..
But, I wish to pass a collection of records and print it out or do some insertion in the package containing the procedure above.
/* execution script for the above package*/
DECLARE
l_rec_type poc1.poc_rectype;
BEGIN
SELECT (SELECT 100, 'Jack' FROM DUAL)
BULK COLLECT INTO l_rec_type
FROM DUAL;
poc1.poc1_prc (l_rec_type);
END;
Please could someone help me on implementing this POC.
I tried everything. but i am feeling helpless
You were close, but you were missing a nested table to hold the values. You had a record type and a record variable. But a record variable can only hold a single row of data. To hold multiple rows of data, you need a record type, a nested table, and a nested table variable.
Here's the package to contain the types and process the data:
CREATE OR REPLACE PACKAGE poc1
AS
TYPE poc_rectype IS RECORD
(
id VARCHAR2 (20),
name VARCHAR2 (20)
);
TYPE poc_tab is table of poc_rectype;
PROCEDURE poc1_prc (poc_recs IN poc_tab);
END poc1;
/
CREATE OR REPLACE PACKAGE BODY poc1
AS
PROCEDURE poc1_prc (poc_recs IN poc_tab)
IS
BEGIN
FOR i IN 1 .. poc_recs.COUNT
LOOP
DBMS_OUTPUT.PUT_LINE ('poc_recs.id: ' || poc_recs(i).id);
END LOOP;
END poc1_prc;
END poc1;
/
Here's an anonymous block that populates the nested table variable and passes it to the collection for processing:
DECLARE
l_pocs poc1.poc_tab;
BEGIN
SELECT id, name
BULK COLLECT INTO l_pocs
FROM
(
SELECT 100 id, 'Jack' name FROM DUAL UNION ALL
SELECT 101 id, 'Jill' name FROM DUAL
);
poc1.poc1_prc(l_pocs);
END;
/
Output:
-------
poc_recs.id: 100
poc_recs.id: 101
Since you tagged the question with 10g, you might need to add an extra step, and create the record type and nested table as separate variables. Older versions of Oracle couldn't always convert from SQL to PL/SQL types.

Want to MERGE many tables, so want to build generic procedure to achieve this

My terminology will be loose but the point will be clear. I have built a procedure which merges data using Merge Statement. Now my list of tables is growing and I am at a point where I think I need generic function so I just pass table name of source, destination and on condition and can achieve merge.
This will make my code less complex to maintain, otherwise I have to write one procedure per table to make it easy to maintain but still not compact.
Although due to professional reasons a linear code is more efficient as end product but that is less relevant.
Here is my general code.
SET DEFINE OFF;
PROMPT drop Procedure XXX_PROJECTS_MERGE;
DROP PROCEDURE CUSTOM.XXX_PROJECTS_MERGE;
PROMPT Procedure XXX_PROJECTS_MERGE;
/***************************************************************************************************
Prompt Procedure XXX_PROJECTS_MERGE;
--
-- XXX_PROJECTS_MERGE (Procedure)
--
***************************************************************************************************/
CREATE OR REPLACE PROCEDURE CUSTOM.XXX_PROJECTS_MERGE (
errbuf OUT VARCHAR2,
retcode OUT NUMBER,
x_Start_Period_Name IN VARCHAR2)
AS
x_retcode NUMBER := 0;
x_errbuf VARCHAR2 (200) := NULL;
BEGIN
-- Update or insert non transactional tables ------------------------------
-- Refreshing Key Project Table--------------------------------------------------------------------
FND_FILE.PUT_LINE (FND_FILE.LOG, 'Starting Project Table Refresh Process');
MERGE INTO CUSTOM.XXX_PROJECT GPRJ
USING (SELECT ROWID,
PROJECT_ID,
set of columns
FROM PA.PA_PROJECTS_ALL
WHERE ORG_ID = 21
AND LAST_UPDATE_DATE >=
(SELECT MAX (LAST_UPDATE_DATE)
FROM CUSTOM.XXX_PROJECT)) OPRJ
ON (GPRJ.PROJECT_ID = OPRJ.PROJECT_ID AND GPRJ.ROWID = OPRJ.ROWID)
WHEN MATCHED
THEN
UPDATE SET
GPRJ.NAME = OPRJ.NAME,
Above set of columns in above update form
WHEN NOT MATCHED
THEN
INSERT (set of columns)
VALUES (set of values as selected above);
COMMIT;
FND_FILE.PUT_LINE (
FND_FILE.LOG,
'Number of Rows Processed or Merged For XXX_PROJECT Table '
|| TO_CHAR (SQL%ROWCOUNT));
END;
/
SHOW ERRORS;
Now I want above to be generic procedure where I pass different set of inputs and can run for any table.

Using array of Records in 'IN' operator in Oracle

I am having a table Course which have column DepId and Course and some other value. I need to search this table for a some set of (DepId, Course). This set will be decided at runtime.
I want to write a single query in a procedure to fetch all the record pertaining to above set.
For example consider the table has data like
DepId Course ...
------------------
1 A
2 B
3 C
4 D
5 E
6 F
Now only I want to search for below records:
DepId Course ...
------------------
1 A
4 D
What would be most efficient way to write above query?
I was thinking of creating an Array of record and passing it in the 'IN' operator. But I was not able to get any example for this. Can someone guide me on this?
Thanks
Leveraging Oracle Collections to Build Array-typed Solutions
The answer to your question is YES, dimensioned variables such as ARRAYS and COLLECTIONS are viable data types in solving problems where there are multiple values in either or both the input and output values.
Additional good news is that the discussion for a simple example (such as the one in the OP) is pretty much the same as for a complex one. Solutions built with arrays are nicely scalable and dynamic if designed with a little advanced planning.
Some Up Front Design Decisions
There are actual collection types called ARRAYS and ASSOCIATIVE ARRAYS. I chose to use NESTED TABLE TYPES because of their accessibility to direct SQL queries. In some ways, they exhibit "array-like" behavior. There are other trade-offs which can be researched through Oracle references.
The query applied to search the COURSE TABLE would apply a JOIN condition instead of an IN-LIST approach.
The use of a STORED PROCEDURE typed object improves database response. Queries within the procedure call can leverage and reuse already compiled code plus their cached execution plans.
Choosing the Right Collection or Array Type
There are a lot of choices of collection types in Oracle for storing variables into memory. Each has an advantage and some sort of limitation. AskTom from Oracle has a good example and break-down of what a developer can expect by choosing one variable collection type over another.
Using NESTED TABLE Types for Managing Multiple Valued Variables
For this solution, I chose to work with NESTED TABLES because of their ability to be accessed directly through SQL commands. After trying several different approaches, I noticed that the plain-SQL accessibility leads to more clarity in the resulting code.
The down-side is that you will notice that there is a little overhead here and there with respect to declaring an instance of a nested table type, initializing each instance, and managing its size with the addition of new values.
In any case, if you anticipate a unknown number of input variables or values (our output), an array-typed data type (collection) of any sort is a more flexible structure for your code. It is likely to require less maintenance in the end.
The Example: A Stored Procedure Search Query
Custom TYPE Definitions
CREATE OR REPLACE TYPE "COURSE_REC_TYPE" IS OBJECT (DEPID NUMBER(10,0), COURSE VARCHAR2(10));
CREATE OR REPLACE TYPE "COURSE_TBL_TYPE" IS TABLE of course_rec_type;
PROCEDURE Source Code
create or replace PROCEDURE ZZ_PROC_COURSE_SEARCH IS
my_input course_tbl_type:= course_tbl_type();
my_output course_tbl_type:= course_tbl_type();
cur_loop_counter pls_integer;
c_output_template constant varchar2(100):=
'DEPID: <<DEPID>>, COURSE: <<COURSE>>';
v_output VARCHAR2(200);
CURSOR find_course_cur IS
SELECT crs.depid, crs.course
FROM zz_course crs,
(SELECT depid, course
FROM TABLE (CAST (my_input AS course_tbl_type))
) search_values
WHERE crs.depid = search_values.depid
AND crs.course = search_values.course;
BEGIN
my_input.extend(2);
my_input(1):= course_rec_type(1, 'A');
my_input(2):= course_rec_type(4, 'D');
cur_loop_counter:= 0;
for i in find_course_cur
loop
cur_loop_counter:= cur_loop_counter + 1;
my_output.extend;
my_output(cur_loop_counter):= course_rec_type(i.depid, i.course);
end loop;
for j in my_output.first .. my_output.last
loop
v_output:= replace(c_output_template, '<<DEPID>>', to_char(my_output(j).depid));
v_output:= replace(v_output, '<<COURSE>>', my_output(j).course);
dbms_output.put_line(v_output);
end loop;
end ZZ_PROC_COURSE_SEARCH;
Procedure OUTPUT:
DEPID: 1, COURSE: A
DEPID: 4, COURSE: D
Statement processed.
0.03 seconds
MY COMMENTS: I wasn't particularly satisfied with the way the input variables were stored. There was a clumsy kind of problem with "loading" values into the nested table structure... If you can consider using a single search key instead of a composite pair (i.e., depid and course), the problem condenses to a simpler form.
Revised Cursor Using a Single Search Value
This is the proposed modification to the table design of the OP. Add a single unique key id column (RecId) to represent each unique combination of DepId and Course.
Note that the RecId column represents a SURROGATE KEY which should have no internal meaning aside from its property as a uniquely assigned value.
Custom TYPE Definitions
CREATE OR REPLACE TYPE "NUM_TBL_TYPE" IS TABLE of INTEGER;
Remove Array Variable
This will be passed directly through an input parameter from the procedure call.
-- REMOVE
my_input course_tbl_type:= course_tbl_type();
Loading and Presenting INPUT Parameter Array (Nested Table)
The following can be removed from the main procedure and presented as part of the call to the procedure.
BEGIN
my_input.extend(2);
my_input(1):= course_rec_type(1, 'A');
my_input(2):= course_rec_type(4, 'D');
Becomes:
create or replace PROCEDURE ZZ_PROC_COURSE_SEARCH (p_search_ids IN num_tbl_type) IS...
and
my_external_input.extend(2);
my_external_input:= num_tbl_type(1, 4);
Changing the Internal Cursor Definition
The cursor looks about the same. You can just as easily use an IN-LIST now that there is only one search parameter.
CURSOR find_course_cur IS
SELECT crs.depid, crs.course
FROM zz_course_new crs,
(SELECT column_value as recid
FROM TABLE (CAST (p_search_ids AS num_tbl_type))
) search_values
WHERE crs.recid = search_values.recid;
The Actual SEARCH Call and Output
The searching portion of this operation is now isolated and dynamic. It does not need to be changed. All the Changes happen in the calling PL/SQL block where the search ID values are a lot easier to read and change.
DECLARE
my_input_external num_tbl_type:= num_tbl_type();
BEGIN
my_input_external.extend(3);
my_input_external:= num_tbl_type(1,3,22);
ZZ_PROC_COURSE_SEARCH (p_search_ids => my_input_external);
END;
-- The OUTPUT (Currently set to DBMS_OUT)
DEPID: 1, COURSE: A
DEPID: 4, COURSE: D
DEPID: 7, COURSE: G
Statement processed.
0.01 seconds
This is something I havee used in the past in a situation similar to yours. Hopefully it helps.
The main benefit of this method would be that if you only passed it a single paramter it would still return all records for that single parameter. This way a single stored procedure with 5 input parameters could be used to search for all combinations of inputs.
Just call the stored procedure passing in the set and should return all values mathcing the criteria
usp_custom_search '1','A'
usp_custom_search '4','D'
usp_custom_search '4',NULL
usp_custom_search NULL,'A'
etc
Stored Procedure:
CREATE OR REPLACE PROCEDURE custom_search (
dep_id IN VARCHAR2,
course_id IN VARCHAR2,
result_set OUT SYS_REFCURSOR)
BEGIN
query_str VARCHAR2(1000);
query_str := 'SELECT';
query_str := query_str || ' DepId, Course';
query_str := query_str || ' FROM Course';
query_str := query_str || ' WHERE 1=1';
IF (dep_id is not null) then query_str := query_str || ' AND DepId = ''' || dep_id || ''''; END IF;
IF (course_id is not null) then query_str := query_str || ' AND Course = ''' || course_id || ''''; END IF;
open result_set for query_str;
END custom_search;
/

pl/sql stored procedure: parameter name same as column name

I have a Stored Procedure like this
procedure P_IssueUpdate
(
Id in integer,
ModifiedDate in date,
Solution in varchar2
) AS
BEGIN
update T_Issue
Set
ModifiedDate = ModifiedDate,
Solution = Solution
where id = id;
END P_IssueUpdate;
my problem is that the parameter name is the same name as the Table column name.
Is there a way to instruct the sql that the value after the "=" should be the parameter and not the column?
Thanks for your help
You can prefix parameter and variable names with the name of the procedure like this:
SQL> declare
2 procedure p (empno number) is
3 ename varchar2(10);
4 begin
5 select ename
6 into p.ename
7 from emp
8 where empno = p.empno;
9 dbms_output.put_line(p.ename);
10 end;
11 begin
12 p (7839);
13 end;
14 /
KING
PL/SQL procedure successfully completed.
i found a solution. it's working by full qualifying the parameter:
procedure P_IssueUpdate
(
Id in integer,
ModifiedDate in date,
Solution in varchar2
) AS
BEGIN
update T_Issue
Set
ModifiedDate = P_IssueUpdate.ModifiedDate,
Solution = P_IssueUpdate.Solution
where id = P_IssueUpdate.id;
END P_IssueUpdate;
what you described is called variable shadowing. It can happen in any language. You were given good workarounds but the common solution is to design a naming scheme so that it will never happen.
For example, name your columns without prefix and have your variables with a prefix that depends upon their scope (P_ for parameters, L_ for local variables, G_ for global package variables, etc...). This will have the added benefit of making the code more readable by giving you additional information.
RE Vincent's answer about prepending a prefix--that solution works until somebody modifies the table and adds a column whose name happens to collide with the parameter name. Not everybody goes through every line of code to make sure their table modifications won't conflict with variable or parameter names. Oracle's recommendation is to qualify every parameter or variable name in a SQL query.
If you're working with an anonymous block (outside a procedure), you can name the block and qualify variables that way:
<<MY_BLOCK>>
declare
X sys.USER_TABLES%rowtype;
Y sys.USER_TABLES.TABLE_NAME%type := 'some_table_name';
begin
select UT.*
into MY_BLOCK.X
from sys.USER_TABLES UT
where UT.TABLE_NAME = MY_BLOCK.Y;
end MY_BLOCK;

Resources