在PL / pgSQL函数中使用变量

时间:2016-12-28 18:28:24

标签: postgresql parameter-passing naming-conventions plpgsql quotes

Postgres PL/pgSQL docs say

  

对于任何不返回行的SQL命令,例如INSERT   如果没有RETURNING子句,则可以在a中执行命令   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会抛出错误。这应该是怎么写的?

2 个答案:

答案 0 :(得分:3)

您不能将参数名称放在单引号(''email''中,也不能使用参数email"因为它与#34相同;因为它的名称与表格中的一列。此名称冲突是强烈建议使用与其中一个表中的列名称相同的变量或参数的原因之一。您有三个选择处理这个:

  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);
    
  4. 我强烈建议选择选项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());
    

答案 1 :(得分:1)

@a_horse answers您的实际问题,并澄清引用问题和命名冲突。

关于引用:

关于命名冲突(plpgsql的行为随时间略有变化):

更好的解决方案

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

CREATE OR REPLACE FUNCTION app.create_identity(_email text, _passwd text
                                             , OUT new_identity_id int) AS
$func$
DECLARE
   _current_ts int := extract(epoch FROM now());
BEGIN
   LOOP
      --+ Generate compeltely 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*                             --
      --------------------------------------------------------------------------
      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 exception when exceeding n (100?) iterations
   END LOOP;
END
$func$  LANGUAGE plpgsql;

重点

  • 您的随机整数计算会导致integer out of range 错误,因为中间词int_max - int_min + 1integer一起运行,但结果赢了&# 39;适合。 我建议使用上面更便宜的正确的算法。

  • 输入带有例外条款的块比没有条件要贵得多。幸运的是,您实际上并不需要提出异常。使用UPSERT (INSERT ... ON CONFLICT ... DO NOTHING),便宜又优雅地解决这个问题(Postgres 9.5 + )。
    The manual:

      

    提示:包含EXCEPTION子句的块明显更多   进入和退出比没有一个的块贵。因此,不要   

  • 您也不需要额外的EXCEPTION构造。将IFSELECT一起使用。

  • 使WHERE new_identity_id参数简化。

  • 使用OUT子句直接将结果 RETURNING插入identity_id参数。除了更简单的代码和更快的执行之外,还有一个额外的微妙好处:您可以获得实际插入的值。如果表格中有触发器或规则,则可能与您使用OUT发送的内容不同。

  • PL / pgSQL中的分配相对昂贵。为了有效的代码,将这些减少到最低限度 您也可以删除最后剩余的变量INSERT,并在子查询中进行计算,然后根本不需要_current_ts。我离开了那个,因为计算它一次可能是有意义的,如果函数循环多次......

  • 剩下的就是一个 SQL命令,包含在DECLARE中重试直到成功。

  • 如果您的表格有可能溢出(使用全部或大多数LOOP个数字) - 严格来说,总是 机会 - 我会添加一个计数器并在可能100次迭代后引发异常以避免无限循环。