如何在 IF-THEN-ENDIF 条件下使用存储过程返回布尔值?

How to use stored procedure returning boolean in IF-THEN-ENDIF condition?

在用于文字游戏的 PostgreSQL table 中,我通过 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()功能?

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

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

is_vip(in_uid) 是 returns 布尔值的函数。可以直接调用:

IF not is_vip(in_uid) THEN

或者,如果您想使用 select:

IF not (select is_vip(in_uid)) THEN

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

如何编写 is_vip() 功能正常?

您的功能可以更高效。使之成为STABLESQL函数,这样就可以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?

你的基本语法错误:像使用任何其他 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) */
      <b>/* and UPDATE immediately using a smart locking strategy */</b>
      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
         <b>FOR    UPDATE SKIP LOCKED  -- see link below !!</b>
         ) g1
      WHERE   g.gid = g1.gid
      <b>RETURNING g.gid
      INTO    out_gid</b>;

      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
               <b>, left(in_letters, 7)
               , substring(in_letters, 8, 7)
               , right(in_letters, -15)</b>  -- guessing you want to start at pos 16!
               , rpad('', 225)  -- fill 15x15 board
               /* only allow board style 1 for non-paying customers */
               <b>, CASE WHEN NOT is_vip(in_uid) THEN 1 END</b>  -- 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 查询中。这样效率更高。

其他几个小的优化。