【问题标题】:How to use stored procedure returning boolean in IF-THEN-ENDIF condition?如何在 IF-THEN-ENDIF 条件下使用存储过程返回布尔值?
【发布时间】:2023-03-07 10:03:01
【问题描述】:

在一个文字游戏的 PostgreSQL 表中,我通过 vip_until 列或 grand_until 列(具有未来有效日期)来跟踪付费玩家:

create table users (
        uid serial primary key,
        vip_until timestamp null,  -- date in future indicates paying customer
        grand_until timestamp null -- date in future indicates paying customer
);

我写了一个简短的存储过程来检查:

create or replace function is_vip(
    IN in_uid integer,
    OUT out_vip boolean
) as $BODY$
        BEGIN
                out_vip := exists(select 1 from users 
                           where uid = in_uid and 
                           greatest(vip_until, grand_until) > current_timestamp);
        END;
$BODY$ language plpgsql;

然后我尝试在另一个存储过程中使用上述函数:

create or replace function join_new_game(
    IN in_uid integer,
    IN in_letters varchar(130),
    IN in_style integer,
    OUT out_gid integer
) as $BODY$
        BEGIN
        /* maybe there is a new game already, just waiting for the player's 1st move*/
        select gid into out_gid from games 
        where (player1 = in_uid and stamp1 is null) 
        or (player2 = in_uid and stamp2 is null) limit 1;

        IF not found THEN
                /* try to find games having just 1 player (with different uid) */
                select gid into out_gid from games 
                where (player1 != in_uid and stamp1 is not null
                and player2 is null) limit 1;

                IF not found THEN
                        /* only allow board style 1 for non-paying customers */
                        IF not select is_vip(in_uid) THEN
                                in_style := 1;  -- the above line fails
                        END IF;

                        /* create new game with player1 = uid and stamp1 = null */
                        insert into games (
                                created, 
                                player1, 
                                stamp1, 
                                stamp2, 
                                letters1, 
                                letters2, 
                                letters, 
                                board, 
                                style 
                        ) values (
                                current_timestamp, 
                                in_uid, 
                                null, 
                                null, 
                                substring(in_letters, 1, 7), 
                                substring(in_letters, 8, 7), 
                                substring(in_letters, 15), 
                                rpad('', 225), -- fill 15x15 board
                                in_style
                        ) returning gid into out_gid;
                ELSE
                        update games set player2 = in_uid where gid = out_gid;
                END IF;
        END IF;
        END;
$BODY$ language plpgsql;

但是我得到这个语法错误:

ERROR:  syntax error at or near "select"
LINE 21:                         IF not select is_vip(in_uid) TH...
                                        ^

如何正确使用is_vip()功能?

【问题讨论】:

标签: postgresql plpgsql user-defined-functions postgresql-9.5


【解决方案1】:

is_vip(in_uid) 是一个返回布尔值的函数。可以直接调用:

IF not is_vip(in_uid) THEN

或者,如果你想使用选择:

IF not (select is_vip(in_uid)) THEN

如果您想使用标量查询作为值表达式,则查询必须用括号括起来。

【讨论】:

    【解决方案2】:

    如何正确编写is_vip()函数?

    您的功能可以更有效率。使其成为STABLE SQL 函数,因此它可以是inlined

    CREATE OR REPLACE FUNCTION is_vip(in_uid integer)
      RETURNS boolean AS
    $func$
    SELECT EXISTS (
       SELECT 1 FROM users
       WHERE  uid = in_uid
       AND   (vip_until   > current_timestamp OR
              grand_until > current_timestamp)
       )
    $func$ LANGUAGE sql STABLE;
    

    理想情况下,您有一个多列索引以允许仅在以下位置进行索引扫描:

    (uid, vip_until, grand_until)
    

    uid 必须是第一列。

    How to use the is_vip function properly?

    @kin already provided a fix 用于您的基本语法错误:使用您自己的函数,就像任何其他 Postgres 函数一样。但这里还有更多

    我格式化了最重要的部分粗体

    CREATE OR REPLACE FUNCTION join_new_game(
        IN in_uid integer,
        IN in_letters varchar(130),
        IN in_style integer,
        OUT out_gid integer) AS
    $func$
    BEGIN
       /* maybe there is a new game already, just waiting for the player's 1st move*/
       SELECT gid INTO out_gid
       FROM   games 
       WHERE (player1 = in_uid AND stamp1 IS NULL) 
       OR    (player2 = in_uid AND stamp2 IS NULL)
       LIMIT 1;
    
       IF NOT FOUND THEN
          /* try to find games having just 1 player (with different uid) */
          /* and UPDATE immediately using a smart locking strategy */
          UPDATE games g
          SET    player2 = in_uid
          FROM  (
             SELECT gid
             FROM   games 
             WHERE  player1 <> in_uid
             AND    stamp1 IS NOT NULL
             AND    player2 IS NULL
             LIMIT  1
             FOR    UPDATE SKIP LOCKED  -- see link below !!
             ) g1
          WHERE   g.gid = g1.gid
          RETURNING g.gid
          INTO    out_gid;
    
          IF NOT FOUND THEN
             /* create new game with player1 = uid and stamp1 = null */
             INSERT INTO games (created, player1, stamp1, stamp2, letters1, letters2, letters, board, style)
             VALUES (current_timestamp, in_uid, null, null
                   , left(in_letters, 7)
                   , substring(in_letters, 8, 7)
                   , right(in_letters, -15)  -- guessing you want to start at pos 16!
                   , rpad('', 225)  -- fill 15x15 board
                   /* only allow board style 1 for non-paying customers */
                   , CASE WHEN NOT is_vip(in_uid) THEN 1 END  -- defaults to NULL
                   )
             RETURNING gid
             INTO  out_gid;
          END IF; 
       END IF;
    END
    $func$  LANGUAGE plpgsql;

    幸运的是,您使用的是最新版本的 Postgres (9.5),它引入了新的智能锁定策略以使用 FOR UPDATE SKIP LOCKED 进行排队。详细解释:

    我还使用CASE 表达式将is_vip() 调用内联到INSERT 查询中。这样更有效率。

    其他几个小优化。

    【讨论】:

    • Erwin,您是否错过了 is_vip 函数中 OR 条件周围的括号?在那个地方使用greatest(vip_until,grand_until) &gt; current_timestamp 会是一个坏主意,因为(uid, vip_until, grand_until) 上的索引不会被使用?
    • @AlexanderFarber:括号,是的,谢谢,已修复。是的,这个想法是让表达变得可悲。 (然后我意识到uid 无论如何都是主键,所以在这里只有一点点收获。)即使没有索引,替代形式也应该快一点。
    【解决方案3】:

    尝试像这样使用 schema.function_name 进行更改:

    IF (<schema>.is_vip(in_uid) = 'false')
    THEN
       ...
    END IF;
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-09-19
      • 1970-01-01
      • 2015-04-22
      • 2016-10-28
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多