"stable or volatile function is used as if it is immutable" warning in Postgresql function - sql

I have a Postgresql function in which there is only a SELECT statement:
CREATE OR REPLACE FUNCTION fun_test()
RETURNS INTEGER AS $$
DECLARE size INTEGER;
BEGIN
SELECT COUNT(*) INTO size FROM tab;
RETURN size;
END;
$$ LANGUAGE plpgsql STABLE;
When I call the function with:
SELECT fun_test()
Although the result are correct, there will be a warning too:
WARNING: A stable or volatile function is used as if it is immutable
HINT: The function should be declared as stable or volatile in create function statement.
I found in Postgresql document that STABLE is a appropriate selection for functions whose results depend on database lookups, parameter variables (such as the current time zone), etc. http://www.postgresql.org/docs/8.2/static/sql-createfunction.html
My question is where the warning comes from? It seems that I am doing what the document requires to do. Any help is appreciated.
EDIT:
I am using postgresql server 8.2.15
The whole story:
CREATE TABLE algo.chengb_tmp
(
userid INT,
username varchar(100)
)
CREATE OR REPLACE FUNCTION algo.chengb_fun_test()
RETURNS INTEGER AS $$
DECLARE size INTEGER;
BEGIN
SELECT COUNT(*) INTO size FROM algo.chengb_tmp;
RETURN size;
END;
$$ LANGUAGE plpgsql STABLE;
SELECT algo.chengb_fun_test()
cheng

Obviously, your question does not show the whole story. I tested your function in PostgreSQL 9.1 and it works for me, as expected. No warning.
Possible explanations include:
A RULE on SELECT on the involved table tab that calls another function. (There are no triggers for SELECT)
A bug in the outdated PostgreSQL version 8.2.15, which might go away with an upgrade.
You over-simplified the question and abstracted the actual cause of the problem away.
Add more details in the question: your Version of PostgreSQL, the complete definition of table tab, the complete error message and its context.
As an aside, could be simplified:
CREATE OR REPLACE FUNCTION fun_test()
RETURNS INTEGER AS
$$
BEGIN
RETURN
(SELECT COUNT(*) FROM tab);
END;
$$ LANGUAGE plpgsql STABLE;
Ore even:
CREATE OR REPLACE FUNCTION fun_test()
RETURNS INTEGER AS
$$
SELECT count(*) FROM tab;
$$ LANGUAGE sql STABLE;
But that's probably not the point here.

Related

Using variables from parent in nested postgresql function?

I'm have a pretty long SQL routine that gets a few parameters and runs through a whole bunch of CASE statements. I have the following structure:
DO $$
DECLARE var INTEGER = "someColumn" FROM TABLE LIMIT 1;
BEGIN;
CREATE OR REPLACE FUNCTION pg_temp.fn(var2 boolean) returns decimal AS
$fn$ SELECT CASE WHEN var2 THEN var::decimal ELSE 0::decimal END $fn$ language sql;
$$
And using var inside of fn does not seem to quite work. As in the column does not exist. Is there a way to make it work, am I maybe thinking too complicated?
Edit: fixed the type that was missing. In the original code there is a type declaration, the declaration is not the problem. Using the variable in the nested function is.
First of all. THIS IS NOT THE BEST PRACTICE. It can be done, but it is not recommended to use nested functions.
The problem is that var is in a different scope than the function. You have to "write" the function as an independent unit. You can do this with EXECUTE and FORMAT.
Here is an example of a function that shows var as a message:
DO $$
DECLARE
sql text; -- To "write" the function
var integer := id FROM table_name ORDER BY id LIMIT 1;
BEGIN
-- To make it hapend I use $test$ to set the function.
-- FORMAT is to allocate var into the function through '%s'.
-- you have to use the correct sintax acording to the data type.
sql := FORMAT($test$
CREATE OR REPLACE FUNCTION test() RETURNS void AS $FUNCTION$
BEGIN
RAISE NOTICE '%s';
END; $FUNCTION$ LANGUAGE plpgsql
$test$,var);
EXECUTE sql;
END; $$

Function returns bad value inside of a trigger

I have two functions that return the good value. But when I call those functions inside of a trigger they always returns 0 instead of the good value.
The return type of those functions is real. The direct and dramatic consequence is that the trigger inserts wrong values in tables when it is called.
The function:
create or replace function get_remaining_hour(id_user_v integer,id_absence_v_type integer,id_year_v integer) returns real as
$BODY$
BEGIN
return (select sum(number_hour)
from remaining_absence_day
where id_user= $1
and id_absence_type=$2
and id_year=$3 );
END;
$BODY$
LANGUAGE 'plpgsql' ;
The trigger function (modified for testing!):
create OR REPLACE function update_absence() returns TRIGGER AS
$BODY$
DECLARE
old_number_hour real;
BEGIN
old_number_hour:=get_remaining_hour(3,2,8);
insert into debugging(col,val) values('old_number_hour', old_number_hour);
return null;
END;
$BODY$
LANGUAGE 'plpgsql' ;
The trigger definition:
drop trigger if exists update_absence on absence;
CREATE TRIGGER update_absence
after update of type,duration_hour,duration_day on absence
for each ROW
execute procedure update_absence();
The presented code should work.
It is particularly odd that you see 0 as result. If no matching row is found in remaining_absence_day, you would see NULL, not 0. But if you call the function with the same parameters in the same environment you should see the same result to begin with.
The remaining possible explanation I can think of: confusion with the schema search path. Like: you have a second instance of the function get_remaining_hour() or the table remaining_absence_day in a different schema. And you call the function with a different setting for search_path.
Did you run your comparison in the same session?
How does the search_path influence identifier resolution and the "current schema"
Or, since you work with an AFTER trigger: there might be other triggers on table absence that modify the table remaining_absence_day, which are fired before your trigger.
All other modifications I made are of cosmetic nature or minor simplifications.
CREATE OR REPLACE FUNCTION get_remaining_hour(id_user_v int
, id_absence_v_type int
, id_year_v int)
RETURNS real AS
$func$
BEGIN
RETURN (
SELECT sum(number_hour)
FROM remaining_absence_day -- referencing the right table? see search_path
WHERE id_user = $1
AND id_absence_type = $2
AND id_year = $3
);
END
$func$ LANGUAGE plpgsql STABLE; -- don't quote the language name
CREATE OR REPLACE FUNCTION update_absence()
RETURNS TRIGGER AS
$func$
BEGIN
INSERT INTO debugging(col, val)
VALUES('old_number_hour', get_remaining_hour(3,2,8)); -- hard coded only for testing?
RETURN null; -- only good for AFTER trigger
END
$func$ LANGUAGE plpgsql;
DROP TRIGGER IF EXISTS update_absence ON absence;
CREATE TRIGGER update_absence
AFTER UPDATE OF type, duration_hour, duration_day ON absence
FOR EACH ROW EXECUTE PROCEDURE update_absence();

PostgreSQL: No function matches the given name and argument types

I have the following procedure:
DROP FUNCTION presAdress();
CREATE FUNCTION presadress() RETURNS VARCHAR(100) AS $$
DECLARE studioName text;
BEGIN
RETURN (
SELECT address AS pres_address
FROM MovieExec
WHERE cert# IN (
SELECT presC#
FROM Studio
WHERE name = studioName)
);
END;
$$ LANGUAGE plpgsql;
I try to run the procedure:
select presadress('Paramount');
But I get the following error message:
ERROR: function presadress(text) does not exist
SQL state: 42883
Hint: No function matches the given name and argument types. You might need to add explicit type casts.
Character: 294
I suspect that this is because there is some kind of error regarding the in parameters of the procedure, but I have been unable to find a solution.
Use a function parameter, like #Gordon demonstrates, but you don't need plpgsql for this at all. And the query can simplified (shorter, faster):
CREATE FUNCTION presadress(_studioname text)
RETURNS text AS
$$
SELECT m.address
FROM studio s
JOIN movieexec m ON m.cert# = s.presc#
WHERE s.name = _studioname
$$ LANGUAGE sql STABLE;
Function volatility can be STABLE.
Related:
How do IMMUTABLE, STABLE and VOLATILE keywords effect behaviour of function?
Difference between language sql and language plpgsql in PostgreSQL functions
PostgreSQL Stored Procedure Performance
I think you want a declaration more like this:
CREATE FUNCTION presadress (v_studioName text)
RETURNS VARCHAR(100) AS $$
BEGIN
RETURN(SELECT address AS pres_address
FROM MovieExec
WHERE cert# IN (SELECT presC#
FROM Studio
WHERE name = v_studioName)
);
END;
$$ LANGUAGE plpgsql;

Return set of records with unknown table_name

I want to return a row from a table with this function (I don't know the name of the table, it's random)
CREATE FUNCTION foo( text ) RETURNS setof record AS $$
DECLARE
table_name ALIAS FOR $1;
BEGIN
SELECT * from table_name ;
END
$$ LANGUAGE plpgsql;
and then I want to do this:
select col1,col2 from foo('bar');
Any ideas?
SQL demands to know the return type at call time. And functions require you to define a return type as well. What you are after is not trivial.
If you don't know the return type at call time, you are basically out of luck. You cannot solve the problem with a single function call.
If you know the type at call time, there is an option with polymorphic types and dynamic SQL with EXECUTE:
CREATE OR REPLACE FUNCTION f_data_of_table(_tbl_type anyelement)
RETURNS SETOF anyelement AS
$func$
BEGIN
RETURN QUERY EXECUTE
'SELECT * FROM ' || pg_typeof(_tbl_type);
END
$func$ LANGUAGE plpgsql;
Call:
SELECT * FROM f_data_of_table(NULL::my_table_name);
Details in this related answer:
Refactor a PL/pgSQL function to return the output of various SELECT queries
Be wary of SQL injection:
Table name as a PostgreSQL function parameter
Only makes sense if you do more than just SELECT * FROM tbl, or you'd simply use the SQL command.
Aside:
Do not use ALIAS to attach names to parameter values. That's outdated and discouraged. Use named parameters instead.

Are there any way to execute a query inside the string value (like eval) in PostgreSQL?

I want to do like this:
SELECT (EVAL 'SELECT 1') + 1;
Are there any way to do like this (EVAL) in PostgreSQL?
If the statements you are trying to "eval" always return the same data type, you could write an eval() function that uses the EXECUTE mentioned by Grzegorz.
create or replace function eval(expression text) returns integer
as
$body$
declare
result integer;
begin
execute expression into result;
return result;
end;
$body$
language plpgsql
Then you could do something like
SELECT eval('select 41') + 1;
But this approach won't work if your dynamic statements return something different for each expression that you want to evaluate.
Also bear in mind that this opens a huge security risk by running arbitrary statements. If that is a problem depends on your environment. If that is only used in interactive SQL sessions then it isn't a problem.
NOTES
The language PLpgSQL syntax have many ways to say:
Y := f(X);
The EXECUTE clause is only for "dynamic execution" (less performance),
EXECUTE 'f(X)' INTO Y;
Use Y := f(X); or SELECT for execute static declarations,
SELECT f(X) INTO Y;
Use PERFORM statment when discard the results or to work with void returns:
PERFORM f(X);
I'd go with data type text since it's more flexible using casting operators like ::int if needed:
create or replace function eval( sql text ) returns text as $$
declare
as_txt text;
begin
if sql is null then return null ; end if ;
execute sql into as_txt ;
return as_txt ;
end;
$$ language plpgsql
-- select eval('select 1')::int*2 -- => 2
-- select eval($$ select 'a'||1||'b' $$) -- => a1b
-- select eval( null ) -- => null
I also added this and another eval( sql, keys_arr, vals_arr ) function supporting some custom key-value substitutions, e.g. for handy :param1 substitutions to postgres-utils
I am not sure if it suits you but PostgreSQL has EXECUTE statement.
Good idea. You can modify to perform direct expressions:
create or replace function eval(expression text) returns integer
as
$body$
declare
result integer;
begin
execute 'SELECT ' || expression into result;
return result;
end;
$body$
language plpgsql;
To run just type this:
SELECT eval('2*2');
Assuming that most sql queries are a part of a bigger system, there mostly will be cases where you form a query with your backend code and then execute it.
So if that’s the case for you, you can just use subselects or common table expressions that are put into your query string by the backend code before execution.
I have trouble coming up with cases where the solution you want works and my solution doesn’t, apart from not having any backend app, of course.