I'am try to create function in but have 42601 error code (SQL Error [42601]: ERROR: syntax error at or near "return"). Try to return false and still have error.
create or replace function patient_age_in_range(patient_id uuid, low integer, high integer) returns bool
language sql
immutable
returns null on null input
return (select date_part('year', age(birthday)) between low and high from patients where id = patient_id)
You need some quoting (most people use $-quoteing) and drop the RETURN statement:
CREATE
OR REPLACE FUNCTION patient_age_in_range(patient_id UUID, low INTEGER, high INTEGER)
RETURNS BOOL
LANGUAGE SQL
IMMUTABLE -- are you sure?
RETURNS NULL ON NULL INPUT
AS
$$
SELECT date_part('year', age(birthday))
BETWEEN low AND high
FROM patients
WHERE ID = patient_id
$$;
Usually a function that selects data from a table is labeled as volatile, not immutable.
Related
I have a Postgres table bearing the following form
CREATE TABLE "public"."days"
(
"id" integer NOT NULL,
"day" character varying(9) NOT NULL,
"visits" bigint[] NOT NULL,
"passes" bigint[] NOT NULL
);
I would like to write a function that allows me to return the visits or the passees column as its result for a specified id. My first attempt goes as follows
CREATE OR REPLACE FUNCTION day_entries(INT,TEXT) RETURNS BIGINT[] LANGUAGE sql AS
'SELECT $2 FROM days WHERE id = $1;'
which fails with an error along the lines of
return type mismatch in function declared to return bigint[]
DETAIL: Actual return type is text.
If I put in visits in place of the $2 things work just as expected. It would make little sense to define several functions to match different columns from the days table. Is there a way to pass the actual column name as a parameter while still keeping Postgres happy?
You can't use parameters as identifiers (=column name), you need dynamic SQL for that. And that requires PL/pgSQL:
CREATE OR REPLACE FUNCTION day_entries(p_id int, p_column text)
RETURNS BIGINT[]
AS
$$
declare
l_result bigint[];
begin
execute format('SELECT %I FROM days WHERE id = $1', p_column)
using p_id
into l_result;
return l_result;
end;
$$
LANGUAGE plpgsql;
format() properly deals with identifiers when building dynamic SQL. The $1 is a parameter placeholder and the value for that is passed with the using p_id clause of the execute statement.
I am trying to convert SQL Server stored function to PostgreSQL stored function I am getting one syntactical error at declare #table1 table
CREATE OR REPLACE FUNCTION ETL_GetBuildingDetailsByUserID ( p_nInstID numeric=0)
RETURNS Boolean
AS $$
declare #table1 table
(
nbuilding numeric(18, 0) NOT NULL,
sbuild_name varchar(50) NULL,
sclient_build_id varchar(50) NULL,
nbuilding_inst_id numeric(18, 0) NOT NULL,
ntemp_building_id numeric(18,0) NULL,
nno_of_floors numeric(18,0) NULL
)
declare v_strDeptIds text;
v_strSubDeptIds text;
BEGIN
v_strsql := 'SELECT building.*
FROM building
WHERE (building.nbuilding_inst_id = '|| cast(p_nInstID as varchar(1)) ||')
';
print v_strsql
v_strsql1 text;
v_strsql1 := v_strsql
Insert into #table1; execute sp_executesql; v_strsql1
select * from #table1;
Return true;
END;
$$ LANGUAGE plpgsql;
Error
ERROR: syntax error at or near "#"
LINE 4: declare #table1 table
Can any one please tell what I am doing wrong?
It seems your function actually returns the result of a SELECT query, not a boolean value, so returns boolean is wrong to begin with.
To return a result, you need to declare the function as returns table(). But as you seem to simply return rows from the building table you can define it as returns setof building.
Then remove the useless dynamic SQL which seems completely unnecessary.
In PL/pgSQL there are no table variables, and copying the result of a query into one before returning that result from that table seems to be an unnecessary step which only slows down things. In Postgres you simply return the result of the query, there is no need to store it locally.
Additionally: rather than casting a parameter to another type inside the function it's better to declare that parameter with the type you expect.
So the simplified version of that function in PostgreSQL would be:
CREATE OR REPLACE FUNCTION ETL_GetBuildingDetailsByUserID ( p_nInstID text)
RETURNS setof building
AS $$
select building.*
from building
WHERE building.nbuilding_inst_id = p_nInstID
$$ LANGUAGE sql;
You can use it like this:
select *
from ETL_GetBuildingDetailsByUserID ('42');
Unrelated, but: using numeric(18,0) for columns that store values without decimals is overkill. You should define those columns as bigint. Much faster and uses less space than numeric.
I'm trying to cast a string to a varchar from user input on a web application. I want to change the value of one of the columns given a certain ID (Primary Key) and the column name is what I'm casting as the varchar.
CREATE OR REPLACE FUNCTION changeQuantities(productID varchar, warehouseID int, change int)
RETURNS void AS $$
BEGIN
EXECUTE format('UPDATE warehouses SET CAST(%I AS VARCHAR) = %s WHERE warehouseID = %s', productID, change, warehouseID)
USING change, warehouseID;
END;
$$ LANGUAGE plpgsql;
The 'productID' is the column name, 'change' is the new value, and 'warehouseID' is the primary key for the table. 'warehouses' is the table. Here is the error I receive:
SELECT changeQuantities('bg412',1,100);
ERROR: syntax error at or near "CAST"
LINE 1: UPDATE warehouses SET CAST(bg412 AS VARCHAR) = 100 WHERE war...
^
QUERY: UPDATE warehouses SET CAST(bg412 AS VARCHAR) = 100 WHERE warehouseID = 1
CONTEXT: PL/pgSQL function changequantities(character varying,integer,integer) line 3 at EXECUTE statement
I have another function just like it that uses a SELECT statement while casting the column name and it works just fine. Can I just not cast something after SET? I haven't found anything on this particular case, so I'm either going to be humiliated or I will help someone else out with similar issues. Thanks for any help.
You can't have a cast() on the left side of the assignment - and you don't need it, as the data type of a column is known. If at all you would need to cast the right hand side of an assignment to the data type of the left hand side.
Assuming that bg412 is a column name, you need:
CREATE OR REPLACE FUNCTION changeQuantities(productID varchar, warehouseID int, change int)
RETURNS void AS $$
BEGIN
EXECUTE format('UPDATE warehouses SET %I = %s WHERE warehouseID = %s', productID, change, warehouseID)
USING change, warehouseID;
END;
$$ LANGUAGE plpgsql;
Unrelated, but: using the the ID of a product as a column name in a table seems like a horrible design. What do you do if you have a million products?
I want to execute this function. But it got error said
ERROR:
syntax error at or near ":="
LINE 7: select result:=MAX(path_history_id)as path INTO result from...
In this function I want to:
execute select with (MAX) and it will return maximum id from a table;
catch that value (it is an integer value);
put that value into last select query where condition.
I cant find a way in postgresql to do this.
CREATE OR REPLACE FUNCTION memcache(IN starting_point_p1 character varying, IN ending_point_p1 character varying)
RETURNS TABLE(path integer, movement_id_out integer, object_id_fk_out integer, path_history_id_fk_out integer, walking_distance_out real, angel_out real, direction_out character varying, time_stamp_out timestamp without time zone, x_coordinate_out real, y_coordinate_out real, z_coordinate_out real) AS
$BODY$
DECLARE result int;
BEGIN
select result:=MAX(path_history_id)as path INTO result from path_history_info where starting_point=starting_point_p1 and ending_point =ending_point_p1 and achieve='1';
return query
select * from movement_info where path_history_id_fk=result;
END;
$BODY$
LANGUAGE plpgsql
Syntax Error
The first query inside your function needs to be changed as follows:
select MAX(path_history_id)as path INTO result
from path_history_info
where starting_point=starting_point_p1
and ending_point =ending_point_p1 and achieve='1';
A single Query
You don't actually need a stored procedure for this. A single query can achieve the same result.
select * from movement_info where path_history_id_fk =
(SELECT MAX(path_history_id) FROM path_history_info
where starting_point=starting_point_p1
and ending_point =ending_point_p1 and achieve='1';
How to retrieve rows from table dynamically without using "column definition list"?
I am trying to do same by using polymorphic type "anyelement"(pseudo type) but getting error "structure of query does not match function result type".
For example: I have table called "table1" which consist of following details.
--Table
create table table1
(
slno integer,
fname varchar,
lname varchar,
city varchar,
country varchar
)
--Function
create or replace function function1(column_name varchar,relation_name anyelement)
returns setof anyelement as
$fun$
declare
cname varchar;
add_column varchar;
group_column varchar;
select_query varchar;
begin
if column_name='fname' then
cname:=quote_ident(column_name);
add_column:='"city"'||','||'"country"';
group_column:='"slno"'||','||cname||','||'"city"'||','||'"country"';
else
cname:=quote_ident(column_name);
add_column:='"city"'||','||'"country"';
group_column:='"slno"'||','||cname||','||'"city"'||','||'"country"';
end if;
select_query:='select slno,'||cname||','||add_column||' from '||pg_typeof(relation_name) || 'group by '||group_column;
return query execute select_query;
end;
$fun$
language plpgsql;
---Function call
select * from function1('fname',NULL::table1);
The handling of anyelement as a return type is described in Polymorphic Types:
When the return value of a function is declared as a polymorphic type,
there must be at least one argument position that is also polymorphic,
and the actual data type supplied as the argument determines the
actual result type for that call.
This argument in your case is relation_name typed as anyelement, and by passing NULL::table1, this indeed tells the planner that this particular call of function1 should return SETOF table1. So far so good.
Now the problem is that once executing, the function does not return SETOF table1 but something else. This is not what the executor was expecting, hence the error.
Despite the title of the question being How to return dynamic rows..., what you seem to want is dynamic columns or polymorphic result sets.
And this is an uphill battle with SQL, because in order to build the execution plan of a query, the planner has to know each column with its type for each intermediate result. If you design your query with a function that has to be executed in order to find the structure of its output, that creates a chicken and egg problem: planning must precede execution, it cannot depend on it.
With its dynamic type-infering technique applied to anyelement, PostgreSQL is already pushing the envelope to implement as much polymorphism as possible given this constraint.
That's because if you call your function with the value of NULL::table1 for relation_name, you must return SETOF table1.
Polymorphic arguments and results are tied to each other and are resolved to a specific data type when a query calling a polymorphic function is parsed. Each position (either argument or return value) declared as anyelement is allowed to have any specific actual data type, but in any given call they must all be the same actual type.
http://www.postgresql.org/docs/9.3/static/extend-type-system.html#EXTEND-TYPES-POLYMORPHIC
But you want to return with
(slno integer, fname varchar, city varchar, country varchar)
which is not row of table1 (misses the lname varchar - 3rd column).
If you are willing to call this function with only 'fname' and 'lname', your function can be much simpler:
create or replace function function1(
column_name varchar,
relation_name anyelement
)
returns table (
slno integer,
name varchar,
city varchar,
country varchar
)
language plpgsql as
$fun$
begin
return query execute format(
$sql$
select slno, %1$I AS name, city, country
from %2$I
group by slno, %1$I, city, country
$sql$,
column_name,
pg_typeof(relation_name)
);
end;
$fun$;
This way, you call your function with NULL::table1 for relation_name, but you can use varchar for relation_name too, if you want (that would be more readable, like your column_name parameter).