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

时间:2016-02-03 19:52:10

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

在一个关于文字游戏的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()功能?

3 个答案:

答案 0 :(得分:3)

name := "TimetableAPI" version := "1.0" lazy val `timetableapi` = (project in file(".")).enablePlugins(PlayScala) scalaVersion := "2.11.7" libraryDependencies ++= Seq(cache, ws, specs2 % Test, evolutions, "mysql" % "mysql-connector-java" % "5.1.34", "com.typesafe.play" %% "play-slick" % "1.1.0", "com.typesafe.play" %% "play-slick-evolutions" % "1.1.0") unmanagedResourceDirectories in Test <+= baseDirectory(_ / "target/web/public/test") resolvers += "scalaz-bintray" at "https://dl.bintray.com/scalaz/releases" routesGenerator := InjectedRoutesGenerator 是一个返回Boolean的函数。你可以直接打电话:

is_vip(in_uid)

或者,如果你想使用select:

IF not is_vip(in_uid) THEN

如果要将标量查询用作值表达式,则查询必须加括号。

答案 1 :(得分: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的新智能锁定策略。详细解释:

我还使用is_vip()表达式将INSERT调用CASE调用到jQuery(function($) { $('input[name="type"]').change(function(event) { $('.2_details').toggle(this.value == 2) }); $('form').validate({ rules: { title: { required: true, minlength: 10 }, 3: { required: true, number: true }, 4: { required: true, number: true, min: function() { return +$('#3').val() || 0; } } } }); });查询中。这样效率更高。

其他一些小的优化。

答案 2 :(得分:1)

尝试使用schema.function_name进行更改,如下所示:

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