PostgreSQL:错误:42601:返回“记录”的函数需要列定义列表

发布于 2024-12-22 15:10:31 字数 731 浏览 5 评论 0原文

据我所知,我的功能与我见过的示例非常相似。有人可以告诉我如何让它发挥作用吗?

create or replace function get_user_by_username(
    username varchar(250),
    online boolean
    ) returns setof record as $$
declare result record;
begin

    if online then 
        update users
        set last_activity = current_timestamp
        where user_name = username;
    end if;

    return query
    select
        user_id,
        user_name,
        last_activity,
        created,
        email,
        approved,
        last_lockout,
        last_login,
        last_password_changed,
        password_question,
        comment
    from
        users
    where
        user_name = username
    limit 1;

    return;
end;
$$ language plpgsql;

As far as I can tell, my function properly resembles the samples I've seen. Can someone clue me in as to how I get this to work?

create or replace function get_user_by_username(
    username varchar(250),
    online boolean
    ) returns setof record as $
declare result record;
begin

    if online then 
        update users
        set last_activity = current_timestamp
        where user_name = username;
    end if;

    return query
    select
        user_id,
        user_name,
        last_activity,
        created,
        email,
        approved,
        last_lockout,
        last_login,
        last_password_changed,
        password_question,
        comment
    from
        users
    where
        user_name = username
    limit 1;

    return;
end;
$ language plpgsql;

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

千寻… 2024-12-29 15:10:31

返回选定的列

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$;

调用:

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

您已DECLARE 结果记录; 但未使用该变量。我删除了那些残渣。

您可以直接从 UPDATE 返回记录,这比调用额外的 SELECT 语句要快得多。使用 RETURN QUERYUPDATE带有 RETURNING 子句

如果用户不是_online,则默认为普通SELECT。如果省略第二个参数,这也是(安全的)默认值 - 只有在函数定义中使用 DEFAULT false 提供默认值后才可能实现。

如果您在函数内部的查询中没有对列名 (tablename.columnname) 进行表限定,请注意列名和命名参数之间的命名冲突,这些冲突是可见的(大多数)在函数内随处可见。
您还可以通过对参数使用位置引用 ($n) 来避免此类冲突。或者使用您从不使用的列名称前缀:例如下划线 (_username)。

如果 users.username 在表中被定义为unique,那么第二个查询中的 LIMIT 1 就很糟糕了。如果不是,则UPDATE可以更新多行,这很可能是错误。我假设一个唯一的用户名并消除噪音。

定义函数的返回类型(如 @ertx 所示),否则您必须为每个函数调用提供列定义列表,这很尴尬。

为此目的创建类型(如 @ertx 提出的)是一种有效的方法,但对于单个函数来说可能有点过分了。在我们使用 RETURNS TABLE 用于此目的 - 就像上面演示的那样。

对于这个简单的函数,您不需要循环

每个函数都需要一个语言声明。在这种情况下,LANGUAGE plpgsql

我使用 timestamptz带时区的时间戳)而不是 timestamp不带时区的时间戳),这是理智的默认值。请参阅:

返回(一组)整行

要返回现有表 users所有列,有一个更简单的方法。 Postgres 自动为每个表定义一个同名的复合类型。只需使用 RETURNS SETOF users 即可大大简化查询:

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$;

返回整行加上自定义添加

要解决 TheRealChx101 在下面的评论中添加的问题:

如果除了整个表之外还有一个计算值怎么办?

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:

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:

∝单色的世界 2024-12-29 15:10:31

如果您想创建返回 setof 记录的函数,则需要在 select 语句中定义列类型

更多信息

您的查询应如下所示:(

select * from get_user_by_username('Username', True) as 
  f(user_id integer, user_name varchar, last_activity, varchar, created date, email        archar, approved boolean, last_lockout timestamp, last_login timestamp, 
  last_password_changed timestamp, password_question varchar, comment varchar)

您可能需要更改数据类型)

我个人更喜欢类型方法。它确保如果编辑该函数,所有查询都将返回正确的结果。这可能会很痛苦,因为每次修改函数的参数时,您都需要重新创建/删除类型。

例如:

CREATE TYPE return_type as 
(user_id integer,
 user_name varchar,
 last_activity varchar,
 created timestamp,
 email varchar,
 approved boolean,
 last_lockout timestamp ,
 last_login timestamp,
 last_password_changed timestamp,
 password_question varchar,
 comment varchar);

create or replace function get_user_by_username( username varchar(250), online 

boolean) returns setof return_type as $
declare _rec return_type;
begin
    if online then 
        update users
        set last_activity = current_timestamp
        where user_name = username;
    end if;
    for _rec in select
        user_id,
        user_name,
        last_activity,
        created,
        email,
        approved,
        last_lockout,
        last_login,
        last_password_changed,
        password_question,
        comment
      from
        users
      where
        user_name = username
      limit 1 
    loop

      return next _rec;

    end loop

end;
$ language plpgsql;

if you would like to create function returning setof record, you'll need to define column types in your select statement

More info

Your query should look something like this:

select * from get_user_by_username('Username', True) as 
  f(user_id integer, user_name varchar, last_activity, varchar, created date, email        archar, approved boolean, last_lockout timestamp, last_login timestamp, 
  last_password_changed timestamp, password_question varchar, comment varchar)

(you will probably need to change the data types)

I personaly prefer the types approach. it assures that if the function is edited, all the queries will return correct results. It might be a pain because every time you modify function's arguments you'll need to recreate/drop types aswell tho.

Eg:

CREATE TYPE return_type as 
(user_id integer,
 user_name varchar,
 last_activity varchar,
 created timestamp,
 email varchar,
 approved boolean,
 last_lockout timestamp ,
 last_login timestamp,
 last_password_changed timestamp,
 password_question varchar,
 comment varchar);

create or replace function get_user_by_username( username varchar(250), online 

boolean) returns setof return_type as $
declare _rec return_type;
begin
    if online then 
        update users
        set last_activity = current_timestamp
        where user_name = username;
    end if;
    for _rec in select
        user_id,
        user_name,
        last_activity,
        created,
        email,
        approved,
        last_lockout,
        last_login,
        last_password_changed,
        password_question,
        comment
      from
        users
      where
        user_name = username
      limit 1 
    loop

      return next _rec;

    end loop

end;
$ language plpgsql;
忆伤 2024-12-29 15:10:31

更改

SELECT * FROM foo(params);

SELECT foo(params);

在你的情况下它将是

SELECT get_user_by_username('myuser', true);

Change

SELECT * FROM foo(params);

to

SELECT foo(params);

In your case it would be

SELECT get_user_by_username('myuser', true);
命硬 2024-12-29 15:10:31

我创建了 my_func() ,它使用 [RETURN NEXT][1] 或 [RETURN QUERY][1] 语句返回 SETOF RECORD 类型,如下所示:

CREATE FUNCTION my_func() RETURNS SETOF RECORD AS $
DECLARE 
  row RECORD;
BEGIN
  FOR row IN VALUES ('John','Smith'), ('David','Miller') LOOP
    RETURN NEXT row;
  END LOOP;
  RETURN;
END;    
$ LANGUAGE plpgsql;

或者:

CREATE FUNCTION my_func() RETURNS SETOF RECORD AS $
BEGIN
  RETURN QUERY VALUES ('John','Smith'), ('David','Miller');
END;    
$ LANGUAGE plpgsql;

然后,调用 my_func() 得到了相同的错误,如下所示:

postgres=# SELECT * FROM my_func();
ERROR:  a column definition list is required for functions returning "record"
LINE 1: SELECT * FROM my_func();
                      ^

因此,我在 my_func() 之后使用 AS 设置了一个列定义列表,然后我可以调用 my_func() 时不会出现错误,如下所示:

postgres=# SELECT * FROM my_func() AS (f_n TEXT, l_n TEXT);
  f_n  |  l_n
-------+--------
 John  | Smith
 David | Miller
(2 rows)

此外,在不使用 FROM 子句的情况下调用 my_func() 会出现语法错误,如下所示:

postgres=# SELECT my_func() AS (f_n TEXT, l_n TEXT);
ERROR:  syntax error at or near "("
LINE 1: SELECT my_func() AS (f_n TEXT, l_n TEXT);

并且,在没有 FROM 子句和列定义列表的情况下调用 my_func() 会出现[错误][2],如下所示:

postgres=# SELECT my_func();
ERROR:  materialize mode required, but it is not allowed in this context

I created my_func() which returns SETOF RECORD type with a [RETURN NEXT][1] or [RETURN QUERY][1] statement as shown below:

CREATE FUNCTION my_func() RETURNS SETOF RECORD AS $
DECLARE 
  row RECORD;
BEGIN
  FOR row IN VALUES ('John','Smith'), ('David','Miller') LOOP
    RETURN NEXT row;
  END LOOP;
  RETURN;
END;    
$ LANGUAGE plpgsql;

Or:

CREATE FUNCTION my_func() RETURNS SETOF RECORD AS $
BEGIN
  RETURN QUERY VALUES ('John','Smith'), ('David','Miller');
END;    
$ LANGUAGE plpgsql;

Then, calling my_func() got the same error as shown below:

postgres=# SELECT * FROM my_func();
ERROR:  a column definition list is required for functions returning "record"
LINE 1: SELECT * FROM my_func();
                      ^

So, I set a column definition list just after my_func() with AS, then I could call my_func() without error as shown below:

postgres=# SELECT * FROM my_func() AS (f_n TEXT, l_n TEXT);
  f_n  |  l_n
-------+--------
 John  | Smith
 David | Miller
(2 rows)

In addition, calling my_func() without a FROM clause got the syntax error as shown below:

postgres=# SELECT my_func() AS (f_n TEXT, l_n TEXT);
ERROR:  syntax error at or near "("
LINE 1: SELECT my_func() AS (f_n TEXT, l_n TEXT);

And, calling my_func() without a FROM clause and column definition list got [the error][2] as shown below:

postgres=# SELECT my_func();
ERROR:  materialize mode required, but it is not allowed in this context
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文