在 PL/pgSQL 函数中使用变量

问题描述 投票:0回答:2

Postgres PL/pgSQL 文档说

对于任何不返回行的 SQL 命令,例如

INSERT
如果没有
RETURNING
子句,您可以在 只需编写命令即可实现 PL/pgSQL 功能。

命令文本中出现的任何 PL/pgSQL 变量名都被视为 一个参数,然后变量的当前值提供为 运行时的参数值。

但是当我在查询中使用变量名称时,我收到错误:

ERROR:  syntax error at or near "email"
LINE 16: ...d,email,password) values(identity_id,current_ts,''email'',''...

这是我的职责:

CREATE OR REPLACE FUNCTION app.create_identity(email varchar,passwd varchar)
RETURNS integer as $$
DECLARE
    current_ts          integer;
    new_identity_id     integer;
    int_max             integer;
    int_min             integer;
BEGIN
    SELECT extract(epoch FROM now())::integer INTO current_ts;
    int_min:=-2147483648;
    int_max:= 2147483647;
    LOOP
        BEGIN
            SELECT floor(int_min + (int_max - int_min + 1) * random()) INTO new_identity_id;
            IF new_identity_id != 0 THEN
                INSERT into app.identity(identity_id,date_inserted,email,password) values(identity_id,current_ts,''email'',''passwd'');
                RETURN new_identity_id;
            END IF;
        EXCEPTION
            WHEN unique_violation THEN
        END;
    END LOOP;
END;
$$ LANGUAGE plpgsql;

为什么当我在查询中使用变量时,Postgres 会抛出错误。这应该怎么写?

postgresql parameter-passing naming-conventions plpgsql quotes
2个回答
3
投票

您不能将参数名称放在单引号中 (

''email''
,并且您不能“按原样”使用参数
email
,因为它与表中的列具有相同的名称。此名称冲突是其中之一强烈建议不要使用与其中一个表中的列同名的变量或参数。您有三种选择来处理此问题:

  1. 重命名变量。常见的命名约定是在参数前面加上

    p_
    ,例如
    p_email
    ,然后在
    insert

    中使用明确的名称
    INSERT into app.identity(identity_id,date_inserted,email,password) 
    values(identity_id,current_ts,p_email,p_password);
    
  2. 使用

    $1
    作为第一个参数,使用
    $2
    作为第二个参数:

    INSERT into app.identity(identity_id,date_inserted,email,password) 
    values(identity_id,current_ts,$1,$2);
    
  3. 参数名称前加上函数名称前缀:

    INSERT into app.identity(identity_id,date_inserted,email,password) 
    values(identity_id,current_ts,create_identity.email,create_identity.password);
    

我强烈建议选择选项 1


不相关,但是:如果您不从表中检索这些值,则不需要 SELECT 语句来分配变量值。

SELECT extract(epoch FROM now())::integer INTO current_ts;

可以简化为:

current_ts := extract(epoch FROM now())::integer;

SELECT floor(int_min + (int_max - int_min + 1) * random()) INTO new_identity_id;

new_identity_id := floor(int_min + (int_max - int_min + 1) * random());

2
投票

关于引用:

关于命名冲突:

更好的解决方案

我建议采用完全不同的方法:

CREATE OR REPLACE FUNCTION app.create_identity(_email text, _passwd text
                                             , OUT new_identity_id int)
  LANGUAGE plpgsql AS
$func$
/* + Generate completely random int4 numbers +
-- integer (= int4) in Postgres is a signed integer occupying 4 bytes
-- int4 ranges from -2147483648 to +2147483647, i.e. -2^31 to 2^31 - 1
-- Multiply bigint 4294967296 (= 2^32) with random() (0.0 <= x < 1.0)
--   trunc() the resulting (positive!) float8 - cheaper than floor()
--   add result to -2147483648 and cast the next result back to int4
-- The result fits the int4 range *exactly*
*/
DECLARE
   _current_ts int := extract(epoch FROM now());
BEGIN
   LOOP
      INSERT INTO app.identity
            (identity_id, date_inserted,  email ,  password)
      SELECT _random_int, _current_ts  , _email , _passwd
      FROM  (SELECT (bigint '-2147483648'       -- could be int, but sum is bigint anyway
                   + bigint '4294967296' * random())::int) AS t(_random_int)  -- random int
      WHERE  _random_int <> 0                   -- exclude 0 (no insert)
      ON     CONFLICT (identity_id) DO NOTHING  -- no exception raised!
      RETURNING identity_id                     -- return *actually* inserted identity_id
      INTO   new_identity_id;                   -- OUT parameter, returned at end

      EXIT WHEN FOUND;                          -- exit after success
      -- maybe add counter and raise warning/exception when exceeding 5/10 (?) iterations
   END LOOP;
END
$func$;

要点

  • 您的随机整数计算将导致

    integer out of range
    错误,因为中间项
    int_max - int_min + 1
    integer
    一起运算,但结果不合适。以上更便宜且正确

  • 输入带有异常子句的块比没有异常子句要昂贵得多。幸运的是,您实际上不需要一开始就引发异常。使用 UPSERT (

    INSERT ... ON CONFLICT ... DO NOTHING
    ),可以廉价而优雅地解决这个问题(Postgres 9.5+)。
    说明书:

    提示: 包含

    EXCEPTION
    子句的块明显更多 进入和退出比没有进入和退出的街区昂贵。因此,不要 无需使用
    EXCEPTION

  • 您也不需要额外的

    IF
    。将
    SELECT
    WHERE
    一起使用。

  • new_identity_id
    设为
    OUT
    参数以简化。

  • 使用

    RETURNING
    子句并将 结果
    identity_id
    直接插入到
    OUT
    参数中。更简单、更快。还有一个额外的微妙好处:您可以获得“实际”插入的值。如果表中有触发器或规则,这可能与输入不同。

  • PL/pgSQL 中的赋值相对昂贵。将这些减少到最低限度以获得高效的代码。
  • 您也可以删除最后一个剩余变量

    _current_ts
    ,并在子查询中进行计算,那么您根本不需要

    DECLARE
    。我留下了那个,因为计算它
    一次
    可能是有意义的,如果函数循环多次......

  • 剩下的就是
  • 一个

    SQL 命令,包装到 LOOP 中以重试直至成功。

    
    

  • 如果你的表有可能溢出(使用大多数
  • int4

    数字) - 严格来说,存在

    always
    chance - 我会添加一个计数器并在 10 次迭代后引发异常以避免无限循环。尝试一半后就已经发出警告了。

© www.soinside.com 2019 - 2024. All rights reserved.