如何在 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()
功能正常?
您的功能可以更高效。使之成为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?
你的基本语法错误:像使用任何其他 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
查询中。这样效率更高。
其他几个小的优化。
在用于文字游戏的 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()
功能正常?
您的功能可以更高效。使之成为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?
我格式化了最重要的部分粗体:
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
查询中。这样效率更高。
其他几个小的优化。