I created Composite Type
Create Type TestDetailReportType1 As
(
sName text,
cDetailsTimeStamp timestamp,
number text,
dropdi text,
queue text,
agent text,
status int,
reference int
)
I created a cursor which i expect to return a list of composite type ...but no records is returned when i execute select * from TestdetailsCursortest11("abc")
but the query written within the function when executed directly returns 31 row...i am new to postgress so i fail to understand which place i am going wrong while making this function ,really appreciate any guidance at the front.
Note->I specifically want to write a cursor in this scenario...I was successfully able to get result when the function was returning table.
CREATE OR REPLACE FUNCTION public.TestdetailsCursortest11(
hgname text)
RETURNS SETOF TestDetailReportType1
LANGUAGE 'plpgsql'
AS $TestdetailsCursortest11$
DECLARE
cDetailcursor refcursor;
cDetailtEvent RECORD; -- variable to store agent event.
cDetail callDetailReportType1;
BEGIN
OPEN cDetailcursor FOR
select tblUsers.UserName,tblCallEvent.StateCreateDate,tblCallRegister.Cli,tblCallRegister.DDI,tblhuntGroup.name,
tblUsers.Extension,
tblCallEvent.StateID,
tblCallRegister.CallID
from tblCallRegister
inner join tblCallEvent on tblCallRegister.callregisterid= tblCallEvent.callregisterid
inner join tblUsers on tblUsers.userid=tblCallEvent.agentid
inner join tblhuntGroup on tblhuntGroup.HGID=tblCallEvent.HGID
where name=hgname;
FETCH NEXT FROM callDetailcursor INTO callDetailtEvent;
callDetail.sName=callDetailtEvent.UserName;
callDetail.cDetailsTimeStamp=callDetailtEvent.StateCreateDate;
callDetail.number =callDetailtEvent.Cli;
IF callDetailtEvent.StateID = 19
THEN
callDetail.dropdi=callDetailtEvent.DDI;
ELSE
callDetail.dropdi=callDetailtEvent.DDI+1;
END IF;
callDetail.queue=callDetailtEvent.name;
callDetail.agent=callDetailtEvent.Extension;
callDetail.status =callDetailtEvent.StateID;
callDetail.reference=callDetailtEvent.CallID;
RETURN;
CLOSE callDetailcursor;
END;
$TestdetailsCursortest11$;
In a set returning function (a.k.a. table function) you use RETURN not to return a result, but to exit the function.
You use RETURN NEXT <value>; to return a result row. So your function should look similar to this:
DECLARE
cDetail callDetailReportType1;
cDetailtEvent RECORD;
BEGIN
FOR cDetailtEvent IN
SELECT ...
LOOP
cDetail.field1 := ...;
cDetail.field2 := ...;
/* return the next result row */
RETURN NEXT cDetail;
END LOOP;
/*
* This is optional; dropping out from the end
* of a function is an implicit RETURN
*/
RETURN;
END;
The way your function is written it will alwazs return an empty result because there is no RETURN NEXT <value>;.
Related
I am trying to create a complicated pl/pgsql function that gathers some results from a query and then checks each one and returns it or not.
This is my code so far. The record and loop part confuse me.
CREATE FUNCTION __a_search_creator(creator text, ordertype integer, orderdate integer, areaid bigint) RETURNS record
AS $$
DECLARE
fromText text;
whereText text;
usingText text;
firstrecord record;
areageom geometry;
BEGIN
IF areaid IS NOT NULL
THEN
EXECUTE format('SELECT area.geom FROM area WHERE area.id=$1')
INTO areageom
USING areaid;
FOR firstrecord IN
EXECUTE format(
'SELECT place.id, person.name, place.geom
FROM '||fromText||'
WHERE '||whereText)
USING creator, ordertype , orderdate
LOOP
--return only data that the place.geom is inside areageom using PostGIS
END LOOP;
END IF;
RETURN firstrecord;
END;
$$
LANGUAGE plpgsql;
I plan to do some extra checks in the loop, as you can see and RETURN only data that the place.geom (lon/lat) is inside areageom. But since I am new to pl/pgsql, I am creating now the first step, just gathering all the data, put them in a record and return.
My problem is that, no matter what I try I keep getting only one result back. I call select __a_search_creator('johnson',8, 19911109, 20); and I get 1,"seth johnson", 65485,84545 but I know I should be getting another row of results. Is there overwrite happening?
I tried putting RETURN NEXT firstrecord; I tried something like
select place.id from place INTO placeid;
select person.name from person INTO personname;
select place.geom from place INTO placegeom;
firstrecord.id := placeid;
firstrecord.name := personname;
firstrecord.geom := placegeom;
That still brings back just one result, I tried testing just this RAISE NOTICE '%', firstrecord.id; that still brings back just one full set of result.
I don't know how to proceed, please advice.
You declared your function with RETURNS record, so it returns a single record (of unknown type).
Use RETURNS SETOF record to return a set of rows. The manual:
The SETOF modifier indicates that the function will return a set of items, rather than a single item.
Better yet, use RETURNS TABLE with a proper table definition. Like you already had it in your recent questions:
Syntax error in dynamic SQL in pl/pgsql function
Using text in pl/pgsql brings empty set of results
Related (with detailed explanation and links to the manual):
How to return result of a SELECT inside a function in PostgreSQL?
Return setof record (virtual table) from function
Return a query from a function?
This is my solution on the problem
IF areaid IS NOT NULL
THEN
EXECUTE format('SELECT area.geom FROM area WHERE area.id=$1')
INTO areageom
USING areaid;
FOR firstrecord IN
EXECUTE format(
'SELECT place.id, person.name, place.geom FROM '||fromText||' WHERE '||whereText)
USING creator, ordertype, orderdate
LOOP
IF ST_Within(firstrecord.geom , areageom)
THEN
RETURN QUERY VALUES(firstrecord.id, firstrecord.name, firstrecord.geom);
END IF;
END LOOP;
END IF;
RETURN;
Thanks to #Erwin Brandstetter for the useful links.
I work with Postgres9.5. I have an example which successfully returns the results of a cursor as a string (concatenation of rows):
Here is the example:
CREATE OR REPLACE FUNCTION get_doctor_appoint()
RETURNS TEXT AS $$
DECLARE
Names TEXT DEfault '';
rec_appoint RECORD;
doctor_appoint cursor
FOR SELECT * FROM appointments
where doctorAMKA = (SELECT doctoramka FROM doctor WHERE username='foo#bar.net')
AND t>'2017-4-6 00:00:00' AND t<'2017-5-6 00:00:00';
BEGIN
OPEN doctor_appoint;
LOOP
FETCH doctor_appoint INTO rec_appoint;
EXIT WHEN NOT FOUND;
Names:=Names||','||rec_appoint.t||':'||rec_appoint.patientamka;
END LOOP;
CLOSE doctor_appoint;
RETURN Names;
END; $$
LANGUAGE plpgsql;
I would like to return the results as a table but haven't found an example that does that.
You declare the function as RETURNS SETOF text.
For every row you want to return, use RETURN NEXT text_value;.
To end function execution, use RETURN or drop out at the bottom end of the function.
I'm very, very new to writing Postgres functions. I'm writing a function to build a materialized path for a child by searching for parents recursively starting with the child's id. Here's the function I have, but I keep getting the error ERROR: RETURN cannot have a parameter in function returning set
create or replace function build_mp(child_id text)
returns SETOF text
language plpgsql
as $$
begin
select parentid from account where childid = child_id;
if parentid IS NULL then
return ARRAY[child_id];
else
return build_mp(parentid) || ARRAY[child_id];
end if;
end $$;
SELECT build_mp('mychild');
What am I doing wrong?
Edit
Here's the working solution. It takes a child's id, then searches recursively for all parents above it building a material path for the new child item.
create or replace function build_mp(child_id text)
returns text[]
language plpgsql
as $$
declare
parent text;
begin
execute 'select parentid from account where childid = $1' INTO parent USING child_id;
if parent IS NULL THEN
return ARRAY[child_id];
else
return build_mp(parent) || ARRAY[child_id];
end if;
end $$;
SELECT build_mp('mychild') AS mp;
To overcome the "ERROR: query has no destination for result data" error you don't need dynamic SQL.
You can select into a variable directly:
select parentid into parent from account where childid = child_id;
But you can simplify your function by using a recursive CTE and a SQL function. That will perform a lot better especially with a large number of levels:
create or replace function build_mp(child_id text)
returns text[]
language sql
as
$$
with recursive all_levels (childid, parentid, level) as (
select childid, parentid, 1
from account
where childid = child_id
union all
select c.childid, c.parentid, p.level + 1
from account c
join all_levels p on p.parentid = c.childid
)
select array_agg(childid order by level)
from all_levels;
$$;
If you want to return an array of text, you have to declare the function as
... RETURNS text[]
instead of
... RETURNS SETOF text
A set returning function returns a table, while an array is a single value of type text[]. In a set returning function, you would use RETURN NEXT child_id for every row you return and RETURN (without an argument) to terminate function processing.
PostgreSQL complains that you use RETURN value within a set returning function, which is not allowed.
I've declared two functions (duplicate_iofs & duplicate_ioftypes) that both return a table after performing INSERT operations :
CREATE OR REPLACE FUNCTION duplicate_iofs(IN iof_group_src_id integer, IN iof_group_dst_id integer)
RETURNS TABLE(src_id integer, new_id integer) AS $$
BEGIN
-- DO INSERT REQUESTS
END;
CREATE OR REPLACE FUNCTION duplicate_ioftypes(IN iof_group_src_id integer, IN iof_group_dst_id integer)
RETURNS TABLE(src_id integer, new_id integer) AS $$
BEGIN
-- DO INSERT REQUESTS
END;
How can I do something similar like that in a third function :
-- 1. Call duplicate_iofs(...) and store the result table (e.g. iofs_table)
-- 2. Call duplicate_ioftypes(...) and store the result table (e.g. ioftypes_table).
-- 3. Iterate through 'iofs_table' and 'ioftypes_table' with nested loops :
FOR iof_row IN SELECT * FROM iofs_table LOOP
FOR ioftype_row IN SELECT * FROM ioftypes_table LOOP
-- DO SOMETHING
END LOOP;
END LOOP;
Note : duplicate_iofs() and duplicate_ioftypes() MUST be called only once and thus, MUST NOT be called into the nested loop.
You can try something like this:
DECLARE
curs_iof CURSOR FOR SELECT * FROM iofs_table;
curs_ioftype CURSOR FOR SELECT * FROM ioftypes_table;
BEGIN
OPEN SCROLL curs_iof;
OPEN SCROLL curs_ioftype;
FETCH curs_iof INTO iof_row;
WHILE FOUND LOOP
FETCH FIRST FROM curs_ioftype INTO ioftype_row;
WHILE FOUND LOOP
-- DO SOMETHING
FETCH curs_ioftype INTO ioftype_row;
END LOOP;
FETCH curs_iof INTO iof_row;
END LOOP;
CLOSE curs_iof;
CLOSE curs_ioftype;
END;
Details here.
I can't find anything in the PostgreSQL documentation that shows how to declare a record, or row, while declaring the tuple structure at the same time. If you don't define you tuple structure you get the error "The tuple structure of a not-yet-assigned record is indeterminate".
This is what I'm doing now, which works fine, but there must be a better way to do it.
CREATE OR REPLACE FUNCTION my_func()
RETURNS TABLE (
"a" integer,
"b" varchar
) AS $$
DECLARE r record;
BEGIN
CREATE TEMP TABLE tmp_t (
"a" integer,
"b" varchar
);
-- Define the tuple structure of r by SELECTing an empty row into it.
-- Is there a more straight-forward way of doing this?
SELECT * INTO r
FROM tmp_t;
-- Now I can assign values to the record.
r.a := at.something FROM "another_table" at
WHERE at.some_id = 1;
-- A related question is - how do I return the single record 'r' from
-- this function?
-- This works:
RETURN QUERY
SELECT * FROM tmp_t;
-- But this doesn't:
RETURN r;
-- ERROR: RETURN cannot have a parameter in function returning set
END; $$ LANGUAGE plpgsql;
You are mixing the syntax for returning SETOF values with syntax for returning a single row or value.
-- A related question is - how do I return the single record 'r' from
When you declare a function with RETURNS TABLE, you have to use RETURN NEXT in the body to return a row (or scalar value). And if you want to use a record variable with that it has to match the return type. Refer to the code examples further down.
Return a single value or row
If you just want to return a single row, there is no need for a record of undefined type. #Kevin already demonstrated two ways. I'll add a simplified version with OUT parameters:
CREATE OR REPLACE FUNCTION my_func(OUT a integer, OUT b text)
AS
$func$
BEGIN
a := ...;
b := ...;
END
$func$ LANGUAGE plpgsql;
You don't even need to add RETURN; in the function body, the value of the declared OUT parameters will be returned automatically at the end of the function - NULL for any parameter that has not been assigned.
And you don't need to declare RETURNS RECORD because that's already clear from the OUT parameters.
Return a set of rows
If you actually want to return multiple rows (including the possibility for 0 or 1 row), you can define the return type as RETURNS ...
SETOF some_type, where some_type can be any registered scalar or composite type.
TABLE (col1 type1, col2 type2) - an ad-hoc row type definition.
SETOF record plus OUT parameters to define column names andtypes.
100% equivalent to RETURNS TABLE.
SETOF record without further definition. But then the returned rows are undefined and you need to include a column definition list with every call (see example).
The manual about the record type:
Record variables are similar to row-type variables, but they have no
predefined structure. They take on the actual row structure of the
row they are assigned during a SELECT or FOR command.
There is more, read the manual.
You can use a record variable without assigning a defined type, you can even return such undefined records:
CREATE OR REPLACE FUNCTION my_func()
RETURNS SETOF record AS
$func$
DECLARE
r record;
BEGIN
r := (1::int, 'foo'::text); RETURN NEXT r; -- works with undefined record
r := (2::int, 'bar'::text); RETURN NEXT r;
END
$func$ LANGUAGE plpgsql;
Call:
SELECT * FROM my_func() AS x(a int, b text);
But this is very unwieldy as you have to provide the column definition list with every call. It can generally be replaced with something more elegant:
If you know the type at time of function creation, declare it right away (RETURNS TABLE or friends).
CREATE OR REPLACE FUNCTION my_func()
RETURNS SETOF tbl_or_type AS
$func$
DECLARE
r tbl_or_type;
BEGIN
SELECT INTO tbl_or_type * FROM tbl WHERE id = 10;
RETURN NEXT r; -- type matches
SELECT INTO tbl_or_type * FROM tbl WHERE id = 12;
RETURN NEXT r;
-- Or simpler:
RETURN QUERY
SELECT * FROM tbl WHERE id = 14;
END
$func$ LANGUAGE plpgsql;
If you know the type at time of the function call, there are more elegant ways using polymorphic types:
Refactor a PL/pgSQL function to return the output of various SELECT queries
Your question is unclear as to what you need exactly.
There might be some way that avoids the explicit type declaration, but offhand the best I can come up with is:
CREATE TYPE my_func_return AS (
a integer,
b varchar
);
CREATE OR REPLACE FUNCTION my_func()
RETURNS my_func_return AS $$
DECLARE
r my_func_return;
BEGIN
SELECT 1, 'one' INTO r.a, r.b;
RETURN r;
END; $$ LANGUAGE plpgsql;
Oh, I almost forgot the simplest way to do this:
CREATE OR REPLACE FUNCTION my_func2(out a int, out b text)
RETURNS RECORD AS $$
BEGIN
SELECT 1, 'one' INTO a, b;
RETURN;
END; $$ LANGUAGE plpgsql;
It is much easier to use OUT parameters rather than a record. If iteratively building a set of records (a table) use RETURN NEXT. If generating from a query, use RETURN QUERY. See:
https://stackoverflow.com/a/955289/398670
and:
http://www.postgresql.org/docs/current/static/plpgsql-declarations.html
http://www.postgresql.org/docs/current/static/sql-createfunction.html
http://www.postgresonline.com/journal/archives/129-Use-of-OUT-and-INOUT-Parameters.html
Think:
CREATE OR REPLACE FUNCTION my_func(OUT a integer, OUT b varchar) RETURNS SETOF RECORD AS $$
BEGIN
-- Assign a and b, RETURN NEXT, repeat. when done, RETURN.
END;
$$ LANGUAGE 'plpgsql';