Postgresql error a column definition list is required for functions returning record

CREATE OR REPLACE FUNCTION transformServers(data jsonb) RETURNS SETOF record as $$ DECLARE r record; ...
CREATE OR REPLACE FUNCTION transformServers(data jsonb)
RETURNS SETOF record  as $$                        
DECLARE                                            
r record;                                          

BEGIN                                              
for r in select.serversSet.id,serversSet.hostname from jsonb_to_recordset(data->'data') AS           
 serversSet (                                       
 id bigint,                                         
 hostname text,                                                                                                                                  
) loop                                             
return next r;                                     
end loop;                                          
return;  
 END;
 $$ language plpgsql;

I run the above code, got an error saying » a column definition list is required for functions returning «record»

asked Feb 18, 2015 at 5:27

Xiujiao Gao's user avatar

1

Problem solved! Actually the problem is in another function, where I call transformServers(json).

Each time we call a function which returns setof record, we need to specify the columns, so when we call transformServers(json) we have to do it as

 select * from transformServers(json) as f(id bigint, hostname text) 

Before, I did

 select * from transformServers(json)

which does not work.

Roland Ewald's user avatar

Roland Ewald

4,5803 gold badges35 silver badges49 bronze badges

answered Feb 18, 2015 at 6:11

Xiujiao Gao's user avatar

Return selected columns

CREATE OR REPLACE FUNCTION get_user_by_username(_username text
                                              , _online bool DEFAULT false)
  RETURNS TABLE (
    user_id int
  , user_name varchar
  , last_activity timestamptz
  )
  LANGUAGE plpgsql AS
$func$
BEGIN
   IF _online THEN
      RETURN QUERY
      UPDATE users u 
      SET    last_activity = current_timestamp  -- ts with time zone
      WHERE  u.user_name = _username
      RETURNING u.user_id
              , u.user_name
              , u.last_activity;
   ELSE
      RETURN QUERY
      SELECT u.user_id
           , u.user_name
           , u.last_activity
      FROM   users u
      WHERE  u.user_name = _username;
   END IF;
END
$func$;

Call:

SELECT * FROM get_user_by_username('myuser', true);

You had DECLARE result record; but didn’t use the variable. I deleted the cruft.

You can return the record directly from the UPDATE, which is much faster than calling an additional SELECT statement. Use RETURN QUERY and UPDATE with a RETURNING clause.

If the user is not _online, default to a plain SELECT. This is also the (safe) default if the second parameter is omitted — which is only possible after providing that default with DEFAULT false in the function definition.

If you don’t table-qualify column names (tablename.columnname) in queries inside the function, be wary of naming conflicts between column names and named parameters, which are visible (most) everywhere inside a function.
You can also avoid such conflicts by using positional references ($n) for parameters. Or use a prefix that you never use for column names: like an underscore (_username).

If users.username is defined unique in your table, then LIMIT 1 in the second query is just cruft. If it is not, then the UPDATE can update multiple rows, which is most likely wrong. I assume a unique username and trim the noise.

Define the return type of the function (like @ertx demonstrated) or you have to provide a column definition list with every function call, which is awkward.

Creating a type for that purpose (like @ertx proposed) is a valid approach, but probably overkill for a single function. That was the way to go in old versions of Postgres before we had RETURNS TABLE for that purpose — like demonstrated above.

You do not need a loop for this simple function.

Every function needs a language declaration. LANGUAGE plpgsql in this case.

I use timestamptz (timestamp with time zone) instead of timestamp (timestamp without time zone), which is the sane default. See:

  • Ignoring time zones altogether in Rails and PostgreSQL

Return (set of) whole row(s)

To return all columns of the existing table users, there is a simpler way. Postgres automatically defines a composite type of the same name for every table. Just use RETURNS SETOF users to vastly simplify the query:

CREATE OR REPLACE FUNCTION get_user_by_username(_username text
                                              , _online bool DEFAULT false)
  RETURNS SETOF users
  LANGUAGE plpgsql AS
$func$
BEGIN
   IF _online THEN
      RETURN QUERY
      UPDATE users u 
      SET    last_activity = current_timestamp
      WHERE  u.user_name = _username
      RETURNING u.*;
   ELSE
      RETURN QUERY
      SELECT *
      FROM   users u
      WHERE  u.user_name = _username;
   END IF;
END
$func$;

Return whole row plus custom addition

To address the question added by TheRealChx101 in a comment below:

What if you also have a calculated value in addition to a whole table? 😑

Not as simple, but doable. We can send the whole row type as one field, and add more:

CREATE OR REPLACE FUNCTION get_user_by_username3(_username text
                                               , _online bool DEFAULT false)
  RETURNS TABLE (
    users_row users
  , custom_addition text
  )
  LANGUAGE plpgsql AS
$func$
BEGIN
   IF _online THEN
      RETURN QUERY
      UPDATE users u 
      SET    last_activity = current_timestamp  -- ts with time zone
      WHERE  u.user_name = _username
      RETURNING u  -- whole row
              , u.user_name || u.user_id;
   ELSE
      RETURN QUERY
      SELECT u, u.user_name || u.user_id
      FROM   users u
      WHERE  u.user_name = _username;
   END IF;
END
$func$;

The «magic» is in the function call, where we (optionally) decompose the row type:

SELECT (users_row).*, custom_addition FROM get_user_by_username('foo', true);

db<>fiddle here (showing all)


If you need something more «dynamic», consider:

  • Refactor a PL/pgSQL function to return the output of various SELECT queries
CREATE OR REPLACE FUNCTION transformServers(data jsonb)
RETURNS SETOF record  as $$                        
DECLARE                                            
r record;                                          

BEGIN                                              
for r in select.serversSet.id,serversSet.hostname from jsonb_to_recordset(data->'data') AS           
 serversSet (                                       
 id bigint,                                         
 hostname text,                                                                                                                                  
) loop                                             
return next r;                                     
end loop;                                          
return;  
 END;
 $$ language plpgsql;

I run the above code, got an error saying » a column definition list is required for functions returning «record»

asked Feb 18, 2015 at 5:27

Xiujiao Gao's user avatar

1

Problem solved! Actually the problem is in another function, where I call transformServers(json).

Each time we call a function which returns setof record, we need to specify the columns, so when we call transformServers(json) we have to do it as

 select * from transformServers(json) as f(id bigint, hostname text) 

Before, I did

 select * from transformServers(json)

which does not work.

Roland Ewald's user avatar

Roland Ewald

4,5803 gold badges35 silver badges49 bronze badges

answered Feb 18, 2015 at 6:11

Xiujiao Gao's user avatar

Good afternon,

in 9.5.3 I have defined the following custom function:

CREATE OR REPLACE FUNCTION words_select_games(IN in_uid integer)
RETURNS SETOF RECORD AS
$func$
BEGIN
RETURN QUERY SELECT
g.gid AS gid,
EXTRACT(EPOCH FROM g.created)::int AS created,
g.player1 AS player1,
COALESCE(g.player2, 0) AS player2,
COALESCE(EXTRACT(EPOCH FROM g.played1)::int, 0) AS played1,
COALESCE(EXTRACT(EPOCH FROM g.played2)::int, 0) AS played2,
ARRAY_TO_STRING(g.hand1, ») AS hand1,
REGEXP_REPLACE(ARRAY_TO_STRING(g.hand2, »), ‘.’, ‘?’, ‘g’)
AS hand2,
g.letters AS letters, /* is a varchar[15][15] */
g.values AS values, /* is an integer[15][15] */
g.bid AS bid,
m.tiles AS last_tiles,
m.score AS last_score
FROM words_games g LEFT JOIN words_moves m USING(mid)
WHERE g.player1 = in_uid
UNION SELECT
g.gid AS gid,
EXTRACT(EPOCH FROM g.created)::int AS created,
g.player2 AS player1,
COALESCE(g.player2, 0) AS player1,
COALESCE(EXTRACT(EPOCH FROM g.played2)::int, 0) AS played1,
COALESCE(EXTRACT(EPOCH FROM g.played1)::int, 0) AS played2,
ARRAY_TO_STRING(g.hand2, ») AS hand1,
REGEXP_REPLACE(ARRAY_TO_STRING(g.hand1, »), ‘.’, ‘?’, ‘g’)
AS hand2,
g.letters AS letters,
g.values AS values,
g.bid AS bid,
m.tiles AS last_tiles,
m.score AS last_score
FROM words_games g LEFT JOIN words_moves m USING(mid)
WHERE g.player2 = in_uid;
END
$func$ LANGUAGE plpgsql;

but calling it gives me errors:

words=> select * from words_select_games(1);
ERROR: a column definition list is required for functions returning
«record»
LINE 1: select * from words_select_games(1);
^
words=> select gid, bid from words_select_games(1);

ERROR: a column definition list is required for
functions returning «record»
LINE 1: select gid, bid from words_select_games(1);
^

I have also unsuccessfully tried

RETURNS SETOF words_games, words_moves AS

and without the comma:

RETURNS SETOF words_games words_moves AS

How would you recommend to fix my declaration problem please?

Regards
Alex

Понравилась статья? Поделить с друзьями:
  • Power alpha error
  • Postgresql error 53200
  • Pow overflow error c builder
  • Pow domain error
  • Postgresql error 42p01 relation does not exist