Is it possible to have constant function parameters in SQL? - sql

Is it possible to have constant function parameters in SQL to make sure the values are not changed later?
Something like this doesn't work:
function my_func(
first_param constant varchar2
, second_param constant varchar2
) return varchar2
is
... -- Rest

That is not necessary as you cannot redefine an IN parameter. For example:
CREATE FUNCTION does_not_work(
a IN NUMBER,
b IN NUMBER
) RETURN NUMBER
IS
BEGIN
IF a < 2 THEN
a := 2;
END IF;
RETURN GREATEST( a, b );
END;
/
(Note: the IN keyword is optional and the default parameter direction; you would get the same error if you declared the signature without the IN keywords.)
Gives:
ORA-24344: success with compilation error
If you look at the errors:
SELECT * FROM USER_ERRORS;
Outputs:
NAME
TYPE
SEQUENCE
LINE
POSITION
TEXT
ATTRIBUTE
MESSAGE_NUMBER
DOES_NOT_WORK
FUNCTION
1
8
5
PLS-00363: expression 'A' cannot be used as an assignment target
ERROR
363
DOES_NOT_WORK
FUNCTION
2
8
5
PL/SQL: Statement ignored
ERROR
0
This tells you that you can't use an IN parameter as an assignment target.

Related

Scope of column names, aliases, and OUT parameters in PL/pgSQL function

I have a hard time understanding why I can refer to the output columns in returns table(col type).
There is a subtle bug in the below code, the order by var refers to res in returns, not to data1 which we aliased to res. res in where is always null and we get 0 rows.
Why can I refer to the column name in output?
In what cases do I want this?
CREATE OR REPLACE FUNCTION public.test(var INTEGER)
RETURNS table(res int )
LANGUAGE plpgsql
AS $function$
begin
return query
select data1 res
from table_with_data
where res < var;
end
$function$
Why can I refer to the column name in output
From the manual, the section about function parameters:
column_name The name of an output column in the RETURNS TABLE syntax. This is effectively another way of declaring a named OUT parameter, except that RETURNS TABLE also implies RETURNS SETOF.
What this means is that in your case res is effectively a writeable variable, which type you plan to return a set of. As any other variable without a default value assigned, it starts off as null.
In what case do I want this
You can return multiple records from a function of this type with a single return query, but another way is by a series of multiple return query or return next - in the second case, filling out the fields in a record of your output table each time. You could have expected a return statement to end the function, but in this scenario only a single return; without anything added would have that effect.
create table public.test_res (data integer);
CREATE OR REPLACE FUNCTION public.test(var INTEGER)
RETURNS table(res int )
LANGUAGE plpgsql
AS $function$
begin
insert into public.test_res select res;--to inspect its initial value later
select 1 into res;
return next;
return next;--note that res isn't reset after returning next
return query select 2;--doesn't affect the current value of res
return next;--returning something else earlier didn't affect res either
return;--it will finish here
select 3 into res;
return next;
end
$function$;
select * from test(0);
-- res
-------
-- 1
-- 1
-- 2
-- 1
--(4 rows)
table public.test_res; --this was the initial value of res within the function
-- data
--------
-- null
--(1 row)
Which is the most useful with LOOPs
CREATE OR REPLACE FUNCTION public.test(var INTEGER)
RETURNS table(comment text,res int) LANGUAGE plpgsql AS $function$
declare rec record;
array_slice int[];
begin
return query select 'return query returned these multiple records in one go', a from generate_series(1,3,1) a(a);
res:=0;
comment:='loop exit when res>4';
loop exit when res>4;
select res+1 into res;
return next;
end loop;
comment:='while res between 5 and 8 loop';
while res between 5 and 8 loop
select res+2 into res;
return next;
end loop;
comment:='for element in reverse 3 .. -3 by 2 loop';
for element in reverse 3 .. -3 by 2 loop
select element into res;
return next;
end loop;
comment:='for <record> in <expression> loop';
for rec in select pid from pg_stat_activity where state<>'idle' loop
select rec.pid into res;
return next;
end loop;
comment:='foreach array_slice slice 1 in array arr loop';
foreach array_slice SLICE 1 in array ARRAY[[1,2,3],[11,12,13],[21,22,23]] loop
select array_slice[1] into res;
return next;
end loop;
end
$function$;
Example results
select * from public.test(0);
-- comment | res
----------------------------------------------------------+--------
-- return query returned these multiple records in one go | 1
-- return query returned these multiple records in one go | 2
-- return query returned these multiple records in one go | 3
-- loop exit when res>4 | 1
-- loop exit when res>4 | 2
-- loop exit when res>4 | 3
-- loop exit when res>4 | 4
-- loop exit when res>4 | 5
-- while res between 5 and 8 loop | 7
-- while res between 5 and 8 loop | 9
-- for element in reverse 3 .. -3 by 2 loop | 3
-- for element in reverse 3 .. -3 by 2 loop | 1
-- for element in reverse 3 .. -3 by 2 loop | -1
-- for element in reverse 3 .. -3 by 2 loop | -3
-- for <record> in <expression> loop | 118786
-- foreach array_slice slice 1 in array arr loop | 1
-- foreach array_slice slice 1 in array arr loop | 11
-- foreach array_slice slice 1 in array arr loop | 21
--(18 rows)
True, OUT parameters (including field names in a RETURNS TABLE (...) clause) are visible in all SQL DML statements in a PL/pgSQL function body, just like other variables. Find details in the manual chapters Variable Substitution and Returning from a Function for PL/pgSQL.
However, a more fundamental misunderstanding comes first here. The syntax of your nested SELECT is invalid to begin with. The PL/pgSQL variable happens to mask this problem (with a different problem). In SQL, you cannot refer to output column names (column aliases in the SELECT clause) in the WHERE clause. This is invalid:
select data1 res
from table_with_data
where res < var;
The manual:
An output column's name can be used to refer to the column's value in
ORDER BY and GROUP BY clauses, but not in the WHERE or HAVING clauses;
there you must write out the expression instead.
This is different for ORDER BY, which you mention in the text, but don't include in the query. See:
GROUP BY + CASE statement
Fixing immediate issue
Could be repaired like this:
CREATE OR REPLACE FUNCTION public.test1(var int)
RETURNS TABLE(res int)
LANGUAGE plpgsql AS
$func$
BEGIN
RETURN QUERY
SELECT data1 AS res -- column alias is just noise (or documentation)
FROM table_with_data
WHERE data1 < var; -- original column name!
END
$func$
fiddle
See:
Real number comparison for trigram similarity
The column alias is just noise in this case. The name of the column returned from the function is res in any case - as defined in the RETURNS TABLE clause.
Aside: It's recommended not to omit the AS keyword for column aliases (unlike table aliases). See:
Query to ORDER BY the number of rows returned from another SELECT
If there was actual ambiguity between column and variable name - say, you declared an OUT parameter or variable named data1 - you'd get an error message like this:
ERROR: column reference "data1" is ambiguous
LINE 2: select data1
^
DETAIL: It could refer to either a PL/pgSQL variable or a table column.
Brute force fix
Could be fixed with a special command at the start of the function body:
CREATE OR REPLACE FUNCTION public.test3(var int)
RETURNS TABLE(data1 int)
LANGUAGE plpgsql AS
$func$
#variable_conflict use_column -- ! to resolve conflicts
BEGIN
RETURN QUERY
SELECT data1
FROM table_with_data
WHERE data1 < var; -- !
END
$func$
See:
Naming conflict between function parameter and result of JOIN with USING clause
Proper fix
Table-qualify column names, and avoid conflicting variable names to begin with.
CREATE OR REPLACE FUNCTION public.test4(_var int)
RETURNS TABLE(res int)
LANGUAGE plpgsql STABLE AS
$func$
BEGIN
RETURN QUERY
SELECT t.data1 -- table-qualify column name
FROM table_with_data t
WHERE t.data1 < _var; -- !
END
$func$
Example:
Calling a PostgreSQL function from Java

ORA-06502: PL/SQL: numeric or value error when concatenating

DECLARE
a NUMBER;
b NUMBER;
BEGIN
a :=: a;
b :=: b;
DBMS_OUTPUT.PUT_LINE('sum = '|| a+b);
END;
I am getting error as numeric or value error
The problem is with operator precedence:
Oracle evaluates operators with equal precedence from left to right within an expression.
... and as the table shows + and || have equal precedence. So in this statement:
DBMS_OUTPUT.PUT_LINE('sum = '|| a+b);
this is interpreted as 'sum = '|| a which gives you a string, then <result string> + b, and string + number will cause the error you see if the string cannot be implicitly converted to a number - and 'sum = 1' can't be.
You can add parentheses to override the default precedence:
DBMS_OUTPUT.PUT_LINE('sum = '|| (a+b));
db<>fiddle
A simplified version to clarify the issue:
begin
dbms_output.put_line('Result ' || 2 + 2);
end;
/
ERROR at line 1:
ORA-06502: PL/SQL: numeric or value error: character to number conversion error
ORA-06512: at line 2
This fails because the expression is built left to right, and you can't add 2 to the string 'Result: 2'.
It would work with multiplication or division, because arithmetic precedence causes those operations to be evaluated first.
begin
dbms_output.put_line('Result ' || 2 / 2);
end;
/
Result 1
PL/SQL procedure successfully completed.
To allow this to work for any operation, you need to bracket the expression:
begin
dbms_output.put_line('Result ' || (2 + 2));
end;
/
Result 4
PL/SQL procedure successfully completed.
Just to add, it is clearer to write host/bind variables without any space after :, for example :myvar rather than : myvar.
You're not seeing a sqldeveloper error, you're getting an error running a sql statement in sqldeveloper, that is something very different.
For simplicity I'm declaring the variables in my example instead of using bind variables like you do:
DECLARE
a NUMBER;
b NUMBER;
BEGIN
a := 1;
b := 2;
DBMS_OUTPUT.PUT_LINE('sum = '||a+b);
END;
Returns
ORA-06502: PL/SQL: numeric or value error: character to number conversion error
ORA-06512: at line 7
The important information is the "line 7". Something is failing in that dbms_output statement and the cause is the operator precedence. Best is to tell the database engine that you want the number concatenated to the string, the error indicates that there is an implicit conversion happening.
The following works just fine:
DECLARE
a NUMBER;
b NUMBER;
BEGIN
a := 1;
b := 2;
DBMS_OUTPUT.PUT_LINE('sum = '|| TO_NUMBER(a+b));
END;
One additional remark. This syntax
a :=: a;
works but is very confusing. I would use a more descriptive name for your bind variable and separate the assignment operator := from the bind variable. Writing it like this is a lot more readable:
a := :variablea;

How to hold negative value into pl/sql variable?

I have a procedure, where i want to declare a variable as number.
This variable is calculated using different columns.
If the output of calculation is negative how can i hold that negative value into that variable? and I keep getting this error: ORA-06502: PL/SQL: numeric or value error: character to number conversion error
declare
out_put_sum number;
begin
with input as(
select x.id,x.name,sum(case when ... then abs(x.quantity) as running_total
from xxxxxx x, yyyyyy y
where ... )
select i.running_total into out_put_sum
from input i;
if out_put_sum > 0 then ...
else ...
end if;
end;
sum(case when......then abs(x.quantity)as
running_total
Here if you are using '-'||abs(x.quantity) to denote negative value then please refrain from doing so. It will create a String out of it. Use -1*abs(x.quantity) instead.

SQL: How to get substring from function output in sql?

IN one of My Application. We have one query, where actully we are calling a function and function returns o/p as String. Any one have idea how to get substring from function returned o/p?
I am using like this
select substr(myfunction(),0,4000) from dual.
I am getting below issue.
ORA-06502: PL/SQL: numeric or value error: character string buffer too
small.
Please help me . Thanks in Advance.
Since you're applying a substr, presumably the value being returned by your function is greater than 4000 characters. If that's the case then you will get this error from SQL, you can't avoid it. It's trying to assign the long string value to an (implicit) SQL-level varchar2, which of course cannot be more than 4000 characters either, before passing that to the substr function.
You will have to add the substr to the return from your function, or if it is sometimes called from somewhere that can handle the long values, you can have a wrapper function that only returns the first 4000 characters - so you can have the appropriate value as needed.
To demonstrate with a dummy function to (inefficiently!) create a large string:
create or replace function myfunction(strlen number) return varchar2 is
str varchar2(32767) := 'X';
begin
while length(str) < least(strlen, 32767) loop
str := str || 'X';
end loop;
return str;
end myfunction;
/
This is fine because the function's output doesn't exceed the SQL varchar2 size:
select length(substr(myfunction(4000),0,4000)) from dual;
LENGTH(SUBSTR(MYFUNCTION(4000),0,4000))
---------------------------------------
4000
But this gets your error because the function's output is too long:
select length(substr(myfunction(4001),0,4000)) from dual;
SQL Error: ORA-06502: PL/SQL: numeric or value error: character string buffer too small
ORA-06512: at "STACKOVERFLOW.MYFUNCTION", line 7
06502. 00000 - "PL/SQL: numeric or value error%s"
With a simple wrapper function as a workaround:
create or replace function trunc_myfunction(strlen number) return varchar2 is
begin
return substr(myfunction(strlen), 0, 4000);
end;
/
select length(substr(trunc_myfunction(4000),0,4000)) from dual;
LENGTH(SUBSTR(TRUNC_MYFUNCTION(4000),0,4000))
---------------------------------------------
4000
select length(substr(trunc_myfunction(4001),0,4000)) from dual;
LENGTH(SUBSTR(TRUNC_MYFUNCTION(4001),0,4000))
---------------------------------------------
4000
select length(substr(trunc_myfunction(32767),0,4000)) from dual;
LENGTH(SUBSTR(TRUNC_MYFUNCTION(32767),0,4000))
----------------------------------------------
4000
Most probably you are trying to store more characters than is allowed in one of the variables that you use in your function. See: PL/SQL: numeric or value error: character string buffer too small %ROWTYPE
Simplest example:
DECLARE
v_varchar2_test VARCHAR2(5);
BEGIN
v_varchar2_test := '123456';
END;
And the error is, like in your case, ORA-06502.
Make sure that the value returned by the function is of the correct type;
I have just tried it like this:
create or replace
FUNCTION MONTH
(DATA IN DATE)
RETURN VARCHAR IS
BEGIN
RETURN TO_CHAR(DATA, 'MM');
END;
and the call:
SELECT substr(MONTH(SYSDATE),0,1) FROM DUAL;
and it worked.

Function/Package issue PL/SQL

I am trying to create a package with four functions. Each function will add a set of numbers and subtract one from the total. I have been having lots of trouble getting the syntax correct. The functions below work on their own, and i try calling the first function at the end.
When I try to create the package i get an error where on line 7, " Encountered the symbol "END" when expecting one of the following: begin function pragma procedure subtype type current cursor delete exists prior 0.05 seconds"
In the package body it says "name is already in use by an existing object". I don't understand as it has to be declared in the specification of the package anyway, and create or replace should solve this if the error is that there is already a package named functionbyfour.
And finally, when I try to use a function in the package, it says " Encountered the symbol "BEGIN" when expecting one of the following: := . ( # % ; not null range default character The symbol ";" was substituted for "BEGIN" to continue. ORA-06550: line 5, column 43: PLS-00103: Encountered the symbol "FROM" when expecting one of the following: . ( * % & = - + ; < / > at in is mod remainder not rem <> or != or ~= >= <= <> and or like like2 like4 likec between || multiset me".
I am using ORACLE EXPRESS edition 11g and am new to PL/SQL(4 weeks).
Any input is greatly appreciated.
CREATE OR REPLACE FUNCTION functionbyfour AS
FUNCTION functone( first number, second number) RETURN NUMBER ;
FUNCTION functtwo( first number, second number, third number) RETURN NUMBER ;
FUNCTION functthree(first number, second number, third number, fourth number) RETURN NUMBER ;
FUNCTION functfour( first number, second number, third number, fourth number,fifth number) RETURN NUMBER ;
END functionbyfour;
/
CREATE OR REPLACE PACKAGE functionbyfour AS
FUNCTION functone (first number, second number ) RETURN number AS total number;
BEGIN
total:=first + second – 1;
RETURN total;
DBMS_OUTPUT.PUT_LINE(total);
END functone;
FUNCTION functtwo (first number, second number, third number ) RETURN number AS total number;
BEGIN
total:=first + second + third – 1;
RETURN total;
DBMS_OUTPUT.PUT_LINE(total);
END functtwo;
FUNCTION functthree (first number, second number,third number, fourth number ) RETURN number AS total number;
BEGIN
total:=first + second + third + fourth – 1;
RETURN total;
DBMS_OUTPUT.PUT_LINE(total);
END functthree;
FUNCTION functfour (first number, second number, third number, fourth number, fifth number ) RETURN number AS total number;
BEGIN
total:=first + second + third + fourth + fifth – 1;
RETURN total;
DBMS_OUTPUT.PUT_LINE(total);
END functfour;
/
BEGIN
SELECT functionbyfour.functone(1,2) FROM DUAL;
END;
/​
You would need to create a package named FunctionByFour (CREATE OR REPLACE PACKAGE)
SQL> ed
Wrote file afiedt.buf
1 CREATE OR REPLACE PACKAGE functionbyfour AS
2 FUNCTION functone( first number, second number) RETURN NUMBER ;
3 FUNCTION functtwo( first number, second number, third number) RETURN NUMBER ;
4 FUNCTION functthree(first number, second number, third number, fourth number) RETURN NUMBER ;
5 FUNCTION functfour( first number, second number, third number, fourth number,fifth number) RETURN NUMBER ;
6* END functionbyfour;
7 /
Package created.
and then a corresponding package body (CREATE OR REPLACE PACKAGE BODY). You'll also need an END for the package body (right now, your code ends at the end of the fourth function)
SQL> ed
Wrote file afiedt.buf
1 CREATE OR REPLACE PACKAGE BODY functionbyfour AS
2 FUNCTION functone (first number, second number ) RETURN number AS total number;
3 BEGIN
4 total:=first + second - 1;
5 RETURN total;
6 DBMS_OUTPUT.PUT_LINE(total);
7 END functone;
8 FUNCTION functtwo (first number, second number, third number ) RETURN number AS total number;
9 BEGIN
10 total:=first + second + third - 1;
11 RETURN total;
12 DBMS_OUTPUT.PUT_LINE(total);
13 END functtwo;
14 FUNCTION functthree (first number, second number,third number, fourth number ) RETURN number AS total number;
15 BEGIN
16 total:=first + second + third + fourth - 1;
17 RETURN total;
18 DBMS_OUTPUT.PUT_LINE(total);
19 END functthree;
20 FUNCTION functfour (first number, second number, third number, fourth number, fifth number ) RETURN number AS total number;
21 BEGIN
22 total:=first + second + third + fourth + fifth - 1;
23 RETURN total;
24 DBMS_OUTPUT.PUT_LINE(total);
25 END functfour;
26* END functionbyfour;
SQL> /
Package body created.
Once you've done that, you can use the function
SQL> SELECT functionbyfour.functone(1,2) FROM DUAL;
FUNCTIONBYFOUR.FUNCTONE(1,2)
----------------------------
2
If you want to put the SELECT statement in a PL/SQL block, you'd need to declare a local variable and do a SELECT INTO to populate the local variable with the result of the function (you could also just assign the local variable the result of the function call without needing to use a SELECT).
SQL> ed
Wrote file afiedt.buf
1 DECLARE
2 l_result NUMBER;
3 BEGIN
4 -- First approach
5 l_result := functionByFour.functOne(1,2);
6 dbms_output.put_line( l_result );
7 -- Second approach
8 SELECT functionByFour.functOne(1,2)
9 INTO l_result
10 FROM dual;
11 dbms_output.put_line( l_result );
12* END;
13 /
2
2
PL/SQL procedure successfully completed.
Also, be aware that putting a DBMS_OUTPUT.PUT_LINE after your RETURN statement is pointless. That code can never be reached. If you want to print the result to the DBMS_OUTPUT buffer, that would need to come before the RETURN.
The line
CREATE OR REPLACE FUNCTION functionbyfour AS
should be
CREATE OR REPLACE PACKAGE functionbyfour AS
The line
CREATE OR REPLACE PACKAGE functionbyfour AS
should be
CREATE OR REPLACE PACKAGE BODY functionbyfour AS
The word second is a key word and you can't use it as a parameter name
You need an
END functionbyfour;
After your END functfour to end the package body
Your dbms_outputs will never be executed as they are after the return
You could do all this in one function
FUNCTION functall(FIRST NUMBER
,sec NUMBER DEFAULT 0
,third NUMBER DEFAULT 0
,fourth NUMBER DEFAULT 0
,fifth NUMBER DEFAULT 0)
RETURN NUMBER
AS
total NUMBER;
BEGIN
total := first + sec + third + fourth + fifth - 1;
dbms_output.PUT_LINE(total);
RETURN total;
END functall;
What a strange thing to want to do? :-)
You are confused about standalone programs and packages.
CREATE FUNCTION can only be used to create a standalone functiion. What you have there should be:
CREATE OR REPLACE PACKAGE functionbyfour AS
A package consists of two parts, a specification and a body. The spec is the public face of the API, the body is the implementation. What you have as the package (spec) is the package body. So change that second chunk of code to start
CREATE OR REPLACE PACKAGE BODY functionbyfour AS
and at least you'll have your program structured correctly.
The Oracle PL/SQL documentation is online, comnprehensive and free. I urge you to read it. Find out more.