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

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;

Related

Why parameter with default value is null?

I just want to ask a simple question, I have a procedure in my package: procedure(p_x in default sysdate). However, when i try to use this p_x to do query in my procdure, i got nothing from my table. My assumption is p_x does not get value from sysdate and it is null. Why does this happen? How can I fix it?
Thanks in advance.
In Package:
Procedure M_m(p_x in default sysdate)
In Package Body:
Procedure M_m(p_x in default sysdate) as
cursor cur is select manager_id, manager_name,manger_department
from employ_table where trunc(EmploymentDate)=trunc(p_x);
r_cur cur%rowtype;
Begin
for r_cur in cur
loop
DBMS_OUTPUT.PUT_LINE(employ_table.manager_id || employ_table.manager_name
|| employ_table.manager_department);
end loop;
end;
I guess Wernfried is right, it depends on how exactly you call it. Just add a DBMS_OUTPUT.PUT_LINE(p_x) immediately before the FOR to verify the value of the variable:
CREATE TABLE employ_table AS SELECT * FROM scott.emp;
UPDATE employ_table SET hiredate=SYSDATE WHERE ename='SCOTT';
CREATE OR REPLACE PACKAGE P IS
PROCEDURE M_m(p_x IN DATE DEFAULT sysdate);
END;
/
CREATE OR REPLACE PACKAGE BODY p IS
PROCEDURE M_m(p_x IN DATE DEFAULT sysdate) IS
CURSOR cur IS
SELECT * FROM employ_table WHERE TRUNC(hiredate)=TRUNC(p_x);
BEGIN
DBMS_OUTPUT.PUT_LINE('p_x='||p_x);
FOR r_cur IN cur LOOP
DBMS_OUTPUT.PUT_LINE(r_cur.mgr);
END LOOP;
END M_m;
END P;
/
EXEC P.m_m(DATE '1980-12-17');
p_x=1980-12-17 00:00:00
7902
EXEC P.m_m();
p_x=2018-05-26 17:50:11
7566
EXEC P.m_m(NULL);
p_x=
Currently (as of 18c) the default or := clause in a PLSQL parameter specification behaves like a pre-12c default clause in a table column definition. That is, the default value is only applied when the corresponding column is not specified at all.
If you define a table column as
somedate date default sysdate
then it will only be assigned the default value if the insert statement does not mention column somedate at all. If the insert sets somedate to null then it will be null, regardless of any default. The same behaviour is true of PL/SQL parameters. An explicit null value passed in overrides any default that you set for a parameter.
You will need to define a local constant along the lines of
k_x constant date := coalesce(p_x,sysdate);
and then have the rest of the procedure refer to that instead of the parameter.
In 12c we have default on null for table columns, but in my opinion PL/SQL lags behind, as it has no equivalent syntax for parameter defaults. If you pass null to a PL/SQL parameter, it respects that null and not any default that you specified, as it has no on null syntax option.
If you would like Oracle to extend PL/SQL parameter defaults along the same lines as table columns then consider voting for my suggestion here:
https://community.oracle.com/ideas/18189

Can I know the number of parameters passed in stored procedure in Oracle PL/SQL

I have a stored procedure in a Oracle Database that receives 3 parameters.
I know that I call it with 1 to 3 parameters but it's possible to know inside itself how many arguments are the defaults or are really passed?
For example:
dummy(1) some keyword say me "1"
dummy(1,2,3) say me "3"
I ask this because I worked with Informix 4GL and I could use "NARGS" to know the number of arguments that I receive.
The short answer is no.. there is not an equivalent to NARGS or "C"s argc.
if you are using null default values you could manually count the number of arguments that do no equal the default value.. but that wont tell you if you explicitly pass the default value as a parameter.
i can think of 2 solutions.
1. user overloaded procedures .. ie
procedure a (p_1 number);
procedure a (p_1 number, p_2 number);
procedure a (p_1 number, p_2 number, p_3 number);
then in the bodies you would "know" by which one you are in.
option 2. pass a varray/plsql table as a single argument but then actually passing the arguments becomes problematic.
create or replace package x
is
type an_arg is record ( n number, v varchar2(2000), d date);
type args is table of an_arg;
procedure a(argv args);
end;
/
create or replace package body x
is
procedure a(argv args)
is
begin
dbms_output.put_line('i was passed '||argv.count||' arguments');
end;
end;
/
Similar to ShoeLace's answer, I think counting the number of parameters that do not equal the default of each parameter would work.
The key to this is to give each parameter a nonsensical default value. Like '~#dummee_v#1u3#~' maybe. Anything you are confident will never actually be passed in. This way you don't have to worry about somebody passing in a parameter value that equals the default value.
So:
create procedure p1 (id1 varchar2 default '~#dummee_v#1u3#~', id2 varchar2 default '~#dummee_v#1u3#~') is
lParamCount number := 0;
lDummyParamValue varchar2 := '~#dummee_v#1u3#~';
begin
if id1 <> lDummyParamValue then lParamCount := lParamCount + 1;
if id2 <> lDummyParamValue then lParamCount := lParamCount + 1;
end p1;

Update statement inside oracle stored procedure is not working

I have one simple update statement inside oracle stored procedure. Its executing successfully but its not updating the table.
CREATE OR REPLACE PROCEDURE UpdateSourceLog
( SourceLogId IN NUMBER, TotalRowCount IN INT,Status IN VARCHAR)
AS
BEGIN
UPDATE SourceLog
SET Status = Status,
TotalRowCount = TotalRowCount,
EndTime = SYSDATE
WHERE SourceLogId = SourceLogId;
COMMIT;
END;
I have tried with changing the perameter name different from column name. Then also Its not working.
And I have tried with anonymous block. I'm not able to find out the isue. Please help me in this regard.
Thanks!
It is a bad practice to give parameters the same name as table columns.
So you should change it:
CREATE OR REPLACE PROCEDURE UpdateSourceLog
( p_SourceLogId IN NUMBER, p_TotalRowCount IN INT,p_status IN VARCHAR)
AS
BEGIN
UPDATE SourceLog
SET Status = p_status,
TotalRowCount = p_TotalRowCount,
EndTime = SYSDATE
WHERE SourceLogId = p_SourceLogId;
COMMIT;
END;
Because for now, most likely, Oracle understands it as column names and just update column to value from this column (no sense at all)

Oracle dynamic PL/SQL. I cant get it to work

So here is my code:
CREATE OR REPLACE PROCEDURE UPDATE_USER
(
updateColumn IN USERS.column_name%type,
changeStr IN VARCHAR2,
unID IN VARCHAR2
)
IS
BEGIN
EXECUTE IMMEDIATE
'UPDATE
users
SET :1 = :2
WHERE
uniqueID = :3'
USING updateColumn, changeStr, unID;
END;
/
I've searched for other answers on this and as far as I can tell this should work. However I get the error:
'Error(3,25): PLS-00302: component 'COLUMN_NAME' must be declared'
Thanks.
The error message specifies line 3, character 25, which points to column_name in the declaration of the updateColumn parameter. It appears that you are trying to pass the column name to update as a parameter, but that means that at compile time the column isn't known, so its type can't be known. This also doesn't really make sense - if it's a number column then you'd be trying to pass the column name into a numeric parameter, which wouldn't work anyway. If you don't want to declare it as a simple varchar2, you could instead use user_tab_columns.column_name%type.
But you can't dynamically set the column name in the update statement using a bind variable. It would compile, but would get an ORA-01747 on execution from the apparent name starting with a colon. You'd need to concatenate it, something like:
CREATE OR REPLACE PROCEDURE UPDATE_USER
(
updateColumn IN user_tab_columns.column_name%type,
changeStr IN VARCHAR2,
unID IN VARCHAR2
)
IS
BEGIN
EXECUTE IMMEDIATE
'UPDATE
users
SET ' || updateColumn || ' = :1
WHERE
uniqueID = :2'
USING changeStr, unID;
END;
/
But you'd need to sanitise the column name to avoid SQL injection. APC's answer to the question you linked to mentions using the DBMS_ASSERT package, for example.

ORACLE stored procedure returning too many rows

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.

Resources