【问题标题】:PostgreSQL: ERROR: 42601: a column definition list is required for functions returning "record"PostgreSQL:错误:42601:返回“记录”的函数需要列定义列表
【发布时间】:2012-01-26 04:06:35
【问题描述】:

据我所知,我的函数与我看到的示例非常相似。有人能告诉我我是如何让它工作的吗?

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;

【问题讨论】:

    标签: postgresql parameter-passing plpgsql set-returning-functions


    【解决方案1】:

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

    More info

    您的查询应如下所示:

    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;
    

    【讨论】:

    • 示例如何定义列类型?对我来说,它看起来只是插入行。 return query 不应该发回结果吗?你能举个例子说明我将如何改变我的功能以满足要求吗?
    • 谢谢...不知道为什么我正在查看的任何文档中都没有显示。
    • 您也可以使用“out”参数,其工作方式类似于声明类型,但您不必管理单独的对象。
    • 正如这里提到的,您还可以定义一个自定义类型并将其与 json_populate_record 结合使用以实现相同的目标,而无需 plpgsql-dba.stackexchange.com/questions/186033/…
    【解决方案2】:

    返回选定的列

    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 result record; 但没有使用该变量。我删除了垃圾。

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

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

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

    如果 users.username 在您的表中被定义为 unique,那么第二个查询中的 LIMIT 1 就很简单了。如果是not,那么UPDATE 可以更新多行,这很可能是错误。我假设一个独特的username 并修剪噪音。

    定义函数的返回类型(就像@ertx 演示的那样)或者你必须为每个函数调用提供一个列定义列表,这很尴尬。

    为此目的创建一个类型(如@ertx 建议的)是一种有效的方法,但对于单个函数来说可能有点过分了。在我们为此目的使用 RETURNS TABLE 之前,这是旧版本 Postgres 的方式——如上所示。

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

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

    我使用timestamptz (timestamp with time zone) 而不是timestamp (timestamp without time zone),这是正常的默认设置。见:

    返回(组)整行

    要返回现有表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 在下面的评论中添加的问题:

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

    没有那么简单,但可行。我们可以将整个行类型发送为 one 字段,并添加更多:

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

    “魔法”在函数调用中,我们(可选地)分解行类型:

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

    dbfiddle here(显示全部)


    如果您需要更“动态”的东西,请考虑:

    【讨论】:

    • 非常干净。 Ertx 把我带到了一个我可以弄清楚其余部分的地方,但这是一篇很棒的文章。我希望它能为其他新手 PostgreSQL 从业者消除一些困惑。
    • 如果除了整张表之外还有一个计算值怎么办? ?
    • @TheRealChx101:我添加了第三个解决方案来解决这个问题。
    • 谢谢。这有帮助。你就像 SO man 周围的数据库耶稣。有时我想知道你脑子里在想什么哈哈哈。
    猜你喜欢
    • 2017-01-02
    • 2015-04-19
    • 1970-01-01
    • 2013-02-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-02-14
    • 1970-01-01
    相关资源
    最近更新 更多